From 44d1110bec8efdbd409b1073aed0396aa60d67c0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 28 Jun 2026 21:18:28 +0200 Subject: [PATCH] PGSC: JCE implementation --- build.gradle | 2 +- .../operator/bc/RFC6637KDFCalculator.java | 2 +- .../operator/jcajce/JcaPGPKeyConverter.java | 2 +- ...lPublicKeyDataDecryptorFactoryBuilder.java | 397 ++++++++++++++++++ .../JcePublicKeyDataDecryptorFactory.java | 4 + .../test/ExternalSecretKeyPacketTest.java | 1 - .../smartcard/OpenPGPSmartCardBackend.java | 1 - .../yubikey/YubikeyOpenPGPSmartCard.java | 2 +- .../yubikey/YubikeySmartCardBackend.java | 91 +++- ...YubikeyPublicKeyDataDecryptorFactory.java} | 6 +- ...yPublicKeyDataDecryptorFactoryBuilder.java | 180 ++++++++ ...ymousRecipientSmartCardDecryptionTest.java | 38 +- .../test/SmartCardMessageDecryptionTest.java | 266 ++++++++++-- .../test/SmartCardTestProperties.java | 6 +- ...relatedSmartCardMessageDecryptionTest.java | 26 +- .../yubikey/CloseYubikeySessionTest.java | 9 +- .../yubikey/YubikeyTestInstanceProvider.java | 14 +- .../smartcard/yubikey/YubikeyTests.java | 44 +- 18 files changed, 1034 insertions(+), 57 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceExternalPublicKeyDataDecryptorFactoryBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactory.java rename pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/{YubikeyPublicKeyDataDecryptorFactory.java => bc/BcYubikeyPublicKeyDataDecryptorFactory.java} (97%) create mode 100644 pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/jcajce/JceYubikeyPublicKeyDataDecryptorFactoryBuilder.java diff --git a/build.gradle b/build.gradle index 35ce692d49..5db6d2a04c 100644 --- a/build.gradle +++ b/build.gradle @@ -297,7 +297,7 @@ configure(subprojects.findAll {it.name != 'bom'}) { } tasks.withType(JavaCompile).configureEach { - options.debug = releaseDebug; + options.debug = true; } tasks.withType(Test).configureEach { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/RFC6637KDFCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/RFC6637KDFCalculator.java index f0692a3965..0fc2135007 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/RFC6637KDFCalculator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/RFC6637KDFCalculator.java @@ -15,7 +15,7 @@ /** * Calculator for the EC based KDF algorithm described in RFC 6637 */ -class RFC6637KDFCalculator +public class RFC6637KDFCalculator { // "Anonymous Sender ", which is the octet sequence private static final byte[] ANONYMOUS_SENDER = Hex.decode("416E6F6E796D6F75732053656E64657220202020"); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java index a8a9c15f25..69b223c5d9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java @@ -669,7 +669,7 @@ public BCPGKey getBCPGKey(byte[] key) } } - private BCPGKey getPublicBCPGKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey) + public BCPGKey getPublicBCPGKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey) throws PGPException { switch (algorithm) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceExternalPublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceExternalPublicKeyDataDecryptorFactoryBuilder.java new file mode 100644 index 0000000000..355c2ed02c --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceExternalPublicKeyDataDecryptorFactoryBuilder.java @@ -0,0 +1,397 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Date; + +import javax.crypto.Cipher; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParametersHolder; +import org.bouncycastle.bcpg.*; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.util.NamedJcaJceHelper; +import org.bouncycastle.jcajce.util.ProviderJcaJceHelper; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.openpgp.*; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.operator.AbstractPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PGPDataDecryptor; +import org.bouncycastle.openpgp.operator.PGPPad; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.RFC6637Utils; +import org.bouncycastle.openpgp.operator.bc.RFC6637KDFCalculator; +import org.bouncycastle.util.Arrays; + +import static org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory.unwrapSessionData; + +public abstract class JceExternalPublicKeyDataDecryptorFactoryBuilder +{ + private final JcePublicKeyDataDecryptorFactoryBuilder softwareDecryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder(); + + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); + private JceAEADUtil aeadHelper = new JceAEADUtil(contentHelper); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); + + public JceExternalPublicKeyDataDecryptorFactoryBuilder() + { + } + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JceExternalPublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider) + { + softwareDecryptorFactory.setProvider(provider); + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + this.contentHelper = helper; + this.aeadHelper = new JceAEADUtil(contentHelper); + + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JceExternalPublicKeyDataDecryptorFactoryBuilder setProvider(String providerName) + { + softwareDecryptorFactory.setProvider(providerName); + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + this.contentHelper = helper; + this.aeadHelper = new JceAEADUtil(contentHelper); + + return this; + } + + public JceExternalPublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider) + { + softwareDecryptorFactory.setContentProvider(provider); + this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + this.aeadHelper = new JceAEADUtil(contentHelper); + + return this; + } + + public JceExternalPublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName) + { + softwareDecryptorFactory.setContentProvider(providerName); + this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + this.aeadHelper = new JceAEADUtil(contentHelper); + + return this; + } + + public abstract PublicKeyDataDecryptorFactory build(OpenPGPKey.OpenPGPSecretKey secretKey) throws PGPException; + + protected PublicKeyDataDecryptorFactory build(final PGPKeyPair keyPair, PublicKeyCryptoCallback cryptoCallback) + { + final PGPPrivateKey privKey = keyPair.getPrivateKey(); + final PGPPublicKey pubKey = keyPair.getPublicKey(); + + if (privKey != null) + { + return softwareDecryptorFactory.build(privKey); + } + + return new AbstractPublicKeyDataDecryptorFactory() + { + @Override + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData, int pkeskVersion) + throws PGPException + { + boolean containsSKAlg = containsSKAlg(pkeskVersion); + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + return decryptSessionData(keyConverter, pubKey, secKeyData, cryptoCallback); + } + else if (keyAlgorithm == PublicKeyAlgorithmTags.X25519) { + try { + byte[] enc = secKeyData[0]; + int pLen = X25519PublicBCPGKey.LENGTH; + byte[] ephemeralKey = Arrays.copyOf(enc, pLen); + + // size of following fields + int size = enc[pLen] & 0xff; + checkRange(pLen + 1 + size, enc); + + // encrypted session key + boolean includesSesKeyAlg = containsSKAlg(pkeskVersion); + int sesKeyLen = size - (includesSesKeyAlg ? 1 : 0); + int sesKeyOff = pLen + 1 + (includesSesKeyAlg ? 1 : 0); + byte[] keyEnc = Arrays.copyOfRange(enc, sesKeyOff, sesKeyOff + sesKeyLen); + + byte[] secret = cryptoCallback.decryptX25519(getPublicKey(ephemeralKey, EdECObjectIdentifiers.id_X25519, 0)); + + byte[] hkdfOut = RFC6637KDFCalculator.createKey(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128, + Arrays.concatenate(ephemeralKey, pubKey.getPublicKeyPacket().getKey().getEncoded(), secret), + "OpenPGP X25519"); + return unwrapSessionData(keyEnc, SymmetricKeyAlgorithmTags.AES_128, new KeyParameter(hkdfOut)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + else if (keyAlgorithm == PublicKeyAlgorithmTags.X448) + { + throw new PGPException("X448 is not supported."); + } + + return decryptSessionData(keyAlgorithm, secKeyData, cryptoCallback); + } + + // OpenPGP v4 + @Override + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + + // OpenPGP v5 + @Override + public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey) + throws PGPException + { + return aeadHelper.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey); + } + + // OpenPGP v6 + @Override + public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey) + throws PGPException + { + return aeadHelper.createOpenPgpV6DataDecryptor(seipd, sessionKey); + } + }; + } + + /** + * Decrypt ECDH encrypted session keys. + * @param converter key converter + * @param pubKey our public key + * @param secKeyData encrypted session key + * @return decrypted session key + * @throws PGPException + */ + private byte[] decryptSessionData(JcaPGPKeyConverter converter, + PGPPublicKey pubKey, + byte[][] secKeyData, + PublicKeyCryptoCallback cryptoCallback) + throws PGPException + { + PublicKeyPacket pubKeyData = pubKey.getPublicKeyPacket(); + + byte[] enc = secKeyData[0]; + + byte[] pEnc; + byte[] keyEnc; + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + checkRange(2 + pLen + 1, enc); + + pEnc = new byte[pLen]; + System.arraycopy(enc, 2, pEnc, 0, pLen); + + int keyLen = enc[pLen + 2] & 0xff; + checkRange(2 + pLen + 1 + keyLen, enc); + + keyEnc = new byte[keyLen]; + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyLen); + + try + { + PublicKey publicKey; + byte[] decSessionKey; + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); + // XDH + if (JcaJcePGPUtil.isX25519(ecKey.getCurveOID())) + { + if (pEnc.length != (1 + X25519PublicBCPGKey.LENGTH) || 0x40 != pEnc[0]) + { + throw new IllegalArgumentException("Invalid Curve25519 public key"); + } + publicKey = getPublicKey(pEnc, EdECObjectIdentifiers.id_X25519, 1); + decSessionKey = cryptoCallback.decryptX25519(publicKey); + } + else if (ecKey.getCurveOID().equals(EdECObjectIdentifiers.id_X448)) + { + if (pEnc.length != (1 + X448PublicBCPGKey.LENGTH) || 0x40 != pEnc[0]) + { + throw new IllegalArgumentException("Invalid Curve25519 public key"); + } + publicKey = getPublicKey(pEnc, EdECObjectIdentifiers.id_X448, 1); + decSessionKey = cryptoCallback.decryptX448(publicKey); + } + else + { + X9ECParametersHolder x9Params = ECNamedCurveTable.getByOIDLazy(ecKey.getCurveOID()); + ECPoint publicPoint = x9Params.getCurve().decodePoint(pEnc); + + publicKey = converter.getPublicKey( + new PGPPublicKey(new PublicKeyPacket( + pubKeyData.getVersion(), + PublicKeyAlgorithmTags.ECDH, + new Date(), + new ECDHPublicBCPGKey( + ecKey.getCurveOID(), + publicPoint, + ecKey.getHashAlgorithm(), + ecKey.getSymmetricKeyAlgorithm() + ) + ), fingerprintCalculator)); + decSessionKey = cryptoCallback.decryptECDH(ecKey, publicKey); + } + + int hashAlgorithm = ecKey.getHashAlgorithm(); + int symmetricKeyAlgorithm = ecKey.getSymmetricKeyAlgorithm(); + byte[] userKeyingMaterial = RFC6637Utils.createUserKeyingMaterial(pubKeyData, fingerprintCalculator); + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator( + new JcaPGPDigestCalculatorProviderBuilder().setProvider(new BouncyCastleProvider()).build().get(hashAlgorithm), + symmetricKeyAlgorithm); + KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(decSessionKey, userKeyingMaterial)); + + byte[] unwrapped = unwrapSessionData(keyEnc, symmetricKeyAlgorithm, key); + return PGPPad.unpadSessionData(unwrapped); + } + catch (Exception e) + { + throw new PGPException("error decrypting session data: " + e.getMessage(), e); + } + } + + /** + * Decrypt X25519 / X448 encrypted session keys. + * @param converter key converter + * @param privKey our private key + * @param enc encrypted session key + * @param pLen Key length + * @param agreementAlgorithm agreement algorithm + * @param symmetricKeyAlgorithm wrapping algorithm + * @param algorithmIdentifier ephemeral key algorithm identifier + * @param algorithmName public key algorithm name + * @param containsSKAlg whether the PKESK packet is version 3 + * @return decrypted session data + * @throws PGPException + */ + private byte[] decryptSessionData(JcaPGPKeyConverter converter, PGPPrivateKey privKey, byte[] enc, int pLen, String agreementAlgorithm, + int symmetricKeyAlgorithm, ASN1ObjectIdentifier algorithmIdentifier, String algorithmName, boolean containsSKAlg) + throws PGPException + { + try + { + // ephemeral key (32 / 56 octets) + byte[] ephemeralKey = Arrays.copyOf(enc, pLen); + + int size = enc[pLen] & 0xff; + + checkRange(pLen + 1 + size, enc); + + // encrypted session key + int sesKeyLen = size - (containsSKAlg ? 1 : 0); + int sesKeyOff = pLen + 1 + (containsSKAlg ? 1 : 0); + byte[] keyEnc = Arrays.copyOfRange(enc, sesKeyOff, sesKeyOff + sesKeyLen); + + PublicKey ephemeralPubKey = getPublicKey(ephemeralKey, algorithmIdentifier, 0); + Key paddedSessionKey = getSessionKey(converter, privKey, agreementAlgorithm, ephemeralPubKey, symmetricKeyAlgorithm, keyEnc, + JcaJcePGPUtil.getHybridValueParameterSpecWithPrepend(ephemeralKey, privKey.getPublicKeyPacket(), algorithmName)); + return paddedSessionKey.getEncoded(); + } + catch (Exception e) + { + throw new PGPException("error decrypting session data: " + e.getMessage(), e); + } + } + + private Key getSessionKey(JcaPGPKeyConverter converter, PGPPrivateKey privKey, String agreementName, + PublicKey publicKey, int symmetricKeyAlgorithm, byte[] keyEnc, AlgorithmParameterSpec ukms) + throws PGPException, GeneralSecurityException + { + Key key = JcaJcePGPUtil.getSecret(helper, publicKey, RFC6637Utils.getKeyEncryptionOID(symmetricKeyAlgorithm).getId(), agreementName, ukms, null); + Cipher c = helper.createKeyWrapper(symmetricKeyAlgorithm); + c.init(Cipher.UNWRAP_MODE, key); + return c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + } + + private PublicKey getPublicKey(byte[] pEnc, ASN1ObjectIdentifier algprithmIdentifier, int pEncOff) + throws PGPException, GeneralSecurityException, IOException + { + KeyFactory keyFact = helper.createKeyFactory("XDH"); + + return keyFact.generatePublic(new X509EncodedKeySpec(new SubjectPublicKeyInfo( + new AlgorithmIdentifier(algprithmIdentifier), Arrays.copyOfRange(pEnc, pEncOff, pEnc.length)).getEncoded())); + } + + /** + * Decrypt RSA / Elgamal encrypted session keys. + * @param keyAlgorithm public key algorithm + * @param secKeyData ESK data + * @return session data + * @throws PGPException + */ + private byte[] decryptSessionData(int keyAlgorithm, + byte[][] secKeyData, + PublicKeyCryptoCallback cryptoCallback) + throws PGPException + { + if (keyAlgorithm == PublicKeyAlgorithmTags.RSA_GENERAL || keyAlgorithm == PublicKeyAlgorithmTags.RSA_ENCRYPT) + { + byte[] sessionKey = Arrays.copyOfRange(secKeyData[0], 2, secKeyData[0].length); + return cryptoCallback.decryptRSA(keyAlgorithm, sessionKey); + } + else if (keyAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT || keyAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_GENERAL) + { + return cryptoCallback.decryptElGamal(keyAlgorithm, secKeyData); + } + else throw new PGPException("Unexpected public key algorithm: " + keyAlgorithm); + } + + private static void checkRange(int pLen, byte[] enc) + throws PGPException + { + if (pLen > enc.length) + { + throw new PGPException("encoded length out of range"); + } + } + + public static abstract class PublicKeyCryptoCallback + { + public abstract byte[] decryptRSA(int keyAlgorithm, + byte[] pEnc) + throws PGPException; + + public abstract byte[] decryptElGamal(int keyAlgorithm, + byte[][] secKeyData) + throws PGPException; + + public abstract byte[] decryptECDH(ECDHPublicBCPGKey pubKey, + PublicKey ephemeralKeyBytes) + throws PGPException; + + public abstract byte[] decryptX25519(PublicKey ephemeralKey) + throws PGPException; + + public abstract byte[] decryptX448(PublicKey ephemeralKey) + throws PGPException; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactory.java new file mode 100644 index 0000000000..5880f415f1 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactory.java @@ -0,0 +1,4 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +public class JcePublicKeyDataDecryptorFactory { +} diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/ExternalSecretKeyPacketTest.java b/pg/src/test/java/org/bouncycastle/bcpg/test/ExternalSecretKeyPacketTest.java index 2ab303791b..70fa2c789f 100644 --- a/pg/src/test/java/org/bouncycastle/bcpg/test/ExternalSecretKeyPacketTest.java +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/ExternalSecretKeyPacketTest.java @@ -1,7 +1,6 @@ package org.bouncycastle.bcpg.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.ContainedPacket; diff --git a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/OpenPGPSmartCardBackend.java b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/OpenPGPSmartCardBackend.java index 390af1916d..d6b88dd997 100644 --- a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/OpenPGPSmartCardBackend.java +++ b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/OpenPGPSmartCardBackend.java @@ -1,6 +1,5 @@ package org.bouncycastle.openpgp.smartcard; -import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; diff --git a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyOpenPGPSmartCard.java b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyOpenPGPSmartCard.java index 22089a0f98..2ff06fd68d 100644 --- a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyOpenPGPSmartCard.java +++ b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyOpenPGPSmartCard.java @@ -61,7 +61,7 @@ public YubikeyOpenPGPSmartCard(YubikeySmartCardBackend backend, } catch (ApduException | IOException | BadResponseException e) { - throw new CardException("Cannot perform initial read from Yubikey", e); + throw new CardException("Cannot perform initial read from Yubikey " + getSerialNumber(), e); } } diff --git a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeySmartCardBackend.java b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeySmartCardBackend.java index 0edbfe7518..98a0787857 100644 --- a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeySmartCardBackend.java +++ b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeySmartCardBackend.java @@ -11,7 +11,6 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.api.KeyPassphraseProvider; import org.bouncycastle.openpgp.api.OpenPGPKey; @@ -20,7 +19,8 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCardBackend; import org.bouncycastle.openpgp.smartcard.card.CardException; -import org.bouncycastle.openpgp.smartcard.yubikey.operator.YubikeyPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.smartcard.yubikey.operator.bc.BcYubikeyPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.smartcard.yubikey.operator.jcajce.JceYubikeyPublicKeyDataDecryptorFactoryBuilder; import org.bouncycastle.util.Arrays; import java.security.NoSuchAlgorithmException; @@ -40,6 +40,7 @@ public class YubikeySmartCardBackend { private static final int X25519_SCALAR_SIZE = 32; + private final YubikeyDecryptorFactoryProvider decryptorFactoryProvider; private final JcaPGPKeyConverter converter; private final JcaKeyFingerprintCalculator fingerprintCalculator; private final Set allowedCardSerials = new HashSet<>(); @@ -47,35 +48,49 @@ public class YubikeySmartCardBackend public static YubikeySmartCardBackend createInstance() { - return createInstance(new YubiKitManager()); + return createInstance(bcImpl()); } - public static YubikeySmartCardBackend createInstance(YubiKitManager yubiKitManager) + public static YubikeySmartCardBackend createInstance(YubikeyDecryptorFactoryProvider decryptorFactoryProvider) { - return createInstance(yubiKitManager, new BouncyCastleProvider()); + return createInstance(new YubiKitManager(), + decryptorFactoryProvider); } public static YubikeySmartCardBackend createInstance(YubiKitManager yubiKitManager, - BouncyCastleProvider provider) + YubikeyDecryptorFactoryProvider decryptorFactoryProvider) { - return new YubikeySmartCardBackend(yubiKitManager, + return createInstance(yubiKitManager, + new BouncyCastleProvider(), + decryptorFactoryProvider); + } + + public static YubikeySmartCardBackend createInstance(YubiKitManager yubiKitManager, + BouncyCastleProvider provider, + YubikeyDecryptorFactoryProvider decryptorFactoryProvider) + { + return new YubikeySmartCardBackend( + yubiKitManager, new JcaPGPKeyConverter().setProvider(provider), - new JcaKeyFingerprintCalculator().setProvider(provider)); + new JcaKeyFingerprintCalculator().setProvider(provider), + decryptorFactoryProvider); } public YubikeySmartCardBackend(YubiKitManager yubiKitManager, JcaPGPKeyConverter keyConverter, - JcaKeyFingerprintCalculator fingerprintCalculator) + JcaKeyFingerprintCalculator fingerprintCalculator, + YubikeyDecryptorFactoryProvider decryptorFactoryProvider) { this.manager = yubiKitManager; this.converter = keyConverter; this.fingerprintCalculator = fingerprintCalculator; + this.decryptorFactoryProvider = decryptorFactoryProvider; } @Override public String getName() { - return "Yubikit"; + return "Yubikit " + decryptorFactoryProvider.getName(); } /** @@ -130,7 +145,7 @@ public PublicKeyDataDecryptorFactory providePublicKeyDataDecryptorFactory( KeyPassphraseProvider userPinProvider) throws PGPException { - return new YubikeyPublicKeyDataDecryptorFactory(secretKey, card, userPinProvider); + return decryptorFactoryProvider.provide(secretKey, card, userPinProvider); } /** @@ -276,4 +291,58 @@ private PGPPublicKey bruteForcePublicKey(PublicKey pk, Date creationTime, } return null; } + + public interface YubikeyDecryptorFactoryProvider + { + PublicKeyDataDecryptorFactory provide(OpenPGPKey.OpenPGPSecretKey secretKey, + YubikeyOpenPGPSmartCard card, + KeyPassphraseProvider userPinProvider) + throws PGPException; + + String getName(); + } + + public static YubikeyDecryptorFactoryProvider bcImpl() + { + return new YubikeyDecryptorFactoryProvider() + { + @Override + public PublicKeyDataDecryptorFactory provide(OpenPGPKey.OpenPGPSecretKey secretKey, + YubikeyOpenPGPSmartCard card, + KeyPassphraseProvider userPinProvider) + throws PGPException + { + return new BcYubikeyPublicKeyDataDecryptorFactory(secretKey, card, userPinProvider); + } + + @Override + public String getName() + { + return "BCYK"; + } + }; + } + + public static YubikeyDecryptorFactoryProvider jceImpl() + { + return new YubikeyDecryptorFactoryProvider() + { + @Override + public PublicKeyDataDecryptorFactory provide(OpenPGPKey.OpenPGPSecretKey secretKey, + YubikeyOpenPGPSmartCard card, + KeyPassphraseProvider userPinProvider) + throws PGPException + { + return new JceYubikeyPublicKeyDataDecryptorFactoryBuilder(card, userPinProvider) + .setProvider(new BouncyCastleProvider()) + .build(secretKey); + } + + @Override + public String getName() + { + return "JCYK"; + } + }; + } } diff --git a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/YubikeyPublicKeyDataDecryptorFactory.java b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/bc/BcYubikeyPublicKeyDataDecryptorFactory.java similarity index 97% rename from pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/YubikeyPublicKeyDataDecryptorFactory.java rename to pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/bc/BcYubikeyPublicKeyDataDecryptorFactory.java index 45b3e251e0..26986cea23 100644 --- a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/YubikeyPublicKeyDataDecryptorFactory.java +++ b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/bc/BcYubikeyPublicKeyDataDecryptorFactory.java @@ -1,4 +1,4 @@ -package org.bouncycastle.openpgp.smartcard.yubikey.operator; +package org.bouncycastle.openpgp.smartcard.yubikey.operator.bc; import com.yubico.yubikit.core.application.InvalidPinException; import com.yubico.yubikit.core.keys.PublicKeyValues; @@ -39,13 +39,13 @@ * unwrap work stays in {@link org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory}. * ElGamal and X448 are not supported by the applet and are rejected. */ -public class YubikeyPublicKeyDataDecryptorFactory +public class BcYubikeyPublicKeyDataDecryptorFactory extends BcExternalPublicKeyDataDecryptorFactory { private final KeyPassphraseProvider userPinProvider; private final YubikeyOpenPGPSmartCard yubikey; - public YubikeyPublicKeyDataDecryptorFactory(OpenPGPKey.OpenPGPSecretKey secretKey, + public BcYubikeyPublicKeyDataDecryptorFactory(OpenPGPKey.OpenPGPSecretKey secretKey, YubikeyOpenPGPSmartCard yubikey, KeyPassphraseProvider userPinProvider) throws PGPException diff --git a/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/jcajce/JceYubikeyPublicKeyDataDecryptorFactoryBuilder.java b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/jcajce/JceYubikeyPublicKeyDataDecryptorFactoryBuilder.java new file mode 100644 index 0000000000..6ba13c19e2 --- /dev/null +++ b/pgsc/src/main/java/org/bouncycastle/openpgp/smartcard/yubikey/operator/jcajce/JceYubikeyPublicKeyDataDecryptorFactoryBuilder.java @@ -0,0 +1,180 @@ +package org.bouncycastle.openpgp.smartcard.yubikey.operator.jcajce; + +import com.yubico.yubikit.core.application.InvalidPinException; +import com.yubico.yubikit.core.keys.PublicKeyValues; +import com.yubico.yubikit.core.smartcard.ApduException; +import com.yubico.yubikit.openpgp.OpenPgpSession; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.api.KeyPassphraseProvider; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.exception.KeyPassphraseException; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JceExternalPublicKeyDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.smartcard.card.CardException; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyOpenPGPSmartCard; +import org.bouncycastle.util.Arrays; + +import java.io.IOException; +import java.security.PublicKey; + +public class JceYubikeyPublicKeyDataDecryptorFactoryBuilder + extends JceExternalPublicKeyDataDecryptorFactoryBuilder +{ + private final KeyPassphraseProvider userPinProvider; + private final YubikeyOpenPGPSmartCard yubikey; + + public JceYubikeyPublicKeyDataDecryptorFactoryBuilder(YubikeyOpenPGPSmartCard yubikey, + KeyPassphraseProvider userPinProvider) + { + this.userPinProvider = userPinProvider; + this.yubikey = yubikey; + } + + private static PGPKeyPair unlock(OpenPGPKey.OpenPGPSecretKey secretKey) + throws PGPException + { + OpenPGPKey.OpenPGPPrivateKey privKey = secretKey.unlock(); + if (privKey == null) + { + return new PGPKeyPair(secretKey.getPGPPublicKey(), null); + } + return privKey.getKeyPair(); + } + + @Override + public PublicKeyDataDecryptorFactory build(OpenPGPKey.OpenPGPSecretKey secretKey) + throws PGPException + { + return build(unlock(secretKey), new PublicKeyCryptoCallback() + { + @Override + public byte[] decryptRSA(int keyAlgorithm, byte[] pEnc) + throws PGPException + { + char[] pin = requireUserPin(userPinProvider, secretKey); + + try (OpenPgpSession openPgpSession = yubikey.openSession()) + { + openPgpSession.verifyUserPin(pin, true); + byte[] decryptedSessionKey = openPgpSession.decrypt(pEnc); + return decryptedSessionKey; + } + catch (ApduException | CardException | IOException e) + { + throw new PGPException("Cannot decrypt message", e); + } + catch (InvalidPinException e) + { + throw new KeyPassphraseException(secretKey, e); + } + finally + { + Arrays.fill(pin, (char) 0); + } + } + + @Override + public byte[] decryptElGamal(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + throw new PGPException("ElGamal not supported on YubiKey."); + } + + @Override + public byte[] decryptECDH(ECDHPublicBCPGKey pubKey, PublicKey ephemeralKeyBytes) + throws PGPException + { + char[] pin = requireUserPin(userPinProvider, secretKey); + + String curveName = ECUtil.getCurveName(pubKey.getCurveOID()); + switch (curveName) + { + case "secp256r1": + case "prime256v1": + case "secp256k1": + case "secp384r1": + case "secp521r1": + case "brainpoolP256r1": + case "brainpoolP384r1": + case "brainpoolP512r1": + case "curve25519": + break; + default: + throw new PGPException("Unsupported EC curve: " + ECUtil.getCurveName(pubKey.getCurveOID()) + " (" + pubKey.getCurveOID() + ")"); + } + + PublicKeyValues ephemeralPublicKey = PublicKeyValues.fromPublicKey(ephemeralKeyBytes); + + try (OpenPgpSession openPgpSession = yubikey.openSession()) + { + openPgpSession.verifyUserPin(pin, true); + // Perform ECDH handshake to generate shared secret + byte[] sharedSecret = openPgpSession.decrypt(ephemeralPublicKey); + return sharedSecret; + } + catch (ApduException | IOException | CardException e) + { + throw new PGPException("Cannot decrypt message", e); + } + catch (InvalidPinException e) + { + throw new KeyPassphraseException(secretKey, e); + } + finally + { + Arrays.fill(pin, (char) 0); + } + } + + @Override + public byte[] decryptX25519(PublicKey ephemeralKey) + throws PGPException + { + char[] pin = requireUserPin(userPinProvider, secretKey); + + PublicKeyValues peerKey = PublicKeyValues.fromPublicKey(ephemeralKey); + + try (OpenPgpSession openPgpSession = yubikey.openSession()) + { + openPgpSession.verifyUserPin(pin, true); + byte[] decryptedSessionKey = openPgpSession.decrypt(peerKey); + return decryptedSessionKey; + } + catch (ApduException | IOException | CardException e) + { + throw new PGPException("Cannot decrypt message", e); + } + catch (InvalidPinException e) + { + throw new KeyPassphraseException(secretKey, e); + } + finally + { + Arrays.fill(pin, (char) 0); + } + } + + @Override + public byte[] decryptX448(PublicKey ephemeralKey) + throws PGPException + { + throw new PGPException("X448 not supported by YubiKey."); + } + }); + } + + private char[] requireUserPin(KeyPassphraseProvider userPinProvider, + OpenPGPKey.OpenPGPSecretKey key) + throws KeyPassphraseException + { + char[] pin = userPinProvider.getKeyPassword(key); + if (pin == null || pin.length == 0) + { + throw new KeyPassphraseException(key, new IllegalStateException("PIN required.")); + } + return pin; + } +} diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/AnonymousRecipientSmartCardDecryptionTest.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/AnonymousRecipientSmartCardDecryptionTest.java index 99d01f8f62..f312c91658 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/AnonymousRecipientSmartCardDecryptionTest.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/AnonymousRecipientSmartCardDecryptionTest.java @@ -17,6 +17,9 @@ import org.bouncycastle.openpgp.smartcard.card.CardException; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorOpenPGPSmartCard; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorSmartCardBackend; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeySmartCardBackend; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestInstanceProvider; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestProperties; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.io.Streams; @@ -101,6 +104,8 @@ private void implTestAnonymousRecipient(String label, OpenPGPKey softwareKey) throws PGPException, IOException, CardException { OpenPGPSmartCard card = manager.findSmartCard(properties.getSerialNumber()); + // -DM System.out.println + System.out.println("Test anonymous " + label + " key on " + card.getCardType() + " " + card.getVersion() + " (" + card.getBackend().getName() + ")"); card.reset(); // move the decryption key onto the card, then strip the private key material from our copy @@ -179,10 +184,41 @@ private boolean hasOnlyWildcardPkesks(byte[] message) } public static void main(String[] args) + throws CardException { + SmartCardTestProperties p; + OpenPGPSmartCardManager m; + + // BCYK + try + { + p = new YubikeyTestProperties(); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.bcImpl()); + runTest(new AnonymousRecipientSmartCardDecryptionTest(m, p)); + } + catch (YubikeyTestInstanceProvider.YubikeySetupException e) + { + // -DM System.out.println + System.out.println("Skipping run of AnonymousRecipientSmartCardDecryptionTest on BC Yubikey."); + } + + // JCYK + try + { + p = new YubikeyTestProperties(); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.jceImpl()); + runTest(new AnonymousRecipientSmartCardDecryptionTest(m, p)); + } + catch (YubikeyTestInstanceProvider.YubikeySetupException e) + { + // -DM System.out.println + System.out.println("Skipping run of AnonymousRecipientSmartCardDecryptionTest on JCE Yubikey."); + } + + SimulatorSmartCardBackend sim = new SimulatorSmartCardBackend(); sim.addSmartCard(new SimulatorOpenPGPSmartCard(sim, 1312)); - OpenPGPSmartCardManager m = new OpenPGPSmartCardManager().addBackend(sim); + m = new OpenPGPSmartCardManager().addBackend(sim); runTest(new AnonymousRecipientSmartCardDecryptionTest(m, new SmartCardTestProperties(1312))); } diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardMessageDecryptionTest.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardMessageDecryptionTest.java index ce0fa7b7ff..feabb793ad 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardMessageDecryptionTest.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardMessageDecryptionTest.java @@ -5,12 +5,14 @@ import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCard; import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCardManager; import org.bouncycastle.openpgp.smartcard.card.CardException; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorOpenPGPSmartCard; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorSmartCardBackend; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeySmartCardBackend; import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestInstanceProvider; import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestProperties; import org.bouncycastle.util.Arrays; @@ -22,7 +24,7 @@ import java.nio.charset.StandardCharsets; public class SmartCardMessageDecryptionTest - extends AbstractOpenPGPSmartCardTest + extends AbstractOpenPGPSmartCardTest { public SmartCardMessageDecryptionTest(OpenPGPSmartCardManager manager, SmartCardTestProperties properties) @@ -38,11 +40,13 @@ public String getName() @Override public void performTest() - throws Exception + throws Exception { testX25519Key(); testLegacyX25519Key(); + testFixedRSA2048Key(); + testRSA2048Key(); testRSA3072Key(); testRSA4096Key(); @@ -50,43 +54,174 @@ public void performTest() testNISTP256ECDHKey(); testNISTP384ECDHKey(); testNISTP521ECDHKey(); + + testBrainpoolP256r1ECDHKey(); + testBrainpoolP384r1ECDHKey(); + testBrainpoolP512r1ECDHKey(); + } + + private void testFixedRSA2048Key() + throws IOException, PGPException, CardException + { + String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: 03D6 5E27 0344 A9DD EABC E33D B698 B8A3 78E7 BFE9\n" + + "Comment: Alice \n" + + "\n" + + "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\n" + + "jJnZpryKw+5pvbfrQpj7JOJXGpPrUQ==\n" + + "=kcdi\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + String MSG = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcBaBhUEcgalz/7ROpoInJb86FgstKCBcyIBCACdqsPIREKZ9UjkwYdG/FKLRchy\n" + + "6yhnChWLc8g1qlaGG29vomiu2+sc5dqZzZ2GOwENBvVUTiMCW2X+/j4vWPJ435RU\n" + + "NFrc89mG7U+XZ8tAXiRDbvKKadUlj5a77vL4mIDMETT7NKHAfYstC1Aqz2sicOaP\n" + + "PxUG6nONG5CAztAUgEtCoMjv7dHnw+8NB7uGbjreznbFFSnwuDL0vus6jtj37YPR\n" + + "8ojGTVP5V09b2brV3jO1jmhmJfK205StLSsTf+76F2X8aWfKv2QVerRDvkXULikm\n" + + "355rXCgQ8nTaP9RKeE53KHbWpjDDdavs8ClYpJtybHnvYsgBTq76Wpc2myDi0sCu\n" + + "AgkCAHpvOyXGD1AJridQ/6dQK86R1rfqDybcdHC2mJhb5x1OuABqGhCq7V3U3avs\n" + + "fLbKJ4IC03rjx0q731so9ock4T6uvqStjMFEA4pZFpgtvQzSkSrq4lovSX3LeDpG\n" + + "9meXTTA5D7NgfkxuEte85Ofx3n0Unacetws1D5UGZeO2n/ty5T1AiROACyHc4d3U\n" + + "5Ayx+3BhAY296EJD3Sh8Ti2yMo7W5vsu/koT+2qryfuVoZhej5++7UpXR6kgQ5O6\n" + + "HJHTVcQh7JDYTnOrkho4Cq0Yvmyaqs2A9ZG2Q/B6viyLw6/Jm5HVo/uFMVyAfzf8\n" + + "BHySf7375akGbXaTRnCQDFsaDX76CvLSXTD5RJn5godgOZZqG+jfUER4vfiQrNVL\n" + + "mIfyJl7brAvaatd6i8U0kYXXB8E+Moul7ldqAjtjQvpIe1gpAZnnOG95hV0jIqPe\n" + + "5SzBdRKVVn2p9FuyFdRnIR5Ha3eDPEDnju9zJ3fy\n" + + "-----END PGP MESSAGE-----"; + // Expect pEnc: 272122ec0b4fd60ecca9533b10bc26a9f9e9f2a2a416b53c383e414127bfcce27173aeb22966c5dd6779301eef78185bffee42a9773026e964143755065ca3ed74dbe576bd1577c13b1c3ca8e2a0e0d6498459aa38f94a61ba3a4dfb396314e1314043c5dfb0e7023a1262d838573e8e6d35b0acbb9115d11c6c54b4d216857de9e6e82003a12850b1ad82343de05d99340c87ed1e77c3f4fcd14a4626a9c3ecce97e3480369e915f047a3b39cfc6010101baaffbc6289d48adca16f9a59d5c5d1d4f709d1e17d14682b7acef8cc3f4c56d4f2c75e7516a28e300543b5608bdbc9614fc15e34539307fe26505d2970585ab59967973e1baff95f17624d2989a15dc904ede6ea5223430d72cec747865ddfa5919caaccb0e11775f780279ad82162e3f43c7c5c9595b3cd788e35e8473d2adc50f843f3ce58896ef2c662336e4e7a0a7baca45eb0848c2b466c988fb2de1db926aad749049a4b26cfc400e3fe17c79a1e3c9c4121e1505f53710fa608880ba23dd621a654b5213323b7ecbc79b7 + // Expect decSessionKey: fd8eea5fb08fb7d070da66a41a12237845a2ab84014956c7fdcc3df02e0db6591072 + + BcOpenPGPApi api = new BcOpenPGPApi(); + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(KEY); + testDecryptWithExternalKey(key, MSG); } private void testRSA2048Key() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with 2048-bit RSA key"); OpenPGPKey rsaKey = api.generateKey(4) .compositeRSAKey(2048, "Alice ") .build(); - testDecryptionWithExternalKey(rsaKey); + testEncryptionAndDecryptionWithExternalKey(rsaKey); } private void testRSA3072Key() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with 3072-bit RSA key"); OpenPGPKey rsaKey = api.generateKey(4) .compositeRSAKey(3072, "Alice ") .build(); - testDecryptionWithExternalKey(rsaKey); + testEncryptionAndDecryptionWithExternalKey(rsaKey); } private void testRSA4096Key() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with 4096-bit RSA key"); OpenPGPKey rsaKey = api.generateKey(4) .compositeRSAKey(4096, "Alice ") .build(); - testDecryptionWithExternalKey(rsaKey); + testEncryptionAndDecryptionWithExternalKey(rsaKey); } private void testNISTP256ECDHKey() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with NIST-P256 ECDH key"); @@ -95,11 +230,11 @@ private void testNISTP256ECDHKey() .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP256ECDSAKeyPair) .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP256ECDHKeyPair) .build(); - testDecryptionWithExternalKey(ecdhKey); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); } private void testNISTP384ECDHKey() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with NIST-P384 ECDH key"); @@ -108,11 +243,11 @@ private void testNISTP384ECDHKey() .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP384ECDSAKeyPair) .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP384ECDHKeyPair) .build(); - testDecryptionWithExternalKey(ecdhKey); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); } private void testNISTP521ECDHKey() - throws CardException, PGPException, IOException + throws CardException, PGPException, IOException { // -DM System.out.println System.out.println("Test decryption with NIST-P521 ECDH key"); @@ -121,11 +256,50 @@ private void testNISTP521ECDHKey() .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP521ECDSAKeyPair) .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateNistP521ECDHKeyPair) .build(); - testDecryptionWithExternalKey(ecdhKey); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); + } + + private void testBrainpoolP256r1ECDHKey() + throws CardException, PGPException, IOException + { + // -DM System.out.println + System.out.println("Test decryption with Brainpool-P256r1 ECDH key"); + OpenPGPKey ecdhKey = api.generateKey(4) + .withPrimaryKey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP256r1ECDSAKeyPair) + .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP256r1ECDSAKeyPair) + .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP256r1ECDHKeyPair) + .build(); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); + } + + private void testBrainpoolP384r1ECDHKey() + throws CardException, PGPException, IOException + { + // -DM System.out.println + System.out.println("Test decryption with Brainpool-P384r1 ECDH key"); + OpenPGPKey ecdhKey = api.generateKey(4) + .withPrimaryKey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP384r1ECDSAKeyPair) + .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP384r1ECDSAKeyPair) + .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP384r1ECDHKeyPair) + .build(); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); + } + + private void testBrainpoolP512r1ECDHKey() + throws CardException, PGPException, IOException + { + // -DM System.out.println + System.out.println("Test decryption with Brainpool-P512r1 ECDH key"); + OpenPGPKey ecdhKey = api.generateKey(4) + .withPrimaryKey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP512r1ECDSAKeyPair) + .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP512r1ECDSAKeyPair) + .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateBrainpoolP512r1ECDHKeyPair) + .build(); + testEncryptionAndDecryptionWithExternalKey(ecdhKey); } private void testLegacyX25519Key() - throws PGPException, IOException, CardException + throws PGPException, IOException, CardException { // -DM System.out.println System.out.println("Test decryption with legacy X25519 key"); @@ -134,11 +308,11 @@ private void testLegacyX25519Key() .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateLegacyEd25519KeyPair) .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateLegacyX25519KeyPair) .build(); - testDecryptionWithExternalKey(x25519Key); + testEncryptionAndDecryptionWithExternalKey(x25519Key); } private void testX25519Key() - throws PGPException, IOException, CardException + throws PGPException, IOException, CardException { // -DM System.out.println System.out.println("Test decryption with X25519 key"); @@ -147,15 +321,15 @@ private void testX25519Key() .addSigningSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateEd25519KeyPair) .addEncryptionSubkey((KeyPairGeneratorCallback) PGPKeyPairGenerator::generateX25519KeyPair) .build(); - testDecryptionWithExternalKey(x25519Key); + testEncryptionAndDecryptionWithExternalKey(x25519Key); } - private void testDecryptionWithExternalKey(OpenPGPKey softwareKey) - throws PGPException, IOException, CardException + private void testEncryptionAndDecryptionWithExternalKey(OpenPGPKey softwareKey) + throws PGPException, IOException, CardException { OpenPGPSmartCard card = manager.findSmartCard(properties.getSerialNumber()); // -DM System.out.println - System.out.println("Test on " + card.getCardType() + " " + card.getVersion()); + System.out.println("Test on " + card.getCardType() + " " + card.getVersion() + " (" + card.getBackend().getName() + ")"); card.reset(); // -DM System.out.println System.out.println(softwareKey.toAsciiArmoredString()); @@ -194,21 +368,65 @@ private void testDecryptionWithExternalKey(OpenPGPKey softwareKey) Arrays.areEqual(plaintext, bOut.toByteArray())); } + private void testDecryptWithExternalKey(OpenPGPKey softwareKey, String message) + throws CardException, IOException, PGPException + { + OpenPGPSmartCard card = manager.findSmartCard(properties.getSerialNumber()); + // -DM System.out.println + System.out.println("Decrypt on " + card.getCardType() + " " + card.getVersion() + " (" + card.getBackend().getName() + ")"); + card.reset(); + // -DM System.out.println + System.out.println(softwareKey.toAsciiArmoredString()); + + char[] adminPin = properties.getAdminPin(); + + OpenPGPKey externalKey = toExternalKey(softwareKey, null); + + // Upload keys to card + OpenPGPKey.OpenPGPSecretKey decryptionKey = softwareKey.getSecretKey(softwareKey.getEncryptionKeys().get(0)); + card.uploadDecryptionKey(decryptionKey.unlock(), adminPin); + + // Decrypt message using card + ByteArrayInputStream bIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); + OpenPGPMessageInputStream mIn = api.decryptAndOrVerifyMessage() + .addDecryptionKey(externalKey, properties.getUserPin()) + .addPublicKeyDataDecryptorFactoryProvider(manager) + .process(bIn); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + Streams.pipeAll(mIn, bOut); + mIn.close(); + } + public static void main(String[] args) - throws CardException + throws CardException { SmartCardTestProperties p; OpenPGPSmartCardManager m; + + // BCYK + try + { + p = new YubikeyTestProperties(); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.bcImpl()); + runTest(new SmartCardMessageDecryptionTest(m, p)); + } + catch (YubikeyTestInstanceProvider.YubikeySetupException e) + { + // -DM System.out.println + System.out.println("Skipping run of SmartCardMessageDecryptionTest on BC Yubikey."); + } + + // JCYK try { p = new YubikeyTestProperties(); - m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.jceImpl()); runTest(new SmartCardMessageDecryptionTest(m, p)); } catch (YubikeyTestInstanceProvider.YubikeySetupException e) { // -DM System.out.println - System.out.println("Skipping run of SmartCardMessageDecryptionTest on Yubikey."); + System.out.println("Skipping run of SmartCardMessageDecryptionTest on JCE Yubikey."); } SimulatorSmartCardBackend sim = new SimulatorSmartCardBackend(); diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardTestProperties.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardTestProperties.java index a9867ffe31..cceefe20fa 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardTestProperties.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/SmartCardTestProperties.java @@ -1,5 +1,7 @@ package org.bouncycastle.openpgp.smartcard.test; +import org.bouncycastle.util.Arrays; + public class SmartCardTestProperties { public static final char[] DEFAULT_ADMIN_PIN = "12345678".toCharArray(); @@ -30,11 +32,11 @@ public Integer getSerialNumber() public char[] getAdminPin() { - return adminPin; + return Arrays.clone(adminPin); } public char[] getUserPin() { - return userPin; + return Arrays.clone(userPin); } } diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/UnrelatedSmartCardMessageDecryptionTest.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/UnrelatedSmartCardMessageDecryptionTest.java index 0d6359ef10..c81229c4eb 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/UnrelatedSmartCardMessageDecryptionTest.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/test/UnrelatedSmartCardMessageDecryptionTest.java @@ -9,6 +9,7 @@ import org.bouncycastle.openpgp.smartcard.card.CardException; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorOpenPGPSmartCard; import org.bouncycastle.openpgp.smartcard.simulator.SimulatorSmartCardBackend; +import org.bouncycastle.openpgp.smartcard.yubikey.YubikeySmartCardBackend; import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestInstanceProvider; import org.bouncycastle.openpgp.smartcard.yubikey.YubikeyTestProperties; import org.bouncycastle.util.Arrays; @@ -38,7 +39,7 @@ public void performTest() { OpenPGPSmartCard card = manager.listSmartCards().get(0); // -DM System.out.println - System.out.println("Run UnrelatedSmartCardMessageDecryptionTest on " + card.getCardType() + " " + card.getVersion()); + System.out.println("Run UnrelatedSmartCardMessageDecryptionTest on " + card.getCardType() + " " + card.getVersion() + " (" + card.getBackend().getName() + ")"); OpenPGPKey cardKey = api.generateKey(4) .compositeRSAKey(3072, "Eric Cartman ") @@ -79,16 +80,35 @@ public static void main(String[] args) { SmartCardTestProperties p; OpenPGPSmartCardManager m; + + // BCYK + try + { + p = new YubikeyTestProperties(); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.bcImpl()); + runTest(new UnrelatedSmartCardMessageDecryptionTest(m, p)); + } + catch (YubikeyTestInstanceProvider.YubikeySetupException e) + { + // -DM System.out.println + System.out.println("Skipping run of SmartCardMessageDecryptionTest on BC Yubikey."); + } + catch (CardException e) + { + throw new RuntimeException(e); + } + + // JCYK try { p = new YubikeyTestProperties(); - m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.jceImpl()); runTest(new UnrelatedSmartCardMessageDecryptionTest(m, p)); } catch (YubikeyTestInstanceProvider.YubikeySetupException e) { // -DM System.out.println - System.out.println("Skipping run of SmartCardMessageDecryptionTest on Yubikey."); + System.out.println("Skipping run of SmartCardMessageDecryptionTest on JCE Yubikey."); } catch (CardException e) { diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/CloseYubikeySessionTest.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/CloseYubikeySessionTest.java index 1f6b3e04ee..c7ccac8893 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/CloseYubikeySessionTest.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/CloseYubikeySessionTest.java @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.smartcard.yubikey; import com.yubico.yubikit.core.smartcard.ApduException; +import com.yubico.yubikit.openpgp.OpenPgpSession; import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCard; import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCardManager; import org.bouncycastle.openpgp.smartcard.card.CardException; @@ -29,9 +30,10 @@ public void performTest() } YubikeyOpenPGPSmartCard yubikey = (YubikeyOpenPGPSmartCard) card; + OpenPgpSession session; try { - yubikey.openSession(); + session = yubikey.openSession(); } catch (ApduException e) { @@ -50,7 +52,10 @@ public void performTest() { throw new RuntimeException(e); } - + finally + { + session.close(); + } } @Override diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTestInstanceProvider.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTestInstanceProvider.java index ffd6638fa6..dfe4f58121 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTestInstanceProvider.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTestInstanceProvider.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.smartcard.yubikey; + import org.bouncycastle.openpgp.smartcard.OpenPGPSmartCardManager; import org.bouncycastle.openpgp.smartcard.card.CardException; import org.bouncycastle.openpgp.smartcard.test.SmartCardTestProperties; @@ -7,7 +8,16 @@ public class YubikeyTestInstanceProvider { - public static OpenPGPSmartCardManager prepareOneYubikeySmartCardManager(SmartCardTestProperties testProperties) + public static OpenPGPSmartCardManager prepareOneYubikeySmartCardManager( + SmartCardTestProperties testProperties) + throws YubikeySetupException, CardException + { + return prepareOneYubikeySmartCardManager(testProperties, YubikeySmartCardBackend.bcImpl()); + } + + public static OpenPGPSmartCardManager prepareOneYubikeySmartCardManager( + SmartCardTestProperties testProperties, + YubikeySmartCardBackend.YubikeyDecryptorFactoryProvider decryptorFactoryProvider) throws YubikeySetupException, CardException { if (testProperties.getSerialNumber() == null) @@ -15,7 +25,7 @@ public static OpenPGPSmartCardManager prepareOneYubikeySmartCardManager(SmartCar throw new YubikeySetupException("Missing yubikey.properties file."); } - YubikeySmartCardBackend backend = YubikeySmartCardBackend.createInstance() + YubikeySmartCardBackend backend = YubikeySmartCardBackend.createInstance(decryptorFactoryProvider) .addAllowedCardSerial(testProperties.getSerialNumber()); if (backend.listSmartCards().isEmpty()) { diff --git a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTests.java b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTests.java index 85d3dfcb73..37a1c61e1e 100644 --- a/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTests.java +++ b/pgsc/src/test/java/org/bouncycastle/openpgp/smartcard/yubikey/YubikeyTests.java @@ -14,20 +14,58 @@ public class YubikeyTests extends TestCase { - public void testYubikeySmartCard() + public void testBCYK() throws CardException { SmartCardTestProperties p; OpenPGPSmartCardManager m; + + try + { + p = new YubikeyTestProperties(); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.bcImpl()); + } + catch (YubikeyTestInstanceProvider.YubikeySetupException e) + { + // -DM System.err.println + System.err.println("Skipping run of OpenPGP Smart Card tests on BC Yubikey."); + return; + } + + AbstractOpenPGPSmartCardTest[] tests = new AbstractOpenPGPSmartCardTest[] + { + new SmartCardMessageDecryptionTest(m, p), + new AnonymousRecipientSmartCardDecryptionTest(m, p), + new UnrelatedSmartCardMessageDecryptionTest(m, p), + new CloseYubikeySessionTest(m, p), + }; + + for (int i = 0; i != tests.length; i++) + { + SimpleTestResult result = (SimpleTestResult)tests[i].perform(); + + if (!result.isSuccessful()) + { + fail(result.toString()); + } + } + } + + public void testJCEYK() + throws CardException + { + SmartCardTestProperties p; + OpenPGPSmartCardManager m; + try { p = new YubikeyTestProperties(); - m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p); + m = YubikeyTestInstanceProvider.prepareOneYubikeySmartCardManager(p, YubikeySmartCardBackend.jceImpl()); } catch (YubikeyTestInstanceProvider.YubikeySetupException e) { // -DM System.err.println - System.err.println("Skipping run of OpenPGP Smart Card tests on Yubikey."); + System.err.println("Skipping run of OpenPGP Smart Card tests on JCE Yubikey."); return; }