diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java index f78e03c4..cd739017 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java @@ -34,7 +34,7 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.util.Passphrase; @@ -49,7 +49,8 @@ public class DecryptionBuilder implements DecryptionBuilderInterface { private Set verificationKeys = new HashSet<>(); private MissingPublicKeyCallback missingPublicKeyCallback = null; - private final KeyFingerPrintCalculator keyFingerPrintCalculator = new BcKeyFingerprintCalculator(); + private final KeyFingerPrintCalculator keyFingerPrintCalculator = + ImplementationFactory.getInstance().getKeyFingerprintCalculator(); @Override public DecryptWith onInputStream(@Nonnull InputStream inputStream) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index b991d316..92b0527c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -15,8 +15,6 @@ */ package org.pgpainless.decryption_verification; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.util.Collections; @@ -28,6 +26,8 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedData; @@ -50,13 +50,9 @@ import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.util.Passphrase; @@ -73,8 +69,8 @@ public final class DecryptionStreamFactory { private final MissingPublicKeyCallback missingPublicKeyCallback; private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); - private final PGPContentVerifierBuilderProvider verifierBuilderProvider = new BcPGPContentVerifierBuilderProvider(); - private final KeyFingerPrintCalculator keyFingerprintCalculator = new BcKeyFingerprintCalculator(); + private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(); + private static final KeyFingerPrintCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getKeyFingerprintCalculator(); private final Map verifiableOnePassSignatures = new HashMap<>(); private DecryptionStreamFactory(@Nullable PGPSecretKeyRingCollection decryptionKeys, @@ -108,13 +104,13 @@ public final class DecryptionStreamFactory { if (signingKey == null) { continue; } - signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey); + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); factory.resultBuilder.addDetachedSignature( new DetachedSignature(signature, new OpenPgpV4Fingerprint(signingKey))); } } else { PGPObjectFactory objectFactory = new PGPObjectFactory( - PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator()); + PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator); pgpInputStream = factory.processPGPPackets(objectFactory); } return new DecryptionStream(pgpInputStream, factory.resultBuilder); @@ -195,8 +191,8 @@ public final class DecryptionStreamFactory { PGPPBEEncryptedData pbeEncryptedData = (PGPPBEEncryptedData) encryptedData; if (decryptionPassphrase != null) { - PBEDataDecryptorFactory passphraseDecryptor = new BcPBEDataDecryptorFactory( - decryptionPassphrase.getChars(), new BcPGPDigestCalculatorProvider()); + PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance() + .getPBEDataDecryptorFactory(decryptionPassphrase); SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId( pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor)); resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); @@ -233,7 +229,8 @@ public final class DecryptionStreamFactory { throw new PGPException("Decryption failed - No suitable decryption key or passphrase found"); } - PublicKeyDataDecryptorFactory keyDecryptor = new BcPublicKeyDataDecryptorFactory(decryptionKey); + PublicKeyDataDecryptorFactory keyDecryptor = ImplementationFactory.getInstance() + .getPublicKeyDataDecryptorFactory(decryptionKey); SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm .fromId(encryptedSessionKey.getSymmetricAlgorithm(keyDecryptor)); @@ -275,9 +272,10 @@ public final class DecryptionStreamFactory { } signature.init(verifierBuilderProvider, verificationKey); - OnePassSignature onePassSignature = new OnePassSignature(signature, new OpenPgpV4Fingerprint(verificationKey)); + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey); + OnePassSignature onePassSignature = new OnePassSignature(signature, fingerprint); resultBuilder.addOnePassSignature(onePassSignature); - verifiableOnePassSignatures.put(new OpenPgpV4Fingerprint(verificationKey), onePassSignature); + verifiableOnePassSignatures.put(fingerprint, onePassSignature); } private PGPPublicKey findSignatureVerificationKey(long keyId) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java index 39a10e0f..e3c90040 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java @@ -37,16 +37,19 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.decryption_verification.DetachedSignature; import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.util.Passphrase; @@ -136,20 +139,30 @@ public final class EncryptionStream extends OutputStream { } LOGGER.log(LEVEL, "At least one encryption key is available -> encrypt using " + symmetricKeyAlgorithm); - BcPGPDataEncryptorBuilder dataEncryptorBuilder = - new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId()); + PGPDataEncryptorBuilder dataEncryptorBuilder = + ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(symmetricKeyAlgorithm); + + // Simplify once https://github.com/bcgit/bc-java/pull/859 is merged + if (dataEncryptorBuilder instanceof BcPGPDataEncryptorBuilder) { + ((BcPGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true); + } else if (dataEncryptorBuilder instanceof JcePGPDataEncryptorBuilder) { + ((JcePGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true); + } - dataEncryptorBuilder.setWithIntegrityPacket(true); PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptorBuilder); for (PGPPublicKey key : encryptionKeys) { LOGGER.log(LEVEL, "Encrypt for key " + Long.toHexString(key.getKeyID())); - encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(key)); + PublicKeyKeyEncryptionMethodGenerator keyEncryption = + ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key); + encryptedDataGenerator.addMethod(keyEncryption); } for (Passphrase passphrase : encryptionPassphrases) { - encryptedDataGenerator.addMethod(new BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())); + PBEKeyEncryptionMethodGenerator passphraseEncryption = + ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase); + encryptedDataGenerator.addMethod(passphraseEncryption); } publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]); @@ -165,8 +178,10 @@ public final class EncryptionStream extends OutputStream { for (OpenPgpV4Fingerprint fingerprint : signingKeys.keySet()) { PGPPrivateKey privateKey = signingKeys.get(fingerprint); LOGGER.log(LEVEL, "Sign using key " + fingerprint); - BcPGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder( - privateKey.getPublicKeyPacket().getAlgorithm(), hashAlgorithm.getAlgorithmId()); + PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance() + .getPGPContentSignerBuilder( + privateKey.getPublicKeyPacket().getAlgorithm(), + hashAlgorithm.getAlgorithmId()); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(signatureType.getCode(), privateKey); diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/BcCryptoEngineImplementation.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/BcCryptoEngineImplementation.java new file mode 100644 index 00000000..66829d07 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/implementation/BcCryptoEngineImplementation.java @@ -0,0 +1,145 @@ +package org.pgpainless.implementation; + +import java.security.KeyPair; +import java.util.Date; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.util.Passphrase; + +public class BcCryptoEngineImplementation implements CryptoEngineImplementation { + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(PGPSecretKey secretKey, Passphrase passphrase) + throws PGPException { + return new BcPBESecretKeyEncryptorBuilder(secretKey.getKeyEncryptionAlgorithm(), + getPGPDigestCalculator(secretKey.getS2K().getHashAlgorithm()), + (int) secretKey.getS2K().getIterationCount()) + .build(passphrase.getChars()); + } + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, + PGPDigestCalculator digestCalculator, + Passphrase passphrase) { + return new BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator) + .build(passphrase.getChars()); + } + + @Override + public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) { + return new BcPBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider()) + .build(passphrase.getChars()); + } + + @Override + public BcPGPDigestCalculatorProvider getPGPDigestCalculatorProvider() { + return new BcPGPDigestCalculatorProvider(); + } + + @Override + public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() { + return new BcPGPContentVerifierBuilderProvider(); + } + + @Override + public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) { + return new BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm); + } + + @Override + public KeyFingerPrintCalculator getKeyFingerprintCalculator() { + return new BcKeyFingerprintCalculator(); + } + + @Override + public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) { + return new BcPBEDataDecryptorFactory(passphrase.getChars(), getPGPDigestCalculatorProvider()); + } + + @Override + public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) { + return new BcPublicKeyDataDecryptorFactory(privateKey); + } + + @Override + public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) { + return new BcPublicKeyKeyEncryptionMethodGenerator(key); + } + + @Override + public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) { + return new BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()); + } + + @Override + public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) { + return new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm); + } + + @Override + public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) + throws PGPException { + return new BcPGPKeyPair(algorithm.getAlgorithmId(), jceToBcKeyPair(algorithm, keyPair, creationDate), creationDate); + } + + @Override + public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, AsymmetricCipherKeyPair keyPair, Date creationDate) throws PGPException { + return new BcPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate); + } + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException { + return new BcPBESecretKeyEncryptorBuilder( + encryptionAlgorithm.getAlgorithmId(), + getPGPDigestCalculator(hashAlgorithm), + s2kCount) + .build(passphrase.getChars()); + } + + // TODO: Find a better conversion method that does not depend on JcaPGPKeyPair. + private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm, + KeyPair keyPair, + Date creationDate) throws PGPException { + BcPGPKeyConverter converter = new BcPGPKeyConverter(); + + PGPKeyPair pair = new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate); + AsymmetricKeyParameter publicKey = converter.getPublicKey(pair.getPublicKey()); + AsymmetricKeyParameter privateKey = converter.getPrivateKey(pair.getPrivateKey()); + + return new AsymmetricCipherKeyPair(publicKey, privateKey); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/CryptoEngineImplementation.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/CryptoEngineImplementation.java new file mode 100644 index 00000000..154abb65 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/implementation/CryptoEngineImplementation.java @@ -0,0 +1,91 @@ +package org.pgpainless.implementation; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Date; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.util.Passphrase; + +public interface CryptoEngineImplementation { + + PBESecretKeyEncryptor getPBESecretKeyEncryptor(PGPSecretKey secretKey, Passphrase passphrase) throws PGPException; + + default PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, + Passphrase passphrase) + throws PGPException { + return getPBESecretKeyEncryptor(symmetricKeyAlgorithm, + getPGPDigestCalculator(HashAlgorithm.SHA1), passphrase); + } + + PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, + PGPDigestCalculator digestCalculator, + Passphrase passphrase); + + PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException; + + PGPDigestCalculatorProvider getPGPDigestCalculatorProvider() throws PGPException; + + default PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException { + return getPGPDigestCalculator(algorithm.getAlgorithmId()); + } + + default PGPDigestCalculator getPGPDigestCalculator(int algorithm) throws PGPException { + return getPGPDigestCalculatorProvider().get(algorithm); + } + + PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider(); + + default PGPContentSignerBuilder getPGPContentSignerBuilder(PublicKeyAlgorithm keyAlgorithm, HashAlgorithm hashAlgorithm) { + return getPGPContentSignerBuilder(keyAlgorithm.getAlgorithmId(), hashAlgorithm.getAlgorithmId()); + } + + PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm); + + KeyFingerPrintCalculator getKeyFingerprintCalculator(); + + PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) throws PGPException; + + PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey); + + PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key); + + PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase); + + default PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm symmetricKeyAlgorithm) { + return getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId()); + } + + PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm); + + PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException; + + PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, AsymmetricCipherKeyPair keyPair, Date creationDate) throws PGPException, NoSuchAlgorithmException, IOException, InvalidKeySpecException; + + PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, + HashAlgorithm hashAlgorithm, + int s2kCount, + Passphrase passphrase) throws PGPException; +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java new file mode 100644 index 00000000..293f7bee --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java @@ -0,0 +1,15 @@ +package org.pgpainless.implementation; + +public class ImplementationFactory { + + private static CryptoEngineImplementation FACTORY_IMPLEMENTATION = new BcCryptoEngineImplementation(); + + public static void setFactoryImplementation(CryptoEngineImplementation implementation) { + FACTORY_IMPLEMENTATION = implementation; + } + + public static CryptoEngineImplementation getInstance() { + return FACTORY_IMPLEMENTATION; + } + +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/JceCryptoEngineImplementation.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/JceCryptoEngineImplementation.java new file mode 100644 index 00000000..b957d790 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/implementation/JceCryptoEngineImplementation.java @@ -0,0 +1,162 @@ +package org.pgpainless.implementation; + +import java.io.IOException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Date; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; +import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.provider.ProviderFactory; +import org.pgpainless.util.Passphrase; + +public class JceCryptoEngineImplementation implements CryptoEngineImplementation { + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(PGPSecretKey secretKey, Passphrase passphrase) + throws PGPException { + return new JcePBESecretKeyEncryptorBuilder(secretKey.getKeyEncryptionAlgorithm()) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()); + } + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, PGPDigestCalculator digestCalculator, Passphrase passphrase) { + return new JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()); + } + + @Override + public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException { + return new JcePBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider()) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()); + } + + @Override + public PGPDigestCalculatorProvider getPGPDigestCalculatorProvider() + throws PGPException { + return new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(ProviderFactory.getProvider()) + .build(); + } + + @Override + public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() { + return new JcaPGPContentVerifierBuilderProvider() + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) { + return new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public KeyFingerPrintCalculator getKeyFingerprintCalculator() { + return new JcaKeyFingerprintCalculator() + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) + throws PGPException { + return new JcePBEDataDecryptorFactoryBuilder(getPGPDigestCalculatorProvider()) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()); + } + + @Override + public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) { + return new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(ProviderFactory.getProvider()) + .build(privateKey); + } + + @Override + public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) { + return new JcePublicKeyKeyEncryptionMethodGenerator(key) + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) { + return new JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) { + return new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) + .setProvider(ProviderFactory.getProvider()); + } + + @Override + public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException { + return new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate); + } + + @Override + public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, AsymmetricCipherKeyPair keyPair, Date creationDate) throws PGPException, NoSuchAlgorithmException, IOException, InvalidKeySpecException { + return new JcaPGPKeyPair(algorithm.getAlgorithmId(), bcToJceKeyPair(keyPair), creationDate); + } + + @Override + public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException { + return new JcePBESecretKeyEncryptorBuilder( + encryptionAlgorithm.getAlgorithmId(), + getPGPDigestCalculator(hashAlgorithm), + s2kCount) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()); + } + + private KeyPair bcToJceKeyPair(AsymmetricCipherKeyPair keyPair) + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + byte[] pkcs8Encoded = PrivateKeyInfoFactory.createPrivateKeyInfo(keyPair.getPrivate()).getEncoded(); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(pkcs8Encoded); + byte[] spkiEncoded = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(keyPair.getPublic()).getEncoded(); + X509EncodedKeySpec spkiKeySpec = new X509EncodedKeySpec(spkiEncoded); + KeyFactory keyFac = KeyFactory.getInstance("RSA"); + return new KeyPair(keyFac.generatePublic(spkiKeySpec), keyFac.generatePrivate(pkcs8KeySpec)); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java index afe3c1c5..f446383f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import javax.annotation.Nonnull; -import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyRingGenerator; @@ -44,14 +43,11 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.ecc.EllipticCurve; import org.pgpainless.key.generation.type.rsa.RsaLength; @@ -331,33 +327,28 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { } private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) { - return new JcaPGPContentSignerBuilder( - certKey.getPublicKey().getAlgorithm(), HashAlgorithm.SHA512.getAlgorithmId()) - .setProvider(ProviderFactory.getProvider()); + return ImplementationFactory.getInstance().getPGPContentSignerBuilder( + certKey.getPublicKey().getAlgorithm(), + HashAlgorithm.SHA512.getAlgorithmId()); } private PBESecretKeyEncryptor buildSecretKeyEncryptor() { PBESecretKeyEncryptor encryptor = passphrase == null || passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted - new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, digestCalculator) - .setProvider(ProviderFactory.getProvider()) - .build(passphrase.getChars()); + ImplementationFactory.getInstance().getPBESecretKeyEncryptor( + SymmetricKeyAlgorithm.AES_256, digestCalculator, passphrase); return encryptor; } private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException { PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ? null : - new JcePBESecretKeyDecryptorBuilder() - .build(passphrase.getChars()); + ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); return decryptor; } private PGPDigestCalculator buildDigestCalculator() throws PGPException { - return new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(ProviderFactory.getProvider()) - .build() - .get(HashAlgorithm.SHA1.getAlgorithmId()); + return ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1); } } } @@ -373,8 +364,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { KeyPair keyPair = certKeyGenerator.generateKeyPair(); // Form PGP key pair - PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(type.getAlgorithm().getAlgorithmId(), - keyPair, new Date()); + PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance().getPGPKeyPair(type.getAlgorithm(), keyPair, new Date()); return pgpKeyPair; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 52eccb0e..b9a94686 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -45,12 +45,10 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeySpec; @@ -202,10 +200,13 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { PBESecretKeyDecryptor ringDecryptor = keyRingProtector.getDecryptor(primaryKey.getKeyID()); PBESecretKeyEncryptor subKeyEncryptor = subKeyProtector.getEncryptor(secretSubKey.getKeyID()); - PGPDigestCalculator digestCalculator = new BcPGPDigestCalculatorProvider() - .get(defaultDigestHashAlgorithm.getAlgorithmId()); - PGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder( - primaryKey.getAlgorithm(), HashAlgorithm.SHA256.getAlgorithmId()); + PGPDigestCalculator digestCalculator = + ImplementationFactory.getInstance().getPGPDigestCalculator(defaultDigestHashAlgorithm); + PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance() + .getPGPContentSignerBuilder( + primaryKey.getAlgorithm(), + HashAlgorithm.SHA256.getAlgorithmId() // TODO: Why SHA256? + ); PGPPrivateKey privateSubKey = unlockSecretKey(secretSubKey, subKeyProtector); PGPKeyPair subKeyPair = new PGPKeyPair(secretSubKey.getPublicKey(), privateSubKey); @@ -222,12 +223,11 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { private PGPSecretKey generateSubKey(@Nonnull KeySpec keySpec, @Nonnull Passphrase subKeyPassphrase) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider() - .get(defaultDigestHashAlgorithm.getAlgorithmId()); + PGPDigestCalculator checksumCalculator = ImplementationFactory.getInstance() + .getPGPDigestCalculator(defaultDigestHashAlgorithm); PBESecretKeyEncryptor subKeyEncryptor = subKeyPassphrase.isEmpty() ? null : - new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithm.AES_256.getAlgorithmId()) - .build(subKeyPassphrase.getChars()); + ImplementationFactory.getInstance().getPBESecretKeyEncryptor(SymmetricKeyAlgorithm.AES_256, subKeyPassphrase); PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); PGPSecretKey secretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), @@ -530,8 +530,8 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { // TODO: Move to utility class private PGPSecretKey lockPrivateKey(PGPPrivateKey privateKey, PGPPublicKey publicKey, SecretKeyRingProtector protector) throws PGPException { - PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider() - .get(defaultDigestHashAlgorithm.getAlgorithmId()); + PGPDigestCalculator checksumCalculator = ImplementationFactory.getInstance() + .getPGPDigestCalculator(defaultDigestHashAlgorithm); PBESecretKeyEncryptor encryptor = protector.getEncryptor(publicKey.getKeyID()); PGPSecretKey secretKey = new PGPSecretKey(privateKey, publicKey, checksumCalculator, publicKey.isMasterKey(), encryptor); return secretKey; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java index 7b0297c7..6f3a1c2c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java @@ -27,7 +27,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.pgpainless.implementation.ImplementationFactory; public class KeyRingReader { @@ -90,27 +90,27 @@ public class KeyRingReader { public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream) throws IOException { return new PGPPublicKeyRing( PGPUtil.getDecoderStream(inputStream), - new BcKeyFingerprintCalculator()); + ImplementationFactory.getInstance().getKeyFingerprintCalculator()); } public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream) throws IOException, PGPException { return new PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(inputStream), - new BcKeyFingerprintCalculator()); + ImplementationFactory.getInstance().getKeyFingerprintCalculator()); } public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream) throws IOException, PGPException { return new PGPSecretKeyRing( PGPUtil.getDecoderStream(inputStream), - new BcKeyFingerprintCalculator()); + ImplementationFactory.getInstance().getKeyFingerprintCalculator()); } public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream) throws IOException, PGPException { return new PGPSecretKeyRingCollection( PGPUtil.getDecoderStream(inputStream), - new BcKeyFingerprintCalculator()); + ImplementationFactory.getInstance().getKeyFingerprintCalculator()); } private static void validateStreamsNotBothNull(InputStream publicIn, InputStream secretIn) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java new file mode 100644 index 00000000..4e8ad59b --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java @@ -0,0 +1,52 @@ +package org.pgpainless.key.protection; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.util.Passphrase; + +public class CallbackBasedKeyringProtector implements SecretKeyRingProtector2 { + + private final Map passphraseCache = new ConcurrentHashMap<>(); + private final Callback callback; + + public CallbackBasedKeyringProtector(Callback callback) { + if (callback == null) { + throw new NullPointerException("Callback MUST NOT be null."); + } + this.callback = callback; + } + + @Override + public PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException { + Passphrase passphrase = lookupPassphraseInCache(key); + if (passphrase != null) { + passphrase = callback.getPassphraseFor(key); + passphraseCache.put(key.getKeyID(), passphrase); + } + return ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); + } + + @Override + public PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException { + Passphrase passphrase = lookupPassphraseInCache(key); + if (passphrase != null) { + passphrase = callback.getPassphraseFor(key); + passphraseCache.put(key.getKeyID(), passphrase); + } + return ImplementationFactory.getInstance().getPBESecretKeyEncryptor(key, passphrase); + } + + private Passphrase lookupPassphraseInCache(PGPSecretKey key) { + return passphraseCache.get(key.getKeyID()); + } + + public interface Callback { + Passphrase getPassphraseFor(PGPSecretKey secretKey); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java index 385e82fb..ad348a7e 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java @@ -15,10 +15,10 @@ */ package org.pgpainless.key.protection; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; @@ -69,7 +69,7 @@ public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, Se @Override @Nullable - public Passphrase getPassphraseFor(@Nonnull Long keyId) { + public Passphrase getPassphraseFor(Long keyId) { Passphrase passphrase = cache.get(keyId); if (passphrase == null || !passphrase.isValid()) { passphrase = provider.getPassphraseFor(keyId); @@ -82,7 +82,7 @@ public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, Se @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) { + public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException { return protector.getDecryptor(keyId); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java index 7328b77b..0bec8b00 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java @@ -23,10 +23,7 @@ import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; import org.pgpainless.util.Passphrase; @@ -36,8 +33,6 @@ import org.pgpainless.util.Passphrase; */ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtector { - private static final PGPDigestCalculatorProvider calculatorProvider = new BcPGPDigestCalculatorProvider(); - protected final KeyRingProtectionSettings protectionSettings; protected final SecretKeyPassphraseProvider passphraseProvider; @@ -86,11 +81,10 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(Long keyId) { + public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null ? null : - new BcPBESecretKeyDecryptorBuilder(calculatorProvider) - .build(passphrase.getChars()); + ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); } @Override @@ -98,10 +92,10 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null ? null : - new BcPBESecretKeyEncryptorBuilder( - protectionSettings.getEncryptionAlgorithm().getAlgorithmId(), - calculatorProvider.get(protectionSettings.getHashAlgorithm().getAlgorithmId()), - protectionSettings.getS2kCount()) - .build(passphrase.getChars()); + ImplementationFactory.getInstance().getPBESecretKeyEncryptor( + protectionSettings.getEncryptionAlgorithm(), + protectionSettings.getHashAlgorithm(), + protectionSettings.getS2kCount(), + passphrase); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java index 4d27ed02..7a48d740 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java @@ -25,6 +25,9 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.pgpainless.util.Passphrase; +/** + * @deprecated use {@link SecretKeyRingProtector2} instead. + */ public interface SecretKeyRingProtector { /** @@ -34,7 +37,7 @@ public interface SecretKeyRingProtector { * @param keyId id of the key * @return decryptor for the key */ - @Nullable PBESecretKeyDecryptor getDecryptor(Long keyId); + @Nullable PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException; /** * Return an encryptor for the key of id {@code keyId}. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector2.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector2.java new file mode 100644 index 00000000..61c68f19 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector2.java @@ -0,0 +1,13 @@ +package org.pgpainless.key.protection; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; + +public interface SecretKeyRingProtector2 { + + PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException; + + PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException; +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtectorAdapter.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtectorAdapter.java new file mode 100644 index 00000000..4252231f --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtectorAdapter.java @@ -0,0 +1,19 @@ +package org.pgpainless.key.protection; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; + +public interface SecretKeyRingProtectorAdapter extends SecretKeyRingProtector, SecretKeyRingProtector2 { + + @Override + default PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException { + return getDecryptor(key.getKeyID()); + } + + @Override + default PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException { + return getEncryptor(key.getKeyID()); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java index 24ea569d..a1e1d865 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java @@ -18,6 +18,7 @@ package org.pgpainless.key.protection.passphrase_provider; import java.util.Map; import javax.annotation.Nullable; +import org.bouncycastle.openpgp.PGPSecretKey; import org.pgpainless.util.Passphrase; /** diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java index aa5d43ef..1b81ef93 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java @@ -17,6 +17,7 @@ package org.pgpainless.key.protection.passphrase_provider; import javax.annotation.Nullable; +import org.bouncycastle.openpgp.PGPSecretKey; import org.pgpainless.util.Passphrase; /** @@ -24,12 +25,15 @@ import org.pgpainless.util.Passphrase; */ public interface SecretKeyPassphraseProvider { + @Nullable default Passphrase getPassphraseFor(PGPSecretKey secretKey) { + return getPassphraseFor(secretKey.getKeyID()); + } /** * Return a passphrase for the given key. If no record has been found, return null. * Note: In case of an unprotected secret key, this method must may not return null, but a {@link Passphrase} with * a content of null. * - * @param keyId id of the key + * @param keyId if of the secret key * @return passphrase or null, if no passphrase record has been found. */ @Nullable Passphrase getPassphraseFor(Long keyId); diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java index 1d29f841..b68f63b3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java @@ -15,7 +15,6 @@ */ package org.pgpainless.util; -import javax.annotation.Nonnull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -26,6 +25,7 @@ import java.util.Iterator; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; @@ -38,13 +38,14 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.util.io.Streams; import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.selection.key.PublicKeySelectionStrategy; -import org.pgpainless.key.selection.key.util.And; import org.pgpainless.key.selection.key.impl.NoRevocation; import org.pgpainless.key.selection.key.impl.SignedByMasterKey; +import org.pgpainless.key.selection.key.util.And; public class BCUtil { @@ -72,8 +73,9 @@ public class BCUtil { publicKey.encode(buffer, false); } } + KeyFingerPrintCalculator fingerprintCalculator = ImplementationFactory.getInstance().getKeyFingerprintCalculator(); - return new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator()); + return new PGPPublicKeyRing(buffer.toByteArray(), fingerprintCalculator); } /* diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java index af97d100..ac317740 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; import org.junit.jupiter.api.Test; import org.pgpainless.key.TestKeys; import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; @@ -37,7 +38,7 @@ public class PassphraseProtectedKeyTest { @Nullable @Override public Passphrase getPassphraseFor(Long keyId) { - if (keyId == TestKeys.CRYPTIE_KEY_ID) { + if (keyId.equals(TestKeys.CRYPTIE_KEY_ID)) { return new Passphrase(TestKeys.CRYPTIE_PASSWORD.toCharArray()); } else { return null; diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java new file mode 100644 index 00000000..551f4532 --- /dev/null +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java @@ -0,0 +1,29 @@ +package org.pgpainless.sop; + +import static org.pgpainless.sop.Print.err_ln; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.pgpainless.PGPainless; + +public class SopKeyUtil { + + public static List loadKeysFromFiles(File... files) throws IOException, PGPException { + List secretKeyRings = new ArrayList<>(); + for (File file : files) { + try(FileInputStream in = new FileInputStream(file)) { + secretKeyRings.add(PGPainless.readKeyRing().secretKeyRing(in)); + } catch (PGPException | IOException e) { + err_ln("Could not load secret key " + file.getName() + ": " + e.getMessage()); + throw e; + } + } + return secretKeyRings; + } +} diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java index 594967e4..bb5022ea 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java @@ -15,11 +15,19 @@ */ package org.pgpainless.sop.commands; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.pgpainless.PGPainless; import picocli.CommandLine; import java.io.File; +import java.io.IOException; +import java.util.List; import static org.pgpainless.sop.Print.err_ln; +import static org.pgpainless.sop.SopKeyUtil.loadKeysFromFiles; @CommandLine.Command(name = "decrypt", description = "Decrypt a message from standard input") @@ -79,5 +87,26 @@ public class Decrypt implements Runnable { err_ln("To enable signature verification, both --verify-out and at least one --verify-with argument must be supplied."); System.exit(23); } + + if (sessionKeyOut != null || withSessionKey != null) { + err_ln("session key in and out are not yet supported."); + System.exit(1); + } + + PGPSecretKeyRingCollection secretKeys; + try { + List secretKeyRings = loadKeysFromFiles(keys); + secretKeys = new PGPSecretKeyRingCollection(secretKeyRings); + } catch (PGPException | IOException e) { + err_ln(e.getMessage()); + System.exit(1); + return; + } + + + + PGPainless.decryptAndOrVerify() + .onInputStream(System.in) + .decryptWith(secretKeys); } }