diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java index 324a5042ea..e18b5e60ff 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java @@ -7,6 +7,7 @@ import org.bouncycastle.bcpg.InputStreamPacket; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; +import org.bouncycastle.bcpg.SymmetricEncDataPacket; import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.UnsupportedPacketVersionException; @@ -110,6 +111,18 @@ public PGPSessionKey getSessionKey( PublicKeyDataDecryptorFactory dataDecryptorFactory) throws PGPException { + if (encData instanceof AEADEncDataPacket) + { + return dataDecryptorFactory.recoverSessionKey(keyData, (AEADEncDataPacket) encData); + } + + if (encData instanceof SymmetricEncDataPacket) + { + return dataDecryptorFactory.recoverSessionKey(keyData, (SymmetricEncDataPacket) encData); + } + + return dataDecryptorFactory.recoverSessionKey(keyData, (SymmetricEncIntegrityPacket) encData); + byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey(), getVersion()); if (keyData.getAlgorithm() == PublicKeyAlgorithmTags.X25519 || keyData.getAlgorithm() == PublicKeyAlgorithmTags.X448) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java index aef64a10b7..cc78cf17cb 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java @@ -1,10 +1,37 @@ package org.bouncycastle.openpgp.operator; +import org.bouncycastle.bcpg.InputStreamPacket; +import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; import org.bouncycastle.openpgp.PGPException; public interface PublicKeyDataDecryptorFactory extends PGPDataDecryptorFactory { + /** + * Recover the session key. + * The return value is a two-dimensional byte array containing the following information about the session key: + * + * @param pkesk public key encrypted session-key packet + * @param encryptedData SEIPD, SED or OED packet + * @return 2-dimensional byte array. + * @throws PGPException + */ + byte[][] recoverSessionKey(PublicKeyEncSessionPacket pkesk, InputStreamPacket encryptedData) + throws PGPException; + + /** + * @deprecated use {@link #recoverSessionKey(PublicKeyEncSessionPacket, InputStreamPacket)} instead. + * @param keyAlgorithm public key algorithm + * @param secKeyData encrypted session key data + * @param pkeskVersion version of the PKESK packet + * @return + * @throws PGPException + */ byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData, int pkeskVersion) throws PGPException; + } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java index 2f8dc4f228..d779787c17 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java @@ -29,7 +29,6 @@ import org.bouncycastle.openpgp.operator.PGPPad; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.RFC6637Utils; -import org.bouncycastle.pqc.crypto.gemss.GeMSSParameters; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; @@ -48,6 +47,98 @@ public BcPublicKeyDataDecryptorFactory(PGPPrivateKey pgpPrivKey) this.pgpPrivKey = pgpPrivKey; } + @Override + public byte[][] recoverSessionKey(PublicKeyEncSessionPacket pkesk, InputStreamPacket encData) + throws PGPException + { + if (pkesk.getAlgorithm() != pgpPrivKey.getPublicKeyPacket().getAlgorithm()) + { + throw new PGPException("Public-Key algorithm field of the Public-Key Encrypted Session Key Packet" + + " does not match the private keys algorithm."); + } + + byte[][] secKeyData = pkesk.getEncSessionKey(); + + try + { + AsymmetricKeyParameter privKey = KEY_CONVERTER.getPrivateKey(pgpPrivKey); + int keyAlgorithm = pkesk.getAlgorithm(); + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.X25519: + + case PublicKeyAlgorithmTags.X448: + return recoverXSessionData(secKeyData[0], privKey, X448PublicBCPGKey.LENGTH, + HashAlgorithmTags.SHA512, SymmetricKeyAlgorithmTags.AES_256, new X448Agreement(), + "X448", new PublicKeyParametersOperation() + { + @Override + public AsymmetricKeyParameter getPublicKeyParameters(byte[] pEnc, int pEncOff) + { + return new X448PublicKeyParameters(pEnc, 0); + } + }, pkesk.getVersion()); + + case PublicKeyAlgorithmTags.ECDH: + byte[] enc = secKeyData[0]; + byte[] pEnc; + byte[] keyEnc; + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + assertOutOfRange(2 + pLen + 1, enc); + + pEnc = new byte[pLen]; + System.arraycopy(enc, 2, pEnc, 0, pLen); + + int keyLen = enc[pLen + 2] & 0xff; + assertOutOfRange(2 + pLen + 1 + keyLen, enc); + + keyEnc = new byte[keyLen]; + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyLen); + + byte[] secret; + RFC6637KDFCalculator rfc6637KDFCalculator; + byte[] userKeyingMaterial; + int symmetricKeyAlgorithm, hashAlgorithm; + + ECDHPublicBCPGKey ecPubKey = (ECDHPublicBCPGKey)pgpPrivKey.getPublicKeyPacket().getKey(); + // XDH + if (ecPubKey.getCurveOID().equals(CryptlibObjectIdentifiers.curvey25519)) + { + if (pEnc.length != 1 + X25519PublicKeyParameters.KEY_SIZE || 0x40 != pEnc[0]) + { + throw new IllegalArgumentException("Invalid Curve25519 public key"); + } + // skip the 0x40 header byte. + secret = BcUtil.getSecret(new X25519Agreement(), privKey, new X25519PublicKeyParameters(pEnc, 1)); + } + else + { + ECDomainParameters ecParameters = ((ECPrivateKeyParameters)privKey).getParameters(); + + ECPublicKeyParameters ephPub = new ECPublicKeyParameters(ecParameters.getCurve().decodePoint(pEnc), + ecParameters); + + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(privKey); + BigInteger S = agreement.calculateAgreement(ephPub); + secret = BigIntegers.asUnsignedByteArray(agreement.getFieldSize(), S); + } + hashAlgorithm = ecPubKey.getHashAlgorithm(); + symmetricKeyAlgorithm = ecPubKey.getSymmetricKeyAlgorithm(); + userKeyingMaterial = RFC6637Utils.createUserKeyingMaterial(pgpPrivKey.getPublicKeyPacket(), new BcKeyFingerprintCalculator()); + rfc6637KDFCalculator = new RFC6637KDFCalculator(new BcPGPDigestCalculatorProvider().get(hashAlgorithm), symmetricKeyAlgorithm); + KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(secret, userKeyingMaterial)); + + return PGPPad.unpadSessionData(unwrapSessionData(keyEnc, symmetricKeyAlgorithm, key)); + } + } catch (InvalidCipherTextException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData, int pkeskVersion) throws PGPException @@ -276,7 +367,7 @@ private byte[] recoverXSessionData2(byte[] ephemeralPK, * @throws PGPException * @throws InvalidCipherTextException */ - private byte[] recoverXSessionData(byte[] enc, AsymmetricKeyParameter privKey, int pLen, int hashAlgorithm, int symmetricKeyAlgorithm, + private byte[][] recoverXSessionData(byte[] enc, AsymmetricKeyParameter privKey, int pLen, int hashAlgorithm, int symmetricKeyAlgorithm, RawAgreement agreement, String algorithmName, PublicKeyParametersOperation pkp, int pkeskVersion) throws PGPException, InvalidCipherTextException { @@ -311,11 +402,19 @@ private byte[] recoverXSessionData(byte[] enc, AsymmetricKeyParameter privKey, i if (pkeskVersion == 6) { - return unwrapped; + return new byte[][] { + unwrapped, // session key + new byte[0], // v6 pkesk does not contain sym-alg (sourced from seipd2 instead) + new byte[0] // X25519, X448 does not include checksum + }; } else { - return Arrays.prepend(unwrapped, enc[pLen + 1]); + return new byte[][] { + unwrapped, // session key + new byte[] { enc[pLen + 1] }, // sym alg + new byte[0] // X25519, X448 does not include checksum + }; } }