From 48d468c31070f5ba9c95b645d3fd8909ecb5fca4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 28 Oct 2022 16:36:08 +0200 Subject: [PATCH] Add and test GnuDummyKeyUtil --- .../key/gnu_dummy_s2k/GNUExtension.java | 24 +++ .../key/gnu_dummy_s2k/GnuDummyKeyUtil.java | 114 ++++++++++ .../key/gnu_dummy_s2k/package-info.java | 8 + .../HardwareSecurityTest.java | 82 ++++++++ .../gnu_dummy_s2k/GnuDummyKeyUtilTest.java | 199 ++++++++++++++++++ 5 files changed, 427 insertions(+) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GNUExtension.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtil.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/package-info.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/decryption_verification/HardwareSecurityTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtilTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GNUExtension.java b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GNUExtension.java new file mode 100644 index 00000000..ff42ef6e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GNUExtension.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.gnu_dummy_s2k; + +import org.bouncycastle.bcpg.S2K; + +public enum GNUExtension { + + NO_PRIVATE_KEY(S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY), + DIVERT_TO_CARD(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD), + ; + + private final int id; + + GNUExtension(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtil.java new file mode 100644 index 00000000..a8de5aa3 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtil.java @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.gnu_dummy_s2k; + +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.SecretSubkeyPacket; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class GnuDummyKeyUtil { + + private GnuDummyKeyUtil() { + + } + + public static Builder modify(PGPSecretKeyRing secretKeys) { + return new Builder(secretKeys); + } + + public static final class Builder { + + private final PGPSecretKeyRing keys; + + private Builder(PGPSecretKeyRing keys) { + this.keys = keys; + } + + public PGPSecretKeyRing removePrivateKeys(KeyFilter filter) { + return doIt(GNUExtension.NO_PRIVATE_KEY, null, filter); + } + + public PGPSecretKeyRing divertPrivateKeysToCard(KeyFilter filter) { + return divertPrivateKeysToCard(filter, new byte[16]); + } + + public PGPSecretKeyRing divertPrivateKeysToCard(KeyFilter filter, byte[] cardSerialNumber) { + return doIt(GNUExtension.DIVERT_TO_CARD, cardSerialNumber, filter); + } + + private PGPSecretKeyRing doIt(GNUExtension extension, byte[] serial, KeyFilter filter) { + byte[] encodedSerial = serial != null ? encodeSerial(serial) : null; + S2K s2k = extensionToS2K(extension); + + List secretKeyList = new ArrayList<>(); + for (PGPSecretKey secretKey : keys) { + if (!filter.filter(secretKey.getKeyID())) { + // No conversion, do not modify subkey + secretKeyList.add(secretKey); + continue; + } + + PublicKeyPacket publicKeyPacket = secretKey.getPublicKey().getPublicKeyPacket(); + if (secretKey.isMasterKey()) { + SecretKeyPacket keyPacket = new SecretKeyPacket(publicKeyPacket, + 0, 255, s2k, null, encodedSerial); + PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey()); + secretKeyList.add(onCard); + } else { + SecretSubkeyPacket keyPacket = new SecretSubkeyPacket(publicKeyPacket, + 0, 255, s2k, null, encodedSerial); + PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey()); + secretKeyList.add(onCard); + } + } + + PGPSecretKeyRing gnuDummyKey = new PGPSecretKeyRing(secretKeyList); + return gnuDummyKey; + } + + private byte[] encodeSerial(byte[] serial) { + byte[] encoded = new byte[serial.length + 1]; + encoded[0] = 0x10; + System.arraycopy(serial, 0, encoded, 1, serial.length); + return encoded; + } + + private S2K extensionToS2K(GNUExtension extension) { + S2K s2k = S2K.gnuDummyS2K(extension == GNUExtension.DIVERT_TO_CARD ? + S2K.GNUDummyParams.divertToCard() : S2K.GNUDummyParams.noPrivateKey()); + return s2k; + } + } + + public interface KeyFilter { + + /** + * Return true, if the given key should be selected, false otherwise. + * + * @param keyId id of the key + * @return select + */ + boolean filter(long keyId); + + static KeyFilter any() { + return keyId -> true; + } + + static KeyFilter only(long onlyKeyId) { + return keyId -> keyId == onlyKeyId; + } + + static KeyFilter selected(Collection ids) { + return keyId -> ids.contains(keyId); + } + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/package-info.java new file mode 100644 index 00000000..5c2a727e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/gnu_dummy_s2k/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Utility classes related to creating keys with GNU DUMMY S2K values. + */ +package org.pgpainless.key.gnu_dummy_s2k; diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/HardwareSecurityTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/HardwareSecurityTest.java new file mode 100644 index 00000000..f1606cec --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/HardwareSecurityTest.java @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.SubkeyIdentifier; +import org.pgpainless.key.gnu_dummy_s2k.GnuDummyKeyUtil; +import org.pgpainless.key.util.KeyIdUtil; + +public class HardwareSecurityTest { + + private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: DE2E 9AB2 6650 8191 53E7 D599 C176 507F 2B5D 43B3\n" + + "Comment: Alice \n" + + "\n" + + "lFgEY1vjgRYJKwYBBAHaRw8BAQdAXjLoPTOIOdvlFT2Nt3rcvLTVx5ujPBGghZ5S\n" + + "D5tEnyoAAP0fAUJTiPrxZYdzs6MP0KFo+Nmr/wb1PJHTkzmYpt4wkRKBtBxBbGlj\n" + + "ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmNb44EJEMF2UH8rXUOz\n" + + "FiEE3i6asmZQgZFT59WZwXZQfytdQ7MCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" + + "AQAAHLYA/AgW+YrpU+UqrwX2dhY6RAfgHTTMU89RHjaTHJx8pLrBAP4gthGof00a\n" + + "XEjwTWteDOO049SIp2AUfj9deJqtrQcHD5xdBGNb44ESCisGAQQBl1UBBQEBB0DN\n" + + "vUT3awa3YLmwf41LRpPrm7B87AOHfYIP8S9QJ4GDJgMBCAcAAP9bwlSaF+lti8JY\n" + + "qKFO3qt3ZYQMu1l/LRBle89ZB4zD+BDOiHUEGBYKAB0FAmNb44ECngECmwwFFgID\n" + + "AQAECwkIBwUVCgkICwAKCRDBdlB/K11Ds/TsAP9kvpUrCWnrWGq+a9n1CqEfCMX5\n" + + "cT+qzrwNf+J0L22KowD+M9SVO0qssiAqutLE9h9dGYLbEiFvsHzK3WSnjKYbIgac\n" + + "WARjW+OBFgkrBgEEAdpHDwEBB0BCPh8M5TnXSmG6Ygwp4j5RR4u3hmxl8CYjX4h/\n" + + "XtvvNwAA/RP04coSrLHVI6vUfbJk4MhWYeyhJBRYY0vGp7yq+wVtEpKI1QQYFgoA\n" + + "fQUCY1vjgQKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNb44EACgkQ\n" + + "mlozJSF7rXQW+AD/TA3YBxTd+YbBSwfgqzWNbfT9BBcFrdn3uPCsbvfmqXoA/3oj\n" + + "oupkgoaXesrGxn2k9hW9/GBXSvNcgY2txZ6/oYoIAAoJEMF2UH8rXUOziZ4A/0Xl\n" + + "xSZJWmkRpBh5AO8Cnqosz6j947IYAxS16ay+sIOHAP9aN9CUNJIIdHnHdFHO4GZz\n" + + "ejjknn4wt8NVJP97JxlnBQ==\n" + + "=qSQb\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + @Test + public void testGetSingleIdOfHardwareBackedKey() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + assertTrue(HardwareSecurity.getIdsOfHardwareBackedKeys(secretKeys).isEmpty()); + long encryptionKeyId = KeyIdUtil.fromLongKeyId("0AAD8F5891262F50"); + + PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.only(encryptionKeyId)); + + Set hardwareBackedKeys = HardwareSecurity + .getIdsOfHardwareBackedKeys(withHardwareBackedEncryptionKey); + assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys, encryptionKeyId)), hardwareBackedKeys); + } + + + @Test + public void testGetIdsOfFullyHardwareBackedKey() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + assertTrue(HardwareSecurity.getIdsOfHardwareBackedKeys(secretKeys).isEmpty()); + + PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.any()); + Set expected = new HashSet<>(); + for (PGPSecretKey key : secretKeys) { + expected.add(new SubkeyIdentifier(secretKeys, key.getKeyID())); + } + + Set hardwareBackedKeys = HardwareSecurity + .getIdsOfHardwareBackedKeys(withHardwareBackedEncryptionKey); + + assertEquals(expected, hardwareBackedKeys); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtilTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtilTest.java new file mode 100644 index 00000000..0b19c551 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/gnu_dummy_s2k/GnuDummyKeyUtilTest.java @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.gnu_dummy_s2k; + +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.util.KeyIdUtil; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GnuDummyKeyUtilTest { + // normal, non-hw-backed key + private static final String FULL_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" + + "Comment: Hardy Hardware \n" + + "\n" + + "lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" + + "eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" + + "eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" + + "qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" + + "l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUB\n" + + "AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeH\n" + + "VtBaHi6efXvnc4rdVj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsM\n" + + "BRYCAwEABAsJCAcFFQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdi\n" + + "Ags0yZrQPkMs6eL+83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUT\n" + + "vP0BnFgEY1vSiBYJKwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D\n" + + "6nfwJtbDT0YAAQCgnCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUE\n" + + "GBYKAH0FAmNb0ogCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KI\n" + + "AAoJEJQCL6VtwFtJDmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5f\n" + + "AQD2oLSEW1GOgIs64+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6\n" + + "AP9Xftw8xZ7/MWhYImk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwG\n" + + "jF6AYiLOzO/R1x5bSlYD3FeJ3Qo=\n" + + "=+vXp\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + private static final long primaryKeyId = KeyIdUtil.fromLongKeyId("C312C97DA9F76A4F"); + private static final long encryptionKeyId = KeyIdUtil.fromLongKeyId("6924D066714CE8C6"); + private static final long signatureKeyId = KeyIdUtil.fromLongKeyId("94022FA56DC05B49"); + private static final byte[] cardSerial = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + + public static final String ALL_KEYS_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" + + "Comment: Hardy Hardware \n" + + "\n" + + "lEwEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" + + "eBwCeTT/AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PtCBIYXJkeSBIYXJkd2FyZSA8\n" + + "aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9qfdqTxYhBAH9q2zg\n" + + "SlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAPk2AP92\n" + + "2T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1Ml9qqx6QGcaNKe8de\n" + + "Me3EhTant6mS9tqMHp2/3gmcUQRjW9KIEgorBgEEAZdVAQUBAQdAVXBLNvNmFh9K\n" + + "X6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgH/wBlAEdOVQIQAAECAwQFBgcICQoL\n" + + "DA0OD4h1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQwxLJ\n" + + "fan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+83te770A/jG0DeJy\n" + + "+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnEwEY1vSiBYJKwYBBAHaRw8BAQdA\n" + + "vSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0b/AGUAR05VAhAAAQIDBAUG\n" + + "BwgJCgsMDQ4PiNUEGBYKAH0FAmNb0ogCngECmwIFFgIDAQAECwkIBwUVCgkIC18g\n" + + "BBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJDmMBAKqsGfRFQxJXyPgugWBgEaO5lt9f\n" + + "MM0yUxa76cmSWe5fAQD2oLSEW1GOgIs64+Z3gvtXopmeupT09HhI7ger98zDAwAK\n" + + "CRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhYImk/xheqPy07K4qo3T1pGKUvUqjWQQEA\n" + + "hE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5bSlYD3FeJ3Qo=\n" + + "=wsFa\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + public static final String PRIMARY_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" + + "Comment: Hardy Hardware \n" + + "\n" + + "lEwEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" + + "eBwCeTT/AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PtCBIYXJkeSBIYXJkd2FyZSA8\n" + + "aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9qfdqTxYhBAH9q2zg\n" + + "SlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAPk2AP92\n" + + "2T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1Ml9qqx6QGcaNKe8de\n" + + "Me3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUBAQdAVXBLNvNmFh9K\n" + + "X6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeHVtBaHi6efXvnc4rd\n" + + "Vj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcF\n" + + "FQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+\n" + + "83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnFgEY1vSiBYJ\n" + + "KwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0YAAQCg\n" + + "nCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUEGBYKAH0FAmNb0ogC\n" + + "ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" + + "DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" + + "4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" + + "Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" + + "SlYD3FeJ3Qo=\n" + + "=s+B1\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + public static final String ENCRYPTION_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" + + "Comment: Hardy Hardware \n" + + "\n" + + "lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" + + "eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" + + "eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" + + "qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" + + "l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcUQRjW9KIEgorBgEEAZdVAQUB\n" + + "AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgH/wBlAEdOVQIQ\n" + + "AAECAwQFBgcICQoLDA0OD4h1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcF\n" + + "FQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+\n" + + "83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnFgEY1vSiBYJ\n" + + "KwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0YAAQCg\n" + + "nCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUEGBYKAH0FAmNb0ogC\n" + + "ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" + + "DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" + + "4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" + + "Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" + + "SlYD3FeJ3Qo=\n" + + "=TPAl\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + public static final String SIGNATURE_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" + + "Comment: Hardy Hardware \n" + + "\n" + + "lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" + + "eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" + + "eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" + + "qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" + + "l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUB\n" + + "AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeH\n" + + "VtBaHi6efXvnc4rdVj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsM\n" + + "BRYCAwEABAsJCAcFFQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdi\n" + + "Ags0yZrQPkMs6eL+83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUT\n" + + "vP0BnEwEY1vSiBYJKwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D\n" + + "6nfwJtbDT0b/AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PiNUEGBYKAH0FAmNb0ogC\n" + + "ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" + + "DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" + + "4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" + + "Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" + + "SlYD3FeJ3Qo=\n" + + "=p8I9\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + @Test + public void testMoveAllKeysToCard() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); + PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_ON_CARD); + + PGPSecretKeyRing onCard = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.any(), cardSerial); + + for (PGPSecretKey key : onCard) { + assertEquals(255, key.getS2KUsage()); + S2K s2K = key.getS2K(); + assertEquals(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD, s2K.getProtectionMode()); + } + + assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + } + + @Test + public void testMovePrimaryKeyToCard() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); + PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(PRIMARY_KEY_ON_CARD); + + PGPSecretKeyRing onCard = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.only(primaryKeyId), cardSerial); + + assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + } + + @Test + public void testMoveEncryptionKeyToCard() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); + PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_KEY_ON_CARD); + + PGPSecretKeyRing onCard = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.only(encryptionKeyId), cardSerial); + + assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + } + + @Test + public void testMoveSigningKeyToCard() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); + PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(SIGNATURE_KEY_ON_CARD); + + PGPSecretKeyRing onCard = GnuDummyKeyUtil.modify(secretKeys) + .divertPrivateKeysToCard(GnuDummyKeyUtil.KeyFilter.only(signatureKeyId), cardSerial); + + assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + } +}