diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java index 198d3333b0..f551f8841e 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java @@ -60,6 +60,15 @@ public class SecretKeyPacket * Users should migrate to AEAD with all due speed. */ public static final int USAGE_AEAD = 0xfd; + + /** + * S2K-usage octet indicating that the secret key material is stored on a hardware-token. + * + * @see + * OpenPGP Hardware-Backed Secret Keys + */ + public static final int USAGE_HARDWARE_BACKED = 0xfc; + private PublicKeyPacket pubKeyPacket; private byte[] secKeyData; private int s2kUsage; @@ -119,6 +128,11 @@ public class SecretKeyPacket int version = pubKeyPacket.getVersion(); s2kUsage = in.read(); + if (s2kUsage == USAGE_HARDWARE_BACKED) + { + return; + } + if (version == 6 && s2kUsage != USAGE_NONE) { // TODO: Use length to parse unknown parameters @@ -290,19 +304,21 @@ public byte[] getEncodedContents() pOut.write(pubKeyPacket.getEncodedContents()); pOut.write(s2kUsage); - - // conditional parameters - byte[] conditionalParameters = encodeConditionalParameters(); - if (pubKeyPacket.getVersion() == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE) + if (s2kUsage != USAGE_HARDWARE_BACKED) { - pOut.write(conditionalParameters.length); - } - pOut.write(conditionalParameters); + // conditional parameters + byte[] conditionalParameters = encodeConditionalParameters(); + if (pubKeyPacket.getVersion() == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE) + { + pOut.write(conditionalParameters.length); + } + pOut.write(conditionalParameters); - // encrypted secret key - if (secKeyData != null && secKeyData.length > 0) - { - pOut.write(secKeyData); + // encrypted secret key + if (secKeyData != null && secKeyData.length > 0) + { + pOut.write(secKeyData); + } } pOut.close(); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/HardwareSecretKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/HardwareSecretKeyTest.java new file mode 100644 index 0000000000..8994b0bdf5 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/HardwareSecretKeyTest.java @@ -0,0 +1,76 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class HardwareSecretKeyTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "HardwareSecretKeyTest"; + } + + @Override + public void performTest() + throws Exception + { + parseHardwareKey(); + } + + private void parseHardwareKey() + throws IOException, PGPException + { + // Test vector from https://www.ietf.org/archive/id/draft-dkg-openpgp-hardware-secrets-02.html#name-as-a-hardware-backed-secret + String armored = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "xTQEZgWtcxYJKwYBBAHaRw8BAQdAlLK6UPQsVHR2ETk1SwVIG3tBmpiEtikYYlCy\n" + + "1TIiqzb8zR08aGFyZHdhcmUtc2VjcmV0QGV4YW1wbGUub3JnPsKNBBAWCAA1AhkB\n" + + "BQJmBa1zAhsDCAsJCAcKDQwLBRUKCQgLAhYCFiEEXlP8Tur0WZR+f0I33/i9Uh4O\n" + + "HEkACgkQ3/i9Uh4OHEnryAD8CzH2ajJvASp46ApfI4pLPY57rjBX++d/2FQPRyqG\n" + + "HJUA/RLsNNgxiFYmK5cjtQe2/DgzWQ7R6PxPC6oa3XM7xPcCxzkEZgWtcxIKKwYB\n" + + "BAGXVQEFAQEHQE1YXOKeaklwG01Yab4xopP9wbu1E+pCrP1xQpiFZW5KAwEIB/zC\n" + + "eAQYFggAIAUCZgWtcwIbDBYhBF5T/E7q9FmUfn9CN9/4vVIeDhxJAAoJEN/4vVIe\n" + + "DhxJVTgA/1WaFrKdP3AgL0Ffdooc5XXbjQsj0uHo6FZSHRI4pchMAQCyJnKQ3RvW\n" + + "/0gm41JCqImyg2fxWG4hY0N5Q7Rc6PyzDQ==\n" + + "=3w/O\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + ByteArrayInputStream bIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = ArmoredInputStream.builder().setIgnoreCRC(false).build(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + + PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(pIn, new BcKeyFingerprintCalculator()); + for (PGPSecretKey k : secretKeys) + { + isEquals("S2K Usage mismatch", SecretKeyPacket.USAGE_HARDWARE_BACKED, k.getS2KUsage()); + } + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = ArmoredOutputStream.builder().clearHeaders().build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKeys.encode(pOut); + pOut.close(); + aOut.close(); + + isEquals("Armor encoding mismatch", armored, bOut.toString()); + } + + public static void main(String[] args) + { + runTest(new HardwareSecretKeyTest()); + } +}