From af5d7981c7e20814bef09bee3aa1f8defee2d274 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 14:38:54 +0100 Subject: [PATCH 001/154] PGPSignatureSubpacketGenerator: Add generics to the packet list --- .../PGPSignatureSubpacketGenerator.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index 4330035bc1..9f259c974c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -649,9 +649,9 @@ public boolean removePacketsOfType(int subpacketType) public boolean hasSubpacket( int type) { - for (int i = 0; i != packets.size(); i++) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { return true; } @@ -670,17 +670,17 @@ public boolean hasSubpacket( public SignatureSubpacket[] getSubpackets( int type) { - List list = new ArrayList(); + List list = new ArrayList<>(); - for (int i = 0; i != packets.size(); i++) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { - list.add(packets.get(i)); + list.add(packet); } } - return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{}); + return list.toArray(new SignatureSubpacket[0]); } public PGPSignatureSubpacketVector generate() @@ -691,9 +691,9 @@ public PGPSignatureSubpacketVector generate() private boolean contains(int type) { - for (int i = 0; i < packets.size(); ++i) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { return true; } From 7cb685b55dcfd703450dcfe0cd7ced49d6200c63 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 14:45:04 +0100 Subject: [PATCH 002/154] PGPSignatureSubpacketVector: Add generics to the subpacket list --- .../openpgp/PGPSignatureSubpacketVector.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java index 10e36a478e..078faa49f6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java @@ -116,7 +116,7 @@ public boolean hasSubpacket( public SignatureSubpacket[] getSubpackets( int type) { - List list = new ArrayList(); + List list = new ArrayList<>(); for (int i = 0; i != packets.length; i++) { @@ -126,20 +126,20 @@ public SignatureSubpacket[] getSubpackets( } } - return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{}); + return list.toArray(new SignatureSubpacket[0]); } public PGPSignatureList getEmbeddedSignatures() throws PGPException { SignatureSubpacket[] sigs = getSubpackets(SignatureSubpacketTags.EMBEDDED_SIGNATURE); - ArrayList l = new ArrayList(); + ArrayList l = new ArrayList<>(); - for (int i = 0; i < sigs.length; i++) + for (SignatureSubpacket sig : sigs) { try { - l.add(new PGPSignature(SignaturePacket.fromByteArray(sigs[i].getData()))); + l.add(new PGPSignature(SignaturePacket.fromByteArray(sig.getData()))); } catch (IOException e) { @@ -147,7 +147,7 @@ public PGPSignatureList getEmbeddedSignatures() } } - return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()])); + return new PGPSignatureList(l.toArray(new PGPSignature[0])); } public NotationData[] getNotationDataOccurrences() @@ -179,7 +179,7 @@ public NotationData[] getNotationDataOccurences() public NotationData[] getNotationDataOccurrences(String notationName) { NotationData[] notations = getNotationDataOccurrences(); - List notationsWithName = new ArrayList(); + List notationsWithName = new ArrayList<>(); for (int i = 0; i != notations.length; i++) { NotationData notation = notations[i]; @@ -188,7 +188,7 @@ public NotationData[] getNotationDataOccurrences(String notationName) notationsWithName.add(notation); } } - return (NotationData[])notationsWithName.toArray(new NotationData[0]); + return notationsWithName.toArray(new NotationData[0]); } public long getIssuerKeyID() From 812f2bbd526e8cbf45a8ba00f0e07d6ec62b19fb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 15:01:52 +0100 Subject: [PATCH 003/154] PGPKeyRingGenerator: Various improvements * Rename masterKey -> primaryKey * Add generics to list structures * sanitize primary and subkeys * properly instantiate signature generators by passing key version --- .../bouncycastle/openpgp/PGPKeyRingGenerator.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index bb01c966b0..667299d54a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -153,7 +153,7 @@ public PGPKeyRingGenerator( this.keySignerBuilder = keySignerBuilder; PGPSignature certSig = (PGPSignature)originalSecretRing.getPublicKey().getSignatures().next(); - List hashedVec = new ArrayList(); + List hashedVec = new ArrayList(); PGPSignatureSubpacketVector existing = certSig.getHashedSubPackets(); for (int i = 0; i != existing.size(); i++) { @@ -164,7 +164,7 @@ public PGPKeyRingGenerator( hashedVec.add(existing.packets[i]); } this.hashedPcks = new PGPSignatureSubpacketVector( - (SignatureSubpacket[])hashedVec.toArray(new SignatureSubpacket[hashedVec.size()])); + hashedVec.toArray(new SignatureSubpacket[0])); this.unhashedPcks = certSig.getUnhashedSubPackets(); keys.addAll(originalSecretRing.keys); @@ -324,7 +324,7 @@ public void addSubKey( sGen.setUnhashedSubpackets(unhashedPcks); - List subSigs = new ArrayList(); + List subSigs = new ArrayList(); subSigs.add(sGen.generateCertification(primaryKey.getPublicKey(), keyPair.getPublicKey())); @@ -362,14 +362,14 @@ public PGPSecretKeyRing generateSecretKeyRing() */ public PGPPublicKeyRing generatePublicKeyRing() { - Iterator it = keys.iterator(); - List pubKeys = new ArrayList(); + Iterator it = keys.iterator(); + List pubKeys = new ArrayList(); - pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); + pubKeys.add((it.next()).getPublicKey()); while (it.hasNext()) { - pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); + pubKeys.add((it.next()).getPublicKey()); } return new PGPPublicKeyRing(pubKeys); From 7e8d9a955fcdae80ca4a764c23eac0e8d7e091eb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 15:04:19 +0100 Subject: [PATCH 004/154] Add PublicKeyUtils This class contains methods for checking properties of public key algorithms --- .../org/bouncycastle/bcpg/PublicKeyUtils.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java index 48c77ba566..47d8745bc2 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java @@ -30,27 +30,26 @@ public static boolean isSigningAlgorithm(int publicKeyAlgorithm) } } -// /** -// * Return true, if the public key algorithm that corresponds to the given ID is capable of encryption. -// * -// * @param publicKeyAlgorithm public key algorithm id -// * @return true if algorithm can encrypt -// */ -// public static boolean isEncryptionAlgorithm(int publicKeyAlgorithm) -// { -// switch (publicKeyAlgorithm) -// { -// case PublicKeyAlgorithmTags.RSA_GENERAL: -// case PublicKeyAlgorithmTags.RSA_ENCRYPT: -// case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: -// case PublicKeyAlgorithmTags.ECDH: -// case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: -// case PublicKeyAlgorithmTags.DIFFIE_HELLMAN: -// case PublicKeyAlgorithmTags.X25519: -// case PublicKeyAlgorithmTags.X448: -// return true; -// default: -// return false; -// } -// } + /** + * Return true, if the public key algorithm that corresponds to the given ID is capable of encryption. + * @param publicKeyAlgorithm public key algorithm id + * @return true if algorithm can encrypt + */ + public static boolean isEncryptionAlgorithm(int publicKeyAlgorithm) + { + switch (publicKeyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ECDH: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + case PublicKeyAlgorithmTags.DIFFIE_HELLMAN: + case PublicKeyAlgorithmTags.X25519: + case PublicKeyAlgorithmTags.X448: + return true; + default: + return false; + } + } } From 0700d5fe88fb6ceb4e2f9c1b2305968eb18f6911 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:04:42 +0100 Subject: [PATCH 005/154] Operator changes --- .../PBESecretKeyDecryptorBuilder.java | 9 ++++++++ .../PBESecretKeyDecryptorBuilderProvider.java | 15 ++++++++++++ .../bc/BcPBESecretKeyDecryptorBuilder.java | 2 ++ ...cPBESecretKeyDecryptorBuilderProvider.java | 14 +++++++++++ .../JcePBESecretKeyDecryptorBuilder.java | 2 ++ ...ePBESecretKeyDecryptorBuilderProvider.java | 23 +++++++++++++++++++ 6 files changed, 65 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilder.java new file mode 100644 index 0000000000..b19389b50e --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilder.java @@ -0,0 +1,9 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.openpgp.PGPException; + +public interface PBESecretKeyDecryptorBuilder +{ + PBESecretKeyDecryptor build(char[] passphrase) + throws PGPException; +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilderProvider.java new file mode 100644 index 0000000000..e94ddb551b --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptorBuilderProvider.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.openpgp.PGPException; + +/** + * Provider for {@link PBESecretKeyDecryptorBuilder} instances. + * The purpose of this class is to act as an abstract factory, whose subclasses can decide, which concrete + * implementation of {@link PBESecretKeyDecryptorBuilder} (builder for objects that can unlock encrypted + * secret keys) to return. + */ +public interface PBESecretKeyDecryptorBuilderProvider +{ + PBESecretKeyDecryptorBuilder provide() + throws PGPException; +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java index cd1d20b1c3..fe986d32a0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java @@ -3,9 +3,11 @@ import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; public class BcPBESecretKeyDecryptorBuilder + implements PBESecretKeyDecryptorBuilder { private PGPDigestCalculatorProvider calculatorProvider; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilderProvider.java new file mode 100644 index 0000000000..a42c56b7c6 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilderProvider.java @@ -0,0 +1,14 @@ +package org.bouncycastle.openpgp.operator.bc; + +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; + +public class BcPBESecretKeyDecryptorBuilderProvider + implements PBESecretKeyDecryptorBuilderProvider +{ + @Override + public PBESecretKeyDecryptorBuilder provide() + { + return new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java index d99af1cc70..d08c9fc190 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java @@ -15,9 +15,11 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; public class JcePBESecretKeyDecryptorBuilder + implements PBESecretKeyDecryptorBuilder { private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); private PGPDigestCalculatorProvider calculatorProvider; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java new file mode 100644 index 0000000000..bc67caa84c --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java @@ -0,0 +1,23 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; + +public class JcePBESecretKeyDecryptorBuilderProvider + implements PBESecretKeyDecryptorBuilderProvider +{ + private final JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder; + + public JcePBESecretKeyDecryptorBuilderProvider(JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder) + { + this.digestCalculatorProviderBuilder = digestCalculatorProviderBuilder; + } + + @Override + public PBESecretKeyDecryptorBuilder provide() + throws PGPException + { + return new JcePBESecretKeyDecryptorBuilder(digestCalculatorProviderBuilder.build()); + } +} From 50e737370d077ed2476f8f70118b981e76cc50d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:05:26 +0100 Subject: [PATCH 006/154] PGPEncryptedDataGenerator: Allow extraction of session-key --- .../openpgp/PGPEncryptedDataGenerator.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java index a0e3d295ca..1cb794fc77 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java @@ -88,6 +88,7 @@ public class PGPEncryptedDataGenerator // If true, force generation of a session key, even if we only have a single password-based encryption method // and could therefore use the S2K output as session key directly. private boolean forceSessionKey = true; + private SessionKeyExtractionCallback sessionKeyExtractionCallback = null; /** * Base constructor. @@ -141,6 +142,11 @@ public void addMethod(PGPKeyEncryptionMethodGenerator method) } + public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) + { + this.sessionKeyExtractionCallback = callback; + } + /** * Create an OutputStream based on the configured methods. *

@@ -213,6 +219,11 @@ else if (directS2K) messageKey = sessionKey; } + if (sessionKeyExtractionCallback != null) + { + sessionKeyExtractionCallback.extractSessionKey(new PGPSessionKey(defAlgorithm, sessionKey)); + } + PGPDataEncryptor dataEncryptor = dataEncryptorBuilder.build(messageKey); digestCalc = dataEncryptor.getIntegrityCalculator(); BCPGHeaderObject encOut; @@ -441,4 +452,9 @@ public void close() this.finish(); } } + + public interface SessionKeyExtractionCallback + { + void extractSessionKey(PGPSessionKey sessionKey); + } } From 9c8d8fb21c0b1c64655116f5d505533dc7b2f19c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:44:01 +0100 Subject: [PATCH 007/154] PGPEncryptedDataList: Expose encrypted data packet --- .../java/org/bouncycastle/openpgp/PGPEncryptedDataList.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java index 2f55f519e7..7c2cf62c20 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java @@ -154,6 +154,11 @@ public PGPEncryptedData get( return (PGPEncryptedData)methods.get(index); } + public InputStreamPacket getEncryptedData() + { + return data; + } + /** * Gets the number of encryption methods in this list. */ From 07b37331200ad4c32203494caf0eaa9eda5fdd1d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:44:53 +0100 Subject: [PATCH 008/154] PGPSignature: Add isRevocation, isHardRevocation methods --- .../bouncycastle/openpgp/PGPSignature.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java index 79419f1a11..996b4e2876 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java @@ -23,6 +23,8 @@ import org.bouncycastle.bcpg.TrustPacket; import org.bouncycastle.bcpg.sig.IssuerFingerprint; import org.bouncycastle.bcpg.sig.IssuerKeyID; +import org.bouncycastle.bcpg.sig.RevocationReason; +import org.bouncycastle.bcpg.sig.RevocationReasonTags; import org.bouncycastle.math.ec.rfc8032.Ed25519; import org.bouncycastle.math.ec.rfc8032.Ed448; import org.bouncycastle.openpgp.operator.PGPContentVerifier; @@ -905,6 +907,37 @@ public static boolean isCertification(int signatureType) || PGPSignature.POSITIVE_CERTIFICATION == signatureType; } + public static boolean isRevocation(int signatureType) + { + return PGPSignature.KEY_REVOCATION == signatureType + || PGPSignature.CERTIFICATION_REVOCATION == signatureType + || PGPSignature.SUBKEY_REVOCATION == signatureType; + } + + public boolean isHardRevocation() + { + if (!isRevocation(getSignatureType())) + { + return false; // no revocation + } + + if (!hasSubpackets()) + { + return true; // consider missing subpackets (and therefore missing reason) as hard revocation + } + + // only consider reasons from the hashed packet area + RevocationReason reason = getHashedSubPackets() != null ? + getHashedSubPackets().getRevocationReason() : null; + if (reason == null) + { + return true; // missing reason packet is hard + } + + return reason.getRevocationReason() == RevocationReasonTags.NO_REASON // No reason is hard + || reason.getRevocationReason() == RevocationReasonTags.KEY_COMPROMISED; // key compromise is hard + } + /** * Return true, if the cryptographic signature encoding of the two signatures match. * From f32459f0b0f2ac0d5acb8df76ac83ae4f7fbeecd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:07:03 +0100 Subject: [PATCH 009/154] API --- .../openpgp/PGPSignatureException.java | 15 + .../openpgp/api/BcOpenPGPImplementation.java | 109 + .../openpgp/api/EncryptedDataPacketType.java | 37 + .../openpgp/api/JcaOpenPGPImplementation.java | 157 ++ .../openpgp/api/KeyPassphraseProvider.java | 112 + .../api/MessageEncryptionMechanism.java | 134 ++ .../api/MissingPassphraseCallback.java | 13 + .../openpgp/api/OpenPGPCertificate.java | 2049 +++++++++++++++++ .../openpgp/api/OpenPGPImplementation.java | 183 ++ .../bouncycastle/openpgp/api/OpenPGPKey.java | 303 +++ .../openpgp/api/OpenPGPKeyMaterialPool.java | 206 ++ .../api/OpenPGPKeyMaterialProvider.java | 40 + .../openpgp/api/OpenPGPMessageGenerator.java | 797 +++++++ .../api/OpenPGPMessageInputStream.java | 743 ++++++ .../api/OpenPGPMessageOutputStream.java | 470 ++++ .../openpgp/api/OpenPGPMessageProcessor.java | 500 ++++ .../openpgp/api/OpenPGPNotationRegistry.java | 19 + .../openpgp/api/OpenPGPSignature.java | 535 +++++ .../openpgp/api/RetainingInputStream.java | 107 + .../IncorrectPGPSignatureException.java | 15 + .../MalformedPGPSignatureException.java | 16 + .../exception/MissingIssuerCertException.java | 15 + .../openpgp/api/util/DebugPrinter.java | 203 ++ .../openpgp/api/util/UTCUtil.java | 48 + .../bouncycastle/openpgp/OpenPGPTestKeys.java | 453 ++++ .../api/test/OpenPGPCertificateTest.java | 846 +++++++ .../api/test/OpenPGPMessageGeneratorTest.java | 180 ++ .../api/test/OpenPGPMessageProcessorTest.java | 664 ++++++ .../api/test/StackPassphraseCallback.java | 38 + .../StaticV6OpenPGPMessageGeneratorTest.java | 92 + .../openpgp/test/RegressionTest.java | 9 +- 31 files changed, 9107 insertions(+), 1 deletion(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/EncryptedDataPacketType.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/OpenPGPTestKeys.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureException.java new file mode 100644 index 0000000000..88b2887319 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureException.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp; + +public class PGPSignatureException + extends PGPException +{ + public PGPSignatureException(String message) + { + super(message); + } + + public PGPSignatureException(String message, Exception cause) + { + super(message, cause); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java new file mode 100644 index 0000000000..b826ea6c26 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java @@ -0,0 +1,109 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilderProvider; +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.BcPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory; + +import java.io.InputStream; + +public class BcOpenPGPImplementation + extends OpenPGPImplementation +{ + @Override + public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream) + { + return new BcPGPObjectFactory(packetInputStream); + } + + @Override + public PGPContentVerifierBuilderProvider pgpContentVerifierBuilderProvider() + { + return new BcPGPContentVerifierBuilderProvider(); + } + + @Override + public PBESecretKeyDecryptorBuilderProvider pbeSecretKeyDecryptorBuilderProvider() + { + return new BcPBESecretKeyDecryptorBuilderProvider(); + } + + @Override + public PGPDataEncryptorBuilder pgpDataEncryptorBuilder(int symmetricKeyAlgorithm) + { + return new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm); + } + + @Override + public PublicKeyKeyEncryptionMethodGenerator publicKeyKeyEncryptionMethodGenerator(PGPPublicKey encryptionSubkey) + { + return new BcPublicKeyKeyEncryptionMethodGenerator(encryptionSubkey); + } + + @Override + public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase) + { + return new BcPBEKeyEncryptionMethodGenerator(messagePassphrase); + } + + @Override + public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase, S2K.Argon2Params argon2Params) + { + return new BcPBEKeyEncryptionMethodGenerator(messagePassphrase, argon2Params); + } + + @Override + public PGPContentSignerBuilder pgpContentSignerBuilder(int publicKeyAlgorithm, int hashAlgorithm) + { + return new BcPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm); + } + + @Override + public PBEDataDecryptorFactory pbeDataDecryptorFactory(char[] messagePassphrase) + throws PGPException + { + return new BcPBEDataDecryptorFactory(messagePassphrase, pgpDigestCalculatorProvider()); + } + + @Override + public SessionKeyDataDecryptorFactory sessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) + { + return new BcSessionKeyDataDecryptorFactory(sessionKey); + } + + @Override + public PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory(PGPPrivateKey decryptionKey) + { + return new BcPublicKeyDataDecryptorFactory(decryptionKey); + } + + @Override + public PGPDigestCalculatorProvider pgpDigestCalculatorProvider() + throws PGPException + { + return new BcPGPDigestCalculatorProvider(); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/EncryptedDataPacketType.java b/pg/src/main/java/org/bouncycastle/openpgp/api/EncryptedDataPacketType.java new file mode 100644 index 0000000000..e8f5a67aa8 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/EncryptedDataPacketType.java @@ -0,0 +1,37 @@ +package org.bouncycastle.openpgp.api; + +/** + * Encryption Mode. + */ +public enum EncryptedDataPacketType +{ + /** + * Symmetrically-Encrypted Data packet. + * This method is deprecated, as it does not protect against malleability. + * + * @deprecated + */ + @Deprecated + SED, // deprecated + /** + * Symmetrically-Encrypted-Integrity-Protected Data packet version 1. + * This method protects the message using symmetric encryption as specified in RFC4880. + * Support for this encryption mode is signalled using + * {@link org.bouncycastle.bcpg.sig.Features#FEATURE_MODIFICATION_DETECTION}. + */ + SEIPDv1, // v4 + + /** + * Symmetrically-Encrypted-Integrity-Protected Data packet version 2. + * This method protects the message using an AEAD encryption scheme specified in RFC9580. + * Support for this feature is signalled using {@link org.bouncycastle.bcpg.sig.Features#FEATURE_SEIPD_V2}. + */ + SEIPDv2, // v6 + + /** + * LibrePGP OCB-Encrypted Data packet. + * This method protects the message using an AEAD encryption scheme specified in LibrePGP. + * Support for this feature is signalled using {@link org.bouncycastle.bcpg.sig.Features#FEATURE_AEAD_ENCRYPTED_DATA}. + */ + LIBREPGP_OED // "v5" +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java new file mode 100644 index 0000000000..c1db969bbd --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java @@ -0,0 +1,157 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +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.JcePBEDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder; + +import java.io.InputStream; +import java.security.Provider; +import java.security.SecureRandom; + +public class JcaOpenPGPImplementation + extends OpenPGPImplementation +{ + private final Provider provider; + private final SecureRandom secureRandom; + + public JcaOpenPGPImplementation() + { + this(new BouncyCastleProvider(), CryptoServicesRegistrar.getSecureRandom()); + } + + public JcaOpenPGPImplementation(Provider provider, SecureRandom secureRandom) + { + this.provider = provider; + this.secureRandom = secureRandom; + } + + @Override + public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream) + { + return new JcaPGPObjectFactory(packetInputStream); + } + + @Override + public PGPContentVerifierBuilderProvider pgpContentVerifierBuilderProvider() + { + JcaPGPContentVerifierBuilderProvider p = new JcaPGPContentVerifierBuilderProvider(); + p.setProvider(provider); + return p; + } + + @Override + public PBESecretKeyDecryptorBuilderProvider pbeSecretKeyDecryptorBuilderProvider() + { + JcaPGPDigestCalculatorProviderBuilder dp = new JcaPGPDigestCalculatorProviderBuilder(); + dp.setProvider(provider); + JcePBESecretKeyDecryptorBuilderProvider p = new JcePBESecretKeyDecryptorBuilderProvider(dp); + return p; + } + + @Override + public PGPDataEncryptorBuilder pgpDataEncryptorBuilder(int symmetricKeyAlgorithm) + { + JcePGPDataEncryptorBuilder b = new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm); + b.setProvider(provider); + b.setSecureRandom(secureRandom); + return b; + } + + @Override + public PublicKeyKeyEncryptionMethodGenerator publicKeyKeyEncryptionMethodGenerator(PGPPublicKey encryptionSubkey) + { + JcePublicKeyKeyEncryptionMethodGenerator g = new JcePublicKeyKeyEncryptionMethodGenerator(encryptionSubkey); + g.setProvider(provider); + g.setSecureRandom(secureRandom); + return g; + } + + @Override + public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase) + { + JcePBEKeyEncryptionMethodGenerator g = new JcePBEKeyEncryptionMethodGenerator(messagePassphrase); + g.setProvider(provider); + g.setSecureRandom(secureRandom); + return g; + } + + @Override + public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase, S2K.Argon2Params argon2Params) + { + JcePBEKeyEncryptionMethodGenerator g = new JcePBEKeyEncryptionMethodGenerator(messagePassphrase, argon2Params); + g.setProvider(provider); + g.setSecureRandom(secureRandom); + return g; + } + + @Override + public PGPContentSignerBuilder pgpContentSignerBuilder(int publicKeyAlgorithm, int hashAlgorithm) + { + JcaPGPContentSignerBuilder b = new JcaPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm); + b.setProvider(provider); + b.setDigestProvider(provider); + b.setSecureRandom(secureRandom); + return b; + } + + @Override + public PBEDataDecryptorFactory pbeDataDecryptorFactory(char[] messagePassphrase) + throws PGPException + { + return new JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider()) + .setProvider(provider) + .build(messagePassphrase); + } + + @Override + public SessionKeyDataDecryptorFactory sessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) + { + return new JceSessionKeyDataDecryptorFactoryBuilder() + .setProvider(provider) + .build(sessionKey); + } + + @Override + public PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory(PGPPrivateKey decryptionKey) + { + return new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(provider) + .setContentProvider(provider) + .build(decryptionKey); + } + + @Override + public PGPDigestCalculatorProvider pgpDigestCalculatorProvider() + throws PGPException + { + return new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(provider) + .build(); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java new file mode 100644 index 0000000000..58c5fac2d6 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java @@ -0,0 +1,112 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.util.Arrays; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface KeyPassphraseProvider +{ + /** + * Return the passphrase for the given key. + * This callback is only fired, if the key is locked and a passphrase is required to unlock it. + * Returning null means, that the passphrase is not available. + * + * @param key the locked (sub-)key. + * @return passphrase or null + */ + char[] getKeyPassword(OpenPGPKey.OpenPGPSecretKey key); + + class DefaultKeyPassphraseProvider + implements KeyPassphraseProvider + { + private final Map passphraseMap = new HashMap<>(); + private final List unassociatedPassphrases = new ArrayList<>(); + private KeyPassphraseProvider callback; + + public DefaultKeyPassphraseProvider() + { + + } + + public DefaultKeyPassphraseProvider(OpenPGPKey key, char[] passphrase) + { + for (OpenPGPKey.OpenPGPSecretKey subkey : key.getSecretKeys().values()) + { + passphraseMap.put(subkey, passphrase); + } + } + + @Override + public char[] getKeyPassword(OpenPGPKey.OpenPGPSecretKey key) + { + if (key.isLocked()) + { + char[] passphrase = passphraseMap.get(key); + if (passphrase != null) + { + return passphrase; + } + + for (char[] unassociatedPassphrase : unassociatedPassphrases) + { + passphrase = unassociatedPassphrase; + if (key.isPassphraseCorrect(passphrase)) + { + addPassphrase(key, passphrase); + return passphrase; + } + } + + if (callback != null) + { + passphrase = callback.getKeyPassword(key); + addPassphrase(key, passphrase); + } + return passphrase; + } + else + { + return null; + } + } + + public DefaultKeyPassphraseProvider addPassphrase(char[] passphrase) + { + boolean found = false; + for (char[] existing : unassociatedPassphrases) + { + found |= (Arrays.areEqual(existing, passphrase)); + } + + if (!found) + { + unassociatedPassphrases.add(passphrase); + } + return this; + } + + public DefaultKeyPassphraseProvider addPassphrase(OpenPGPKey key, char[] passphrase) + { + for (OpenPGPKey.OpenPGPSecretKey subkey : key.getSecretKeys().values()) + { + addPassphrase(subkey, passphrase); + } + return this; + } + + public DefaultKeyPassphraseProvider addPassphrase(OpenPGPKey.OpenPGPSecretKey key, char[] passphrase) + { + passphraseMap.put(key, passphrase); + return this; + } + + public DefaultKeyPassphraseProvider setMissingPassphraseCallback(KeyPassphraseProvider callback) + { + this.callback = callback; + return this; + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java b/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java new file mode 100644 index 0000000000..0fb1d36673 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java @@ -0,0 +1,134 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; + +/** + * Encryption mode (SEIPDv1 / SEIPDv2 / OED) and algorithms. + */ +public class MessageEncryptionMechanism +{ + private final EncryptedDataPacketType mode; + private final int symmetricKeyAlgorithm; + private final int aeadAlgorithm; + + /** + * Create a {@link MessageEncryptionMechanism} tuple. + * + * @param mode encryption mode (packet type) + * @param symmetricKeyAlgorithm symmetric key algorithm for message encryption + * @param aeadAlgorithm aead algorithm for message encryption + */ + private MessageEncryptionMechanism(EncryptedDataPacketType mode, + int symmetricKeyAlgorithm, + int aeadAlgorithm) + { + this.mode = mode; + this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; + this.aeadAlgorithm = aeadAlgorithm; + } + + public EncryptedDataPacketType getMode() + { + return mode; + } + + public int getSymmetricKeyAlgorithm() + { + return symmetricKeyAlgorithm; + } + + public int getAeadAlgorithm() + { + return aeadAlgorithm; + } + + /** + * The data will not be encrypted. + * Useful for sign-only operations. + * + * @return unencrypted encryption setup + */ + public static MessageEncryptionMechanism unencrypted() + { + int none = 0; + return new MessageEncryptionMechanism(EncryptedDataPacketType.SEIPDv1, + SymmetricKeyAlgorithmTags.NULL, none); + } + + /** + * The data will be encrypted and integrity protected using a SEIPDv1 packet. + * + * @param symmetricKeyAlgorithm symmetric cipher algorithm for message encryption + * @return sym. enc. integrity protected encryption setup + */ + public static MessageEncryptionMechanism integrityProtected(int symmetricKeyAlgorithm) + { + int none = 0; + return new MessageEncryptionMechanism(EncryptedDataPacketType.SEIPDv1, symmetricKeyAlgorithm, none); + } + + /** + * The data will be OCB-encrypted as specified by the non-standard LibrePGP document. + * + * @param symmetricKeyAlgorithm symmetric key algorithm which will be combined with OCB to form + * an OCB-encrypted data packet + * @return LibrePGP OCB encryption setup + */ + public static MessageEncryptionMechanism librePgp(int symmetricKeyAlgorithm) + { + return new MessageEncryptionMechanism(EncryptedDataPacketType.LIBREPGP_OED, + symmetricKeyAlgorithm, AEADAlgorithmTags.OCB); + } + + /** + * The data will be AEAD-encrypted using the method described in RFC9580. + * + * @param symmetricKeyAlgorithm symmetric cipher algorithm + * @param aeadAlgorithm AEAD algorithm + * @return AEAD encryption setup + */ + public static MessageEncryptionMechanism aead(int symmetricKeyAlgorithm, int aeadAlgorithm) + { + return new MessageEncryptionMechanism(EncryptedDataPacketType.SEIPDv2, symmetricKeyAlgorithm, aeadAlgorithm); + } + + /** + * Return true, if the message will be encrypted. + * + * @return is encrypted + */ + public boolean isEncrypted() + { + return symmetricKeyAlgorithm != SymmetricKeyAlgorithmTags.NULL; + } + + @Override + public int hashCode() + { + return mode.hashCode() + + 13 * symmetricKeyAlgorithm + + 17 * aeadAlgorithm; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + if (this == obj) + { + return true; + } + if (!(obj instanceof MessageEncryptionMechanism)) + { + return false; + } + MessageEncryptionMechanism m = (MessageEncryptionMechanism) obj; + return getMode() == m.getMode() + && getSymmetricKeyAlgorithm() == m.getSymmetricKeyAlgorithm() + && getAeadAlgorithm() == m.getAeadAlgorithm(); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java b/pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java new file mode 100644 index 0000000000..2e547018a0 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java @@ -0,0 +1,13 @@ +package org.bouncycastle.openpgp.api; + +public interface MissingPassphraseCallback +{ + /** + * Return a passphrase for message decryption. + * Returning null means, that no passphrase is available and decryption is aborted. + * + * @return passphrase + */ + char[] getPassphrase(); + +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java new file mode 100644 index 0000000000..c34d3c2207 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -0,0 +1,2049 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SignaturePacket; +import org.bouncycastle.bcpg.SignatureSubpacket; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.openpgp.*; +import org.bouncycastle.openpgp.api.exception.IncorrectPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; +import org.bouncycastle.openpgp.api.util.UTCUtil; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.util.Iterable; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * OpenPGP certificates (TPKs - transferable public keys) are long-living structures that may change during + * their lifetime. A key-holder may add new components like subkeys or identities, along with associated + * binding self-signatures to the certificate and old components may expire / get revoked at some point. + * Since any such changes may have an influence on whether a data signature is valid at a given time, or what subkey + * should be used when generating an encrypted / signed message, an API is needed that provides a view on the + * certificate that takes into consideration a relevant window in time. + *

+ * Compared to a {@link PGPPublicKeyRing}, an {@link OpenPGPCertificate} has been evaluated at (or rather for) + * a given evaluation time. It offers a clean API for accessing the key-holder's preferences at a specific + * point in time and makes sure, that relevant self-signatures on certificate components are validated and verified. + * + * @see OpenPGP for Application Developers - Chapter 4 + * for background information on the terminology used in this class. + */ +public class OpenPGPCertificate +{ + private final OpenPGPImplementation implementation; + + private final PGPKeyRing keyRing; + + private final OpenPGPPrimaryKey primaryKey; + private final Map subkeys; + + // Note: get() needs to be accessed with OpenPGPCertificateComponent.getPublicComponent() to ensure + // proper functionality with secret key components. + private final Map componentSignatureChains; + + public OpenPGPCertificate(PGPKeyRing keyRing) + { + this(keyRing, OpenPGPImplementation.getInstance()); + } + + /** + * Instantiate an {@link OpenPGPCertificate} from a parsed {@link PGPPublicKeyRing}. + * + * @param keyRing public key ring + * @param implementation OpenPGP implementation + */ + public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementation) + { + this.implementation = implementation; + + this.keyRing = keyRing; + this.subkeys = new HashMap<>(); + this.componentSignatureChains = new LinkedHashMap<>(); + + Iterator rawKeys = keyRing.getPublicKeys(); + + PGPPublicKey rawPrimaryKey = rawKeys.next(); + this.primaryKey = new OpenPGPPrimaryKey(rawPrimaryKey, this); + processPrimaryKey(primaryKey); + + while (rawKeys.hasNext()) + { + PGPPublicKey rawSubkey = rawKeys.next(); + OpenPGPSubkey subkey = new OpenPGPSubkey(rawSubkey, this); + subkeys.put(new KeyIdentifier(rawSubkey), subkey); + processSubkey(subkey); + } + } + + /** + * Parse an {@link OpenPGPCertificate} (or {@link OpenPGPKey}) from its ASCII armored representation. + * @param armor ASCII armored key or certificate + * @return certificate or key + * @throws IOException + */ + public static OpenPGPCertificate fromAsciiArmor(String armor) + throws IOException + { + return fromAsciiArmor(armor, OpenPGPImplementation.getInstance()); + } + + /** + * Parse an {@link OpenPGPCertificate} (or {@link OpenPGPKey}) from its ASCII armored representation. + * @param armor ASCII armored key or certificate + * @param implementation OpenPGP implementation + * @return certificate or key + * @throws IOException + */ + public static OpenPGPCertificate fromAsciiArmor( + String armor, + OpenPGPImplementation implementation) + throws IOException + { + return fromBytes( + armor.getBytes(StandardCharsets.UTF_8), + implementation); + } + + public static OpenPGPCertificate fromBytes( + byte[] bytes, + OpenPGPImplementation implementation) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + Object object = objectFactory.nextObject(); + + // TODO: Is it dangerous, if we don't explicitly fail upon encountering secret key material here? + // Could it lead to a situation where we need to be cautious with the certificate API design to + // prevent the user from doing dangerous things like accidentally publishing their private key? + + if (object instanceof PGPSecretKeyRing) + { + return new OpenPGPKey((PGPSecretKeyRing) object, implementation); + } + else if (object instanceof PGPPublicKeyRing) + { + return new OpenPGPCertificate((PGPPublicKeyRing) object, implementation); + } + else + { + throw new IOException("Neither a certificate, nor secret key."); + } + } + + + /** + * Return the primary key of the certificate. + * + * @return primary key + */ + public OpenPGPPrimaryKey getPrimaryKey() + { + return primaryKey; + } + + /** + * Return a {@link Map} containing the subkeys of this certificate, keyed by their {@link KeyIdentifier}. + * Note: This map does NOT contain the primary key ({@link #getPrimaryKey()}). + * + * @return subkeys + */ + public Map getSubkeys() + { + return new HashMap<>(subkeys); + } + + /** + * Return a {@link List} containing all {@link OpenPGPCertificateComponent components} of the certificate. + * Components are primary key, subkeys and identities (user-ids, user attributes). + * + * @return list of components + */ + public List getComponents() + { + return new ArrayList<>(componentSignatureChains.keySet()); + } + + /** + * Return all {@link OpenPGPComponentKey OpenPGPComponentKeys} in the certificate. + * The return value is a {@link List} containing the {@link OpenPGPPrimaryKey} and all + * {@link OpenPGPSubkey OpenPGPSubkeys}. + * + * @return list of all component keys + */ + public List getKeys() + { + List keys = new ArrayList<>(); + keys.add(primaryKey); + keys.addAll(subkeys.values()); + return keys; + } + + /** + * Return the {@link OpenPGPComponentKey} identified by the passed in {@link KeyIdentifier}. + * + * @param identifier key identifier + * @return component key + */ + public OpenPGPComponentKey getKey(KeyIdentifier identifier) + { + if (identifier.matches(getPrimaryKey().getPGPPublicKey())) + { + return primaryKey; + } + + return subkeys.get(identifier); + } + + /** + * Return the {@link OpenPGPComponentKey} that likely issued the passed in {@link PGPSignature}. + * + * @param signature signature + * @return issuer (sub-)key + */ + public OpenPGPComponentKey getSigningKeyFor(PGPSignature signature) + { + List keyIdentifiers = signature.getKeyIdentifiers(); + // issuer is primary key + if (KeyIdentifier.matches(keyIdentifiers, getPrimaryKey().getKeyIdentifier(), true)) + { + return primaryKey; + } + + for (KeyIdentifier subkeyIdentifier : subkeys.keySet()) + { + if (KeyIdentifier.matches(keyIdentifiers, subkeyIdentifier, true)) + { + return subkeys.get(subkeyIdentifier); + } + } + + return null; // external issuer + } + + /** + * Return the {@link PGPKeyRing} that this certificate is based on. + * + * @return underlying key ring + */ + public PGPKeyRing getPGPKeyRing() + { + return keyRing; + } + + public PGPPublicKeyRing getPGPPublicKeyRing() + { + if (keyRing instanceof PGPPublicKeyRing) + { + return (PGPPublicKeyRing) keyRing; + } + + List list = new ArrayList<>(); + for (Iterator it = keyRing.getPublicKeys(); it.hasNext(); ) + { + list.add(it.next()); + } + return new PGPPublicKeyRing(list); + } + + public KeyIdentifier getKeyIdentifier() + { + return primaryKey.getKeyIdentifier(); + } + + /** + * Return a list of ALL (sub-)key's identifiers, including those of expired / revoked / unbound keys. + * @return all keys identifiers + */ + public List getAllKeyIdentifiers() + { + List identifiers = new ArrayList<>(); + for (Iterator it = keyRing.getPublicKeys(); it.hasNext(); ) + { + PGPPublicKey key = it.next(); + identifiers.add(key.getKeyIdentifier()); + } + return identifiers; + } + + public static OpenPGPCertificate join(OpenPGPCertificate certificate, String armored) + throws IOException, PGPException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(armored.getBytes()); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream wrapper = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objFac = certificate.implementation.pgpObjectFactory(wrapper); + + Object next; + while ((next = objFac.nextObject()) != null) + { + if (next instanceof PGPPublicKeyRing) + { + PGPPublicKeyRing publicKeys = (PGPPublicKeyRing) next; + OpenPGPCertificate otherCert = new OpenPGPCertificate(publicKeys, certificate.implementation); + try + { + return join(certificate, otherCert); + } + catch (IllegalArgumentException e) + { + // skip over wrong certificate + } + } + + else if (next instanceof PGPSecretKeyRing) + { + + } + + else if (next instanceof PGPSignatureList) + { + // assume there to be primary key (self) signatures + // TODO: Allow consumption of 3rd-party sigs + PGPSignatureList signatures = (PGPSignatureList) next; + + PGPPublicKeyRing publicKeys = certificate.getPGPPublicKeyRing(); + PGPPublicKey primaryKey = publicKeys.getPublicKey(); + for (PGPSignature signature : signatures) + { + primaryKey = PGPPublicKey.addCertification(primaryKey, signature); + } + publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, primaryKey); + return new OpenPGPCertificate(publicKeys, certificate.implementation); + } + } + return null; + } + + public static OpenPGPCertificate join(OpenPGPCertificate certificate, OpenPGPCertificate other) + throws PGPException + { + PGPPublicKeyRing joined = PGPPublicKeyRing.join( + certificate.getPGPPublicKeyRing(), other.getPGPPublicKeyRing()); + return new OpenPGPCertificate(joined, certificate.implementation); + } + + public byte[] getFingerprint() + { + return primaryKey.getPGPPublicKey().getFingerprint(); + } + + public String getPrettyFingerprint() + { + return FingerprintUtil.prettifyFingerprint(getFingerprint()); + } + + public String toAsciiArmoredString() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream.Builder armorBuilder = ArmoredOutputStream.builder() + .clearHeaders(); + // Add fingerprint comment + splitMultilineComment(armorBuilder, getPrettyFingerprint()); + + // Add user-id comments + for (OpenPGPUserId userId : getPrimaryKey().getUserIDs()) + { + ellipsizedComment(armorBuilder, userId.getUserId()); + } + + ArmoredOutputStream aOut = armorBuilder.build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + + // Make sure we export a TPK + List list = new ArrayList<>(); + for (Iterator it = getPGPKeyRing().getPublicKeys(); it.hasNext(); ) + { + list.add(it.next()); + } + PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(list); + + publicKeys.encode(pOut, true); + pOut.close(); + aOut.close(); + return bOut.toString(); + } + + private void splitMultilineComment(ArmoredOutputStream.Builder armorBuilder, String comment) + { + int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len + + comment = comment.trim(); + + while (comment.length() > availableCommentCharsPerLine) + { + // split comment into multiple lines + armorBuilder.addComment(comment.substring(0, availableCommentCharsPerLine)); + comment = comment.substring(availableCommentCharsPerLine).trim(); + } + + if (!comment.isEmpty()) + { + armorBuilder.addComment(comment); + } + } + + private void ellipsizedComment(ArmoredOutputStream.Builder armorBuilder, String comment) + { + int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len + comment = comment.trim(); + + if (comment.length() > availableCommentCharsPerLine) + { + comment = comment.substring(0, availableCommentCharsPerLine - 1) + '…'; + } + armorBuilder.addComment(comment); + } + + protected List fingerprintComments() + { + // TODO: Implement slicing in ArmoredOutputStream.Builder instead? + String prettyPrinted = FingerprintUtil.prettifyFingerprint(getFingerprint()); + + int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len + List slices = new ArrayList<>(); + + while (prettyPrinted.length() > availableCommentCharsPerLine) + { + slices.add(prettyPrinted.substring(0, availableCommentCharsPerLine)); + prettyPrinted = prettyPrinted.substring(availableCommentCharsPerLine).trim(); + } + slices.add(prettyPrinted); + return slices; + } + + private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent component, + OpenPGPComponentKey origin, + Date evaluationDate) + { + // Check if there are signatures at all for the component + OpenPGPSignatureChains chainsForComponent = getAllSignatureChainsFor(component); + if (component == getPrimaryKey() && chainsForComponent.isEmpty()) + { + // If cert has no direct-key signatures, consider UID bindings instead + // TODO: Only consider current primary user id? + for (OpenPGPIdentityComponent identity : getPrimaryKey().identityComponents) + { + chainsForComponent.addAll(getAllSignatureChainsFor(identity)); + } + } + + // Isolate chains which originate from the passed origin key component + OpenPGPSignatureChains fromOrigin = chainsForComponent.fromOrigin(origin); + if (fromOrigin == null) + { + return null; + } + + // Return chain that currently takes precedence + return fromOrigin.getChainAt(evaluationDate); + } + + private OpenPGPSignatureChains getAllSignatureChainsFor(OpenPGPCertificateComponent component) + { + return componentSignatureChains.get(component.getPublicComponent()); + } + + private void processPrimaryKey(OpenPGPPrimaryKey primaryKey) + { + OpenPGPSignatureChains keySignatureChains = new OpenPGPSignatureChains(primaryKey); + List keySignatures = primaryKey.getKeySignatures(); + + // Key Signatures + for (OpenPGPComponentSignature sig : keySignatures) + { + OpenPGPSignatureChain chain = OpenPGPSignatureChain.direct(sig, sig.issuer, primaryKey); + keySignatureChains.add(chain); + } + componentSignatureChains.put(primaryKey, keySignatureChains); + + // Identities + for (OpenPGPIdentityComponent identity : primaryKey.identityComponents) + { + OpenPGPSignatureChains identityChains = new OpenPGPSignatureChains(identity); + List bindings; + + if (identity instanceof OpenPGPUserId) + { + bindings = primaryKey.getUserIdSignatures((OpenPGPUserId) identity); + } + else + { + bindings = primaryKey.getUserAttributeSignatures((OpenPGPUserAttribute) identity); + } + + for (OpenPGPComponentSignature sig : bindings) + { + OpenPGPSignatureChain chain = OpenPGPSignatureChain.direct(sig, sig.getIssuerComponent(), identity); + identityChains.add(chain); + } + componentSignatureChains.put(identity, identityChains); + } + } + + private void processSubkey(OpenPGPSubkey subkey) + { + List bindingSignatures = subkey.getKeySignatures(); + OpenPGPSignatureChains subkeyChains = new OpenPGPSignatureChains(subkey); + + for (OpenPGPComponentSignature sig : bindingSignatures) + { + OpenPGPComponentKey issuer = subkey.getCertificate().getSigningKeyFor(sig.getSignature()); + if (issuer == null) + { + continue; // external key + } + + OpenPGPSignatureChains issuerChains = getAllSignatureChainsFor(issuer); + if (!issuerChains.chains.isEmpty()) + { + for (OpenPGPSignatureChain issuerChain : issuerChains.chains) + { + subkeyChains.add(issuerChain.plus(sig, subkey)); + } + } + else + { + subkeyChains.add(new OpenPGPSignatureChain( + new OpenPGPSignatureChain.Certification(sig, issuer, subkey))); + } + } + this.componentSignatureChains.put(subkey, subkeyChains); + } + + /** + * Return true, if the passed in component is - at evaluation time - properly bound to the certificate. + * + * @param component OpenPGP certificate component + * @param evaluationTime evaluation time + * @return true if component is bound at evaluation time, false otherwise + */ + private boolean isBound(OpenPGPCertificateComponent component, + Date evaluationTime) + { + return isBoundBy(component, getPrimaryKey(), evaluationTime); + } + + /** + * Return true, if the passed in component is - at evaluation time - properly bound to the certificate with + * a signature chain originating at the passed in root component. + * + * @param component OpenPGP certificate component + * @param root root certificate component + * @param evaluationTime evaluation time + * @return true if component is bound at evaluation time, originating at root, false otherwise + */ + private boolean isBoundBy(OpenPGPCertificateComponent component, + OpenPGPComponentKey root, + Date evaluationTime) + { + try + { + OpenPGPSignatureChain chain = getSignatureChainFor(component, root, evaluationTime); + if (chain == null) + { + // Component is not bound at all + return false; + } + + // Chain needs to be valid (signatures correct) + if (chain.isValid(implementation.pgpContentVerifierBuilderProvider())) + { + // Chain needs to not contain a revocation signature, otherwise the component is considered revoked + return !chain.isRevocation(); + } + + // Signature is not correct + return false; + } + catch (PGPException e) + { + // Signature verification failed (signature broken?) + return false; + } + } + + /** + * Return a {@link List} containing all currently marked, valid encryption keys. + * + * @return encryption keys + */ + public List getEncryptionKeys() + { + return getEncryptionKeys(new Date()); + } + + /** + * Return a list of all keys that are - at evaluation time - valid encryption keys. + * + * @param evaluationTime evaluation time + * @return encryption keys + */ + public List getEncryptionKeys(Date evaluationTime) + { + List encryptionKeys = new ArrayList<>(); + + for (OpenPGPComponentKey key : getKeys()) + { + if (!isBound(key, evaluationTime)) + { + // Key is not bound + continue; + } + + if (!key.isEncryptionKey(evaluationTime)) + { + continue; + } + + encryptionKeys.add(key); + } + + return encryptionKeys; + } + + /** + * Return a {@link List} containing all currently valid marked signing keys. + * + * @return list of signing keys + */ + public List getSigningKeys() + { + return getSigningKeys(new Date()); + } + + /** + * Return a list of all keys that - at evaluation time - are validly marked as signing keys. + * + * @param evaluationTime evaluation time + * @return list of signing keys + */ + public List getSigningKeys(Date evaluationTime) + { + List signingKeys = new ArrayList<>(); + + for (OpenPGPComponentKey key : getKeys()) + { + if (!isBound(key, evaluationTime)) + { + // Key is not bound + continue; + } + + if (!key.isSigningKey(evaluationTime)) + { + continue; + } + + signingKeys.add(key); + } + + return signingKeys; + } + + /** + * Return {@link OpenPGPSignatureChains} that contain preference information. + * + * @return signature chain containing certificate-wide preferences (typically DK signature) + */ + private OpenPGPSignatureChain getPreferenceSignature(Date evaluationTime) + { + OpenPGPSignatureChain directKeyBinding = getPrimaryKey().getSignatureChains() + .fromOrigin(getPrimaryKey()) + .getCertificationAt(evaluationTime); + + if (directKeyBinding != null) + { + return directKeyBinding; + } + + List uidBindings = new ArrayList<>(); + for (OpenPGPUserId userId : getPrimaryKey().getUserIDs()) + { + OpenPGPSignatureChain uidBinding = getAllSignatureChainsFor(userId) + .fromOrigin(getPrimaryKey()) + .getCertificationAt(evaluationTime); + + if (uidBinding != null) + { + uidBindings.add(uidBinding); + } + } + + uidBindings.sort(Comparator.comparing(OpenPGPSignatureChain::getSince).reversed()); + for (OpenPGPSignatureChain binding : uidBindings) + { + PGPSignature sig = binding.getHeadLink().getSignature().getSignature(); + if (sig.getHashedSubPackets().isPrimaryUserID()) + { + return binding; + } + } + + return uidBindings.isEmpty() ? null : uidBindings.get(0); + } + + public List getIdentities() + { + return new ArrayList<>(primaryKey.identityComponents); + } + + /** + * Component on an OpenPGP certificate. + * Components can either be {@link OpenPGPComponentKey keys} or {@link OpenPGPIdentityComponent identities}. + */ + public static abstract class OpenPGPCertificateComponent + { + private final OpenPGPCertificate certificate; + + public OpenPGPCertificateComponent(OpenPGPCertificate certificate) + { + this.certificate = certificate; + } + + /** + * Return this components {@link OpenPGPCertificate}. + * + * @return certificate + */ + public OpenPGPCertificate getCertificate() + { + return certificate; + } + + /** + * Return a detailed String representation of this component. + * + * @return detailed String representation + */ + public abstract String toDetailString(); + + /** + * Return true, if this component is - at evaluation time - properly bound to its certificate. + * + * @param evaluationTime evaluation time + * @return true if bound, false otherwise + */ + public boolean isBoundAt(Date evaluationTime) + { + return getCertificate().isBound(this, evaluationTime); + } + + /** + * Return all {@link OpenPGPSignatureChains} that bind this component. + * + * @return signature chains + */ + public OpenPGPSignatureChains getSignatureChains() + { + return getCertificate().getAllSignatureChainsFor(this); + } + + /** + * Return the public {@link OpenPGPCertificateComponent} that belongs to this component. + * For public components (pubkeys, identities...), that's simply this, while secret components + * return their corresponding public component. + * This is used to properly map secret key and public key components in {@link Map Maps} that use + * {@link OpenPGPCertificateComponent components} as map keys. + * + * @return public certificate component + */ + protected OpenPGPCertificateComponent getPublicComponent() + { + return this; + } + } + + /** + * OpenPGP Signature made over some {@link OpenPGPCertificateComponent} on a {@link OpenPGPCertificate}. + */ + public static class OpenPGPComponentSignature + extends OpenPGPSignature + { + + private final OpenPGPCertificateComponent target; + + /** + * Component signature. + * @param signature signature + * @param issuer key that issued the signature. + * Is nullable (e.g. for 3rd party sigs where the certificate is not available). + * @param target signed certificate component + */ + public OpenPGPComponentSignature(PGPSignature signature, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent target) + { + super(signature, issuer); + this.target = target; + } + + /** + * Return the {@link OpenPGPComponentKey} that issued this signature. + * + * @return issuer + */ + public OpenPGPComponentKey getIssuerComponent() + { + return getIssuer(); + } + + /** + * Return the {@link OpenPGPCertificateComponent} that this signature was calculated over. + * + * @return target + */ + public OpenPGPCertificateComponent getTargetComponent() + { + return target; + } + + /** + * Return the {@link OpenPGPComponentKey} that this signature is calculated over. + * Contrary to {@link #getTargetComponent()}, which returns the actual target, this method returns the + * {@link OpenPGPComponentKey} "closest" to the target. + * For a subkey-binding signature, this is the target subkey, while for an identity-binding signature + * (binding for a user-id or attribute) the return value is the {@link OpenPGPComponentKey} which + * carries the identity. + * + * @return target key component of the signature + */ + public OpenPGPComponentKey getTargetKeyComponent() + { + if (getTargetComponent() instanceof OpenPGPIdentityComponent) + { + // Identity signatures indirectly authenticate the primary key + return ((OpenPGPIdentityComponent) getTargetComponent()).getPrimaryKey(); + } + if (getTargetComponent() instanceof OpenPGPComponentKey) + { + // Key signatures authenticate the target key + return (OpenPGPComponentKey) getTargetComponent(); + } + throw new IllegalArgumentException("Unknown target type."); + } + + /** + * Verify this signature. + * + * @param contentVerifierBuilderProvider provider for verifiers + * @throws PGPSignatureException if the signature cannot be verified successfully + */ + public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + if (issuer == null) + { + // No issuer available + throw new MissingIssuerCertException("Issuer certificate unavailable."); + } + + sanitize(issuer); + + // Direct-Key signature + if (target == issuer) + { + verifyKeySignature( + issuer, + issuer, + contentVerifierBuilderProvider); + } + + // Subkey binding signature + else if (target instanceof OpenPGPSubkey) + { + verifyKeySignature( + issuer, + (OpenPGPSubkey) target, + contentVerifierBuilderProvider); + } + + // User-ID binding + else if (target instanceof OpenPGPUserId) + { + verifyUserIdSignature( + issuer, + (OpenPGPUserId) target, + contentVerifierBuilderProvider); + } + + // User-Attribute binding + else if (target instanceof OpenPGPUserAttribute) + { + verifyUserAttributeSignature( + issuer, + (OpenPGPUserAttribute) target, + contentVerifierBuilderProvider); + } + + else + { + throw new PGPSignatureException("Unexpected signature type: " + getType()); + } + } + + public void verifyKeySignature(OpenPGPComponentKey issuer, + OpenPGPComponentKey target, + PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + this.isTested = true; + try + { + signature.init(contentVerifierBuilderProvider, issuer.getPGPPublicKey()); + if (issuer == target) + { + // Direct-Key Signature + isCorrect = signature.verifyCertification(target.getPGPPublicKey()); + } + else + { + // Subkey Binding Signature + isCorrect = signature.verifyCertification(issuer.getPGPPublicKey(), target.getPGPPublicKey()); + } + + if (!isCorrect) + { + throw new IncorrectPGPSignatureException("Key Signature is not correct."); + } + } + catch (PGPException e) + { + this.isCorrect = false; + throw new PGPSignatureException("Key Signature could not be verified.", e); + } + } + + public void verifyUserIdSignature(OpenPGPComponentKey issuer, + OpenPGPUserId target, + PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + this.isTested = true; + try + { + signature.init(contentVerifierBuilderProvider, issuer.getPGPPublicKey()); + isCorrect = signature.verifyCertification(target.getUserId(), target.getPrimaryKey().getPGPPublicKey()); + if (!isCorrect) + { + throw new IncorrectPGPSignatureException("UserID Signature is not correct."); + } + } + catch (PGPException e) + { + this.isCorrect = false; + throw new PGPSignatureException("UserID Signature could not be verified.", e); + } + } + + public void verifyUserAttributeSignature(OpenPGPComponentKey issuer, + OpenPGPUserAttribute target, + PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + this.isTested = true; + try + { + signature.init(contentVerifierBuilderProvider, issuer.getPGPPublicKey()); + isCorrect = signature.verifyCertification(target.getUserAttribute(), target.getPrimaryKey().getPGPPublicKey()); + if (!isCorrect) + { + throw new IncorrectPGPSignatureException("UserAttribute Signature is not correct."); + } + } + catch (PGPException e) + { + this.isCorrect = false; + throw new PGPSignatureException("Could not verify UserAttribute Signature.", e); + } + } + + @Override + protected String getTargetDisplay() + { + return target.toString(); + } + } + + /** + * A component key is either an {@link OpenPGPPrimaryKey}, or an {@link OpenPGPSubkey}. + * + * @see + * OpenPGP for Application Developers - Layers of keys in OpenPGP + */ + public static abstract class OpenPGPComponentKey + extends OpenPGPCertificateComponent + { + protected final PGPPublicKey rawPubkey; + + /** + * Constructor. + * @param rawPubkey public key + * @param certificate certificate + */ + public OpenPGPComponentKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) + { + super(certificate); + this.rawPubkey = rawPubkey; + } + + public PGPPublicKey getPGPPublicKey() + { + return rawPubkey; + } + + /** + * Return the {@link KeyIdentifier} of this key. + * + * @return key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(rawPubkey); + } + + /** + * Return the creation time of this key. + * + * @return creation time + */ + public Date getCreationTime() + { + return rawPubkey.getCreationTime(); + } + + /** + * Return true, if the key is currently marked as encryption key. + * + * @return true if the key is an encryption key, false otherwise + */ + public boolean isEncryptionKey() + { + return isEncryptionKey(new Date()); + } + + /** + * Return true, if the is - at evaluation time - marked as an encryption key. + * + * @param evaluationTime evaluation time + * @return true if key is an encryption key at evaluation time, false otherwise + */ + public boolean isEncryptionKey(Date evaluationTime) + { + if (!rawPubkey.isEncryptionKey()) + { + // Skip keys that are not encryption-capable by algorithm + return false; + } + + KeyFlags keyFlags = getKeyFlags(evaluationTime); + if (keyFlags == null) + { + return false; + } + + int flags = keyFlags.getFlags(); + return (flags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS || + (flags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE; + } + + /** + * Return true, if the key is currently marked as a signing key for message signing. + * + * @return true, if key is currently signing key + */ + public boolean isSigningKey() + { + return isSigningKey(new Date()); + } + + /** + * Return true, if the key is - at evaluation time - marked as signing key for message signing. + * + * @param evaluationTime evaluation time + * @return true if key is signing key at evaluation time + */ + public boolean isSigningKey(Date evaluationTime) + { + // TODO: Replace with https://github.com/bcgit/bc-java/pull/1857/files#diff-36f593d586240aec2546daad96d16b5debd3463202a3d5d82c0b2694572c8426R14-R30 + int alg = rawPubkey.getAlgorithm(); + if (alg != PublicKeyAlgorithmTags.RSA_GENERAL && + alg != PublicKeyAlgorithmTags.RSA_SIGN && + alg != PublicKeyAlgorithmTags.DSA && + alg != PublicKeyAlgorithmTags.ECDSA && + alg != PublicKeyAlgorithmTags.EDDSA_LEGACY && + alg != PublicKeyAlgorithmTags.Ed25519 && + alg != PublicKeyAlgorithmTags.Ed448) + { + // Key is not signing-capable by algorithm + return false; + } + + KeyFlags keyFlags = getKeyFlags(evaluationTime); + if (keyFlags == null) + { + return false; + } + + int flags = keyFlags.getFlags(); + return (flags & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA; + } + + /** + * Return true, if the key is currently marked as certification key that can sign 3rd-party certificates. + * + * @return true, if key is certification key + */ + public boolean isCertificationKey() + { + return isCertificationKey(new Date()); + } + + /** + * Return true, if the key is - at evaluation time - marked as certification key that can sign 3rd-party + * certificates. + * + * @param evaluationTime evaluation time + * @return true if key is certification key at evaluation time + */ + public boolean isCertificationKey(Date evaluationTime) + { + // TODO: Replace with https://github.com/bcgit/bc-java/pull/1857/files#diff-36f593d586240aec2546daad96d16b5debd3463202a3d5d82c0b2694572c8426R14-R30 + int alg = rawPubkey.getAlgorithm(); + if (alg != PublicKeyAlgorithmTags.RSA_GENERAL && + alg != PublicKeyAlgorithmTags.RSA_SIGN && + alg != PublicKeyAlgorithmTags.DSA && + alg != PublicKeyAlgorithmTags.ECDSA && + alg != PublicKeyAlgorithmTags.EDDSA_LEGACY && + alg != PublicKeyAlgorithmTags.Ed25519 && + alg != PublicKeyAlgorithmTags.Ed448) + { + // Key is not signing-capable by algorithm + return false; + } + + KeyFlags keyFlags = getKeyFlags(evaluationTime); + if (keyFlags == null) + { + return false; + } + + int flags = keyFlags.getFlags(); + return (flags & KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER; + } + + /** + * Return the {@link KeyFlags} signature subpacket that currently applies to the key. + * @return key flags subpacket + */ + public KeyFlags getKeyFlags() + { + return getKeyFlags(new Date()); + } + + /** + * Return the {@link KeyFlags} signature subpacket that - at evaluation time - applies to the key. + * @param evaluationTime evaluation time + * @return key flags subpacket + */ + public KeyFlags getKeyFlags(Date evaluationTime) + { + SignatureSubpacket subpacket = getApplyingSubpacket( + evaluationTime, SignatureSubpacketTags.KEY_FLAGS); + if (subpacket != null) + { + return (KeyFlags) subpacket; + } + return null; + } + + /** + * Return the {@link Features} signature subpacket that currently applies to the key. + * @return feature signature subpacket + */ + public Features getFeatures() + { + return getFeatures(new Date()); + } + + /** + * Return the {@link Features} signature subpacket that - at evaluation time - applies to the key. + * @param evaluationTime evaluation time + * @return features subpacket + */ + public Features getFeatures(Date evaluationTime) + { + SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.FEATURES); + if (subpacket != null) + { + return (Features) subpacket; + } + return null; + } + + /** + * Return the {@link SignatureSubpacket} instance of the given subpacketType, which currently applies to + * the key. Since subpackets from the Direct-Key signature apply to all subkeys of a certificate, + * this method first inspects the signature that immediately applies to this key (e.g. a subkey-binding + * signature), and - if the queried subpacket is found in there, returns that instance. + * Otherwise, indirectly applying signatures (e.g. Direct-Key signatures) are queried. + * That way, preferences from the direct-key signature are considered, but per-key overwrites take precedence. + * + * @see + * OpenPGP for application developers - Attribute Shadowing + * + * @param evaluationTime evaluation time + * @param subpacketType subpacket type that is being searched for + * @return subpacket from directly or indirectly applying signature + */ + protected SignatureSubpacket getApplyingSubpacket(Date evaluationTime, int subpacketType) + { + OpenPGPSignatureChain binding = getSignatureChains().getCertificationAt(evaluationTime); + if (binding == null) + { + // is not bound + return null; + } + + // Check signatures + try + { + if (!binding.isValid()) + { + // Binding is incorrect + return null; + } + } + catch (PGPSignatureException e) + { + // Binding cannot be verified + return null; + } + + // find signature "closest to the key", e.g. subkey binding signature + OpenPGPComponentSignature keySignature = binding.getHeadLink().getSignature(); + + PGPSignatureSubpacketVector hashedSubpackets = keySignature.getSignature().getHashedSubPackets(); + if (hashedSubpackets == null || !hashedSubpackets.hasSubpacket(subpacketType)) + { + // If the subkey binding signature doesn't carry the desired subpacket, + // check direct-key or primary uid sig instead + OpenPGPSignatureChain preferenceBinding = getCertificate().getPreferenceSignature(evaluationTime); + if (preferenceBinding == null) + { + // No direct-key / primary uid sig found -> No subpacket + return null; + } + hashedSubpackets = preferenceBinding.getHeadLink().getSignature().getSignature().getHashedSubPackets(); + } + // else -> attribute from DK sig is shadowed by SB sig + + // Extract subpacket from hashed area + return hashedSubpackets.getSubpacket(subpacketType); + } + + public PreferredAEADCiphersuites getAEADCipherSuitePreferences() + { + return getAEADCipherSuitePreferences(new Date()); + } + + public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTime) + { + SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, + SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + if (subpacket != null) + { + return (PreferredAEADCiphersuites) subpacket; + } + return null; + } + + public PreferredAlgorithms getSymmetricCipherPreferences() + { + return getSymmetricCipherPreferences(new Date()); + } + + public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) + { + SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); + if (subpacket != null) + { + return (PreferredAlgorithms) subpacket; + } + return null; + } + + public PreferredAlgorithms getHashAlgorithmPreferences() + { + return getHashAlgorithmPreferences(new Date()); + } + + public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) + { + SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); + if (subpacket != null) + { + return (PreferredAlgorithms) subpacket; + } + return null; + } + } + + /** + * The primary key of a {@link OpenPGPCertificate}. + */ + public static class OpenPGPPrimaryKey + extends OpenPGPComponentKey + { + @Override + public String toString() + { + return "PrimaryKey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase() + "]"; + } + + @Override + public String toDetailString() + { + return "PrimaryKey[" + getKeyIdentifier() + "] (" + UTCUtil.format(getCreationTime()) + ")"; + } + + protected final List identityComponents; + + public OpenPGPPrimaryKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) + { + super(rawPubkey, certificate); + this.identityComponents = new ArrayList<>(); + + Iterator userIds = rawPubkey.getUserIDs(); + while (userIds.hasNext()) + { + identityComponents.add(new OpenPGPUserId(userIds.next(), this)); + } + + Iterator userAttributes = rawPubkey.getUserAttributes(); + while (userAttributes.hasNext()) + { + identityComponents.add(new OpenPGPUserAttribute(userAttributes.next(), this)); + } + } + + /** + * Return all {@link OpenPGPUserId OpenPGPUserIds} on this key. + * + * @return user ids + */ + public List getUserIDs() + { + List userIds = new ArrayList<>(); + for (OpenPGPIdentityComponent identity : identityComponents) + { + if (identity instanceof OpenPGPUserId) + { + userIds.add((OpenPGPUserId) identity); + } + } + return userIds; + } + + /** + * Return all {@link OpenPGPUserAttribute OpenPGPUserAttributes} on this key. + * + * @return user attributes + */ + public List getUserAttributes() + { + List userAttributes = new ArrayList<>(); + for (OpenPGPIdentityComponent identity : identityComponents) + { + if (identity instanceof OpenPGPUserAttribute) + { + userAttributes.add((OpenPGPUserAttribute) identity); + } + } + return userAttributes; + } + + protected List getKeySignatures() + { + Iterator iterator = rawPubkey.getSignatures(); + List list = new ArrayList<>(); + while (iterator.hasNext()) + { + PGPSignature sig = iterator.next(); + int type = sig.getSignatureType(); + if (type != PGPSignature.DIRECT_KEY && type != PGPSignature.KEY_REVOCATION) + { + continue; + } + // try to find issuer for self-signature + OpenPGPCertificate.OpenPGPComponentKey issuer = getCertificate() + .getSigningKeyFor(sig); + + list.add(new OpenPGPCertificate.OpenPGPComponentSignature(sig, issuer, this)); + } + return list; + } + + protected List getUserIdSignatures(OpenPGPUserId identity) + { + Iterator iterator = rawPubkey.getSignaturesForID(identity.getUserId()); + List list = new ArrayList<>(); + while (iterator.hasNext()) + { + PGPSignature sig = iterator.next(); + // try to find issuer for self-signature + OpenPGPCertificate.OpenPGPComponentKey issuer = getCertificate() + .getSigningKeyFor(sig); + + list.add(new OpenPGPCertificate.OpenPGPComponentSignature(sig, issuer, identity)); + } + return list; + } + + protected List getUserAttributeSignatures(OpenPGPUserAttribute identity) + { + Iterator iterator = rawPubkey.getSignaturesForUserAttribute(identity.getUserAttribute()); + List list = new ArrayList<>(); + while (iterator.hasNext()) + { + PGPSignature sig = iterator.next(); + // try to find issuer for self-signature + OpenPGPCertificate.OpenPGPComponentKey issuer = getCertificate() + .getSigningKeyFor(sig); + + list.add(new OpenPGPCertificate.OpenPGPComponentSignature(sig, issuer, identity)); + } + return list; + } + } + + /** + * A subkey on a {@link OpenPGPCertificate}. + */ + public static class OpenPGPSubkey + extends OpenPGPComponentKey + { + public OpenPGPSubkey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) + { + super(rawPubkey, certificate); + } + + @Override + public String toString() + { + return "Subkey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase() + "]"; + } + + @Override + public String toDetailString() + { + return "Subkey[" + getKeyIdentifier() + "] (" + UTCUtil.format(getCreationTime()) + ")"; + } + + protected List getKeySignatures() + { + Iterator iterator = rawPubkey.getSignatures(); + List list = new ArrayList<>(); + while (iterator.hasNext()) + { + PGPSignature sig = iterator.next(); + int type = sig.getSignatureType(); + if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) + { + continue; + } + // try to find issuer for self-signature + OpenPGPCertificate.OpenPGPComponentKey issuer = getCertificate() + .getSigningKeyFor(sig); + + list.add(new OpenPGPCertificate.OpenPGPComponentSignature(sig, issuer, this)); + } + return list; + } + } + + /** + * An identity bound to the {@link OpenPGPPrimaryKey} of a {@link OpenPGPCertificate}. + * An identity may either be a {@link OpenPGPUserId} or (deprecated) {@link OpenPGPUserAttribute}. + */ + public static abstract class OpenPGPIdentityComponent + extends OpenPGPCertificateComponent + { + private final OpenPGPPrimaryKey primaryKey; + + public OpenPGPIdentityComponent(OpenPGPPrimaryKey primaryKey) + { + super(primaryKey.getCertificate()); + this.primaryKey = primaryKey; + } + + public OpenPGPPrimaryKey getPrimaryKey() + { + return primaryKey; + } + + @Override + public String toDetailString() + { + return toString(); + } + } + + /** + * A UserId. + */ + public static class OpenPGPUserId + extends OpenPGPIdentityComponent + { + private final String userId; + + public OpenPGPUserId(String userId, OpenPGPPrimaryKey primaryKey) + { + super(primaryKey); + this.userId = userId; + } + + public String getUserId() + { + return userId; + } + + @Override + public String toString() + { + return "UserID[" + userId + "]"; + } + } + + /** + * A UserAttribute. + * Use of UserAttributes is deprecated in RFC9580. + */ + public static class OpenPGPUserAttribute + extends OpenPGPIdentityComponent + { + + private final PGPUserAttributeSubpacketVector userAttribute; + + public OpenPGPUserAttribute(PGPUserAttributeSubpacketVector userAttribute, OpenPGPPrimaryKey primaryKey) + { + super(primaryKey); + this.userAttribute = userAttribute; + } + + public PGPUserAttributeSubpacketVector getUserAttribute() + { + return userAttribute; + } + + @Override + public String toString() + { + return "UserAttribute" + userAttribute.toString(); + } + } + + /** + * Chain of {@link OpenPGPSignature signatures}. + * Such a chain originates from a certificates primary key and points towards some certificate component that + * is bound to the certificate. + * As for example a subkey can only be bound by a primary key that holds either at least one + * direct-key self-signature or at least one user-id binding signature, multiple signatures may form + * a validity chain. + * An {@link OpenPGPSignatureChain} can either be a certification + * ({@link #isCertification()}), e.g. it represents a positive binding, + * or it can be a revocation ({@link #isRevocation()}) which invalidates a positive binding. + */ + public static class OpenPGPSignatureChain + implements Comparable, Iterable + { + private final List chainLinks = new ArrayList<>(); + + private OpenPGPSignatureChain(Link rootLink) + { + this.chainLinks.add(rootLink); + } + + // copy constructor + private OpenPGPSignatureChain(OpenPGPSignatureChain copy) + { + this.chainLinks.addAll(copy.chainLinks); + } + + /** + * Return an NEW instance of the {@link OpenPGPSignatureChain} with the new link appended. + * @param sig signature + * @param targetComponent signature target + * @return new instance + */ + public OpenPGPSignatureChain plus(OpenPGPComponentSignature sig, + OpenPGPCertificateComponent targetComponent) + { + if (getHeadKey() != sig.getIssuerComponent()) + { + throw new IllegalArgumentException("Chain head is not equal to link issuer."); + } + + OpenPGPSignatureChain chain = new OpenPGPSignatureChain(this); + + chain.chainLinks.add(Link.create(sig, sig.getIssuerComponent(), targetComponent)); + + return chain; + } + + public static OpenPGPSignatureChain direct(OpenPGPComponentSignature sig, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent targetComponent) + { + return new OpenPGPSignatureChain(Link.create(sig, issuer, targetComponent)); + } + + public Link getRootLink() + { + return chainLinks.get(0); + } + + public OpenPGPComponentKey getRootKey() + { + return getRootLink().issuer; + } + + public Link getHeadLink() + { + return chainLinks.get(chainLinks.size() - 1); + } + + public OpenPGPComponentKey getHeadKey() + { + return getHeadLink().signature.getTargetKeyComponent(); + } + + public boolean isCertification() + { + for (Link link : chainLinks) + { + if (link instanceof Revocation) + { + return false; + } + } + return true; + } + + public boolean isRevocation() + { + for (Link link : chainLinks) + { + if (link instanceof Revocation) + { + return true; + } + } + return false; + } + + public boolean isHardRevocation() + { + for (Link link : chainLinks) + { + if (link.signature.signature.isHardRevocation()) + { + return true; + } + } + return false; + } + + /** + * Return the date since which this signature chain is valid. + * This is the creation time of the most recent link in the chain. + * + * @return most recent signature creation time + */ + public Date getSince() + { + // Find most recent chain link + return chainLinks.stream() + .map(it -> it.signature) + .max(Comparator.comparing(OpenPGPComponentSignature::getCreationTime)) + .map(OpenPGPComponentSignature::getCreationTime) + .orElse(null); + } + + /** + * Return the date until which the chain link is valid. + * This is the earliest expiration time of any signature in the chain. + * + * @return earliest expiration time + */ + public Date getUntil() + { + Date soonestExpiration = null; + for (Link link : chainLinks) + { + Date until = link.until(); + if (until != null) + { + soonestExpiration = (soonestExpiration == null) ? until : + (until.before(soonestExpiration) ? until : soonestExpiration); + } + } + return soonestExpiration; + } + + public boolean isEffectiveAt(Date evaluationDate) + { + if (isHardRevocation()) + { + return true; + } + Date since = getSince(); + Date until = getUntil(); + return !evaluationDate.before(since) && (until == null || evaluationDate.before(until)); + } + + public boolean isValid() + throws PGPSignatureException + { + return isValid(getRootKey().getCertificate().implementation.pgpContentVerifierBuilderProvider()); + } + + public boolean isValid(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + boolean correct = true; + for (Link link : chainLinks) + { + if (!link.signature.isTested) + { + link.verify(contentVerifierBuilderProvider); + } + + if (!link.signature.isCorrect) + { + correct = false; + } + } + return correct; + } + + @Override + public String toString() + { + StringBuilder b = new StringBuilder(); + String until = getUntil() == null ? "EndOfTime" : UTCUtil.format(getUntil()); + b.append("From ").append(UTCUtil.format(getSince())).append(" until ").append(until).append("\n"); + for (Link link : chainLinks) + { + b.append(" ").append(link.toString()).append("\n"); + } + return b.toString(); + } + + @Override + public int compareTo(OpenPGPSignatureChain other) + { + if (isHardRevocation()) + { + return -1; + } + + if (other.isHardRevocation()) + { + return 1; + } + + return -getSince().compareTo(other.getSince()); + } + + @Override + public Iterator iterator() + { + return chainLinks.iterator(); + } + + /** + * Link in a {@link OpenPGPSignatureChain}. + */ + public static abstract class Link + { + protected final OpenPGPComponentSignature signature; + protected final OpenPGPComponentKey issuer; + protected final OpenPGPCertificateComponent target; + + public Link(OpenPGPComponentSignature signature, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent target) + { + this.signature = signature; + this.issuer = issuer; + this.target = target; + } + + public Date since() + { + return signature.getCreationTime(); + } + + public Date until() + { + return signature.getExpirationTime(); + } + + public boolean verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + throws PGPSignatureException + { + signature.verify(contentVerifierBuilderProvider); + return true; + } + + @Override + public String toString() + { + return signature.toString(); + } + + public static Link create(OpenPGPComponentSignature signature, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent target) + { + if (signature.isRevocation()) + { + return new Revocation(signature, issuer, target); + } + else + { + return new Certification(signature, issuer, target); + } + } + + public OpenPGPComponentSignature getSignature() + { + return signature; + } + } + + /** + * "Positive" signature chain link. + */ + public static class Certification + extends Link + { + /** + * Positive certification. + * + * @param signature signature + * @param issuer key that issued the certification. + * Is nullable (e.g. for 3rd-party sigs where the cert is not available) + * @param target signed certificate component + */ + public Certification(OpenPGPComponentSignature signature, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent target) + { + super(signature, issuer, target); + } + } + + /** + * "Negative" signature chain link. + */ + public static class Revocation + extends Link + { + /** + * Revocation. + * + * @param signature signature + * @param issuer key that issued the revocation. + * Is nullable (e.g. for 3rd-party sigs where the cert is not available) + * @param target revoked certification component + */ + public Revocation(OpenPGPComponentSignature signature, + OpenPGPComponentKey issuer, + OpenPGPCertificateComponent target) + { + super(signature, issuer, target); + } + + @Override + public Date since() + { + if (signature.signature.isHardRevocation()) + { + return new Date(0L); + } + return super.since(); + } + + @Override + public Date until() + { + if (signature.signature.isHardRevocation()) + { + return new Date(Long.MAX_VALUE); + } + return super.until(); + } + } + } + + /** + * Collection of multiple {@link OpenPGPSignatureChain} objects. + */ + public static class OpenPGPSignatureChains implements Iterable + { + private final OpenPGPCertificateComponent targetComponent; + private final Set chains = new TreeSet<>(); + + public OpenPGPSignatureChains(OpenPGPCertificateComponent component) + { + this.targetComponent = component; + } + + /** + * Add a single chain to the collection. + * @param chain chain + */ + public void add(OpenPGPSignatureChain chain) + { + this.chains.add(chain); + } + + public void addAll(OpenPGPSignatureChains otherChains) + { + this.chains.addAll(otherChains.chains); + } + + public boolean isEmpty() + { + return chains.isEmpty(); + } + + /** + * Return a positive certification chain for the component for the given evaluationTime. + * @param evaluationTime time for which validity of the {@link OpenPGPCertificateComponent} is checked. + * @return positive certification chain or null + */ + public OpenPGPSignatureChain getCertificationAt(Date evaluationTime) + { + for (OpenPGPSignatureChain chain : chains) + { + boolean isEffective = chain.isEffectiveAt(evaluationTime); + boolean isCertification = chain.isCertification(); + if (isEffective && isCertification) + { + return chain; + } + } + return null; + } + + public OpenPGPSignatureChains getChainsAt(Date evaluationTime) + { + OpenPGPSignatureChains effectiveChains = new OpenPGPSignatureChains(targetComponent); + for (OpenPGPSignatureChain chain : chains) + { + if (chain.isEffectiveAt(evaluationTime)) + { + effectiveChains.add(chain); + } + } + return effectiveChains; + } + + /** + * Return a negative certification chain for the component for the given evaluationTime. + * @param evaluationTime time for which revocation-ness of the {@link OpenPGPCertificateComponent} is checked. + * @return negative certification chain or null + */ + public OpenPGPSignatureChain getRevocationAt(Date evaluationTime) + { + for (OpenPGPSignatureChain chain : chains) + { + if (!chain.isRevocation()) + { + continue; + } + + if (chain.isEffectiveAt(evaluationTime)) + { + return chain; + } + } + return null; + } + + @Override + public String toString() + { + StringBuilder b = new StringBuilder(targetComponent.toDetailString()) + .append(" is bound with ").append(chains.size()).append(" chains:").append("\n"); + for (OpenPGPSignatureChain chain : chains) + { + b.append(chain.toString()); + } + return b.toString(); + } + + public OpenPGPSignatureChains fromOrigin(OpenPGPComponentKey root) + { + OpenPGPSignatureChains chainsFromRoot = new OpenPGPSignatureChains(root); + for (OpenPGPSignatureChain chain : chains) + { + OpenPGPComponentKey chainRoot = chain.getRootKey(); + if (chainRoot == root) + { + chainsFromRoot.add(chain); + } + } + return chainsFromRoot; + } + + public OpenPGPSignatureChain getChainAt(Date evaluationDate) + { + OpenPGPSignatureChains atDate = getChainsAt(evaluationDate); + Iterator it = atDate.chains.iterator(); + if (it.hasNext()) + { + return it.next(); + } + return null; + } + + @Override + public Iterator iterator() + { + return chains.iterator(); + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java new file mode 100644 index 0000000000..94e96021b4 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -0,0 +1,183 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; + +import java.io.InputStream; + +/** + * Bouncy Castle provides two implementations of OpenPGP operators. + * The

JCA/JCE
implementation makes use of Java Cryptography Architecture and the + * Java Cryptography Extension, while
Bc
uses Bouncy Castles Lightweight Cryptography API. + * The purpose of {@link OpenPGPImplementation} is to define a shared interface for instantiating concrete + * objects of either API. + * It is advised to define the desired implementation by calling {@link #setInstance(OpenPGPImplementation)} and + * acquiring it via {@link #getInstance()}, as swapping out the entire implementation can then be done by + * replacing the instance in one single place. + * This pattern was successfully explored by PGPainless. + */ +public abstract class OpenPGPImplementation +{ + private static OpenPGPImplementation INSTANCE; + + /** + * Replace the {@link OpenPGPImplementation} instance that is returned by {@link #getInstance()}. + * @param implementation instance + */ + public static void setInstance(OpenPGPImplementation implementation) + { + INSTANCE = implementation; + } + + /** + * Return the currently set {@link OpenPGPImplementation} instance. + * The default is {@link BcOpenPGPImplementation}. + * + * @return instance + */ + public static OpenPGPImplementation getInstance() + { + if (INSTANCE == null) + { + setInstance(new BcOpenPGPImplementation()); + } + return INSTANCE; + } + + /** + * Return an instance of {@link PGPObjectFactory} based on the given {@link InputStream}. + * + * @param packetInputStream packet input stream + * @return object factory + */ + public abstract PGPObjectFactory pgpObjectFactory(InputStream packetInputStream); + + /** + * Return an instance of {@link PGPContentVerifierBuilderProvider} which is responsible for providing + * implementations needed for signature verification. + * + * @return content verifier builder provider + */ + public abstract PGPContentVerifierBuilderProvider pgpContentVerifierBuilderProvider(); + + /** + * Return an instance of {@link PBESecretKeyDecryptorBuilderProvider} which is responsible for providing + * implementations needed for secret key unlocking. + * + * @return secret key decryptor builder provider + */ + public abstract PBESecretKeyDecryptorBuilderProvider pbeSecretKeyDecryptorBuilderProvider(); + + /** + * Return an instance of {@link PGPDataEncryptorBuilder} which is responsible for providing implementations + * needed for creating encrypted data packets. + * + * @param symmetricKeyAlgorithm symmetric encryption algorithm + * @return data encryptor builder + */ + public abstract PGPDataEncryptorBuilder pgpDataEncryptorBuilder( + int symmetricKeyAlgorithm); + + /** + * Return an instance of {@link PublicKeyKeyEncryptionMethodGenerator} which is responsible for + * creating public-key-based encryptors for OpenPGP messages. + * Public-key-based encryptors are used when a message is encrypted for a recipients public key. + * + * @param encryptionSubkey subkey for which a message shall be encrypted + * @return public-key key-encryption method generator + */ + public abstract PublicKeyKeyEncryptionMethodGenerator publicKeyKeyEncryptionMethodGenerator( + PGPPublicKey encryptionSubkey); + + /** + * Return an instance of {@link PBEKeyEncryptionMethodGenerator} which is responsible for creating + * symmetric-key-based encryptors for OpenPGP messages, using {@link S2K#SALTED_AND_ITERATED} mode. + * Symmetric-key-based encryptors are used when a message is encrypted using a passphrase. + * + * @param messagePassphrase passphrase to encrypt the message with + * @return pbe key encryption method generator + */ + public abstract PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator( + char[] messagePassphrase); + + /** + * Return an instance of {@link PBEKeyEncryptionMethodGenerator} which is responsible for creating + * symmetric-key-based encryptors for OpenPGP messages, using {@link S2K#ARGON_2} mode. + * Symmetric-key-based encryptors are used when a message is encrypted using a passphrase. + * + * @param messagePassphrase passphrase to encrypt the message with + * @param argon2Params parameters for the Argon2 hash function + * @return pbe key encryption method generator + */ + public abstract PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator( + char[] messagePassphrase, + S2K.Argon2Params argon2Params); + + /** + * Return an instance of {@link PGPContentSignerBuilder}, which is responsible for providing concrete + * implementations needed for signature creation. + * + * @param publicKeyAlgorithm the signing-keys public-key algorithm + * @param hashAlgorithm signature hash algorithm + * @return content signer builder + */ + public abstract PGPContentSignerBuilder pgpContentSignerBuilder( + int publicKeyAlgorithm, + int hashAlgorithm); + + /** + * Return an instance of the {@link PBEDataDecryptorFactory}, which is responsible for providing concrete + * implementations needed to decrypt OpenPGP messages that were encrypted symmetrically with a passphrase. + * + * @param messagePassphrase message passphrase + * @return pbe data decryptor factory + * @throws PGPException if the factory cannot be instantiated + */ + public abstract PBEDataDecryptorFactory pbeDataDecryptorFactory( + char[] messagePassphrase) + throws PGPException; + + /** + * Return an instance of the {@link SessionKeyDataDecryptorFactory}, which is responsible for providing + * concrete implementations needed to decrypt OpenPGP messages using a {@link PGPSessionKey}. + * + * @param sessionKey session key + * @return session-key data decryptor factory + */ + public abstract SessionKeyDataDecryptorFactory sessionKeyDataDecryptorFactory( + PGPSessionKey sessionKey); + + /** + * Return an instance of the {@link PublicKeyDataDecryptorFactory}, which is responsible for providing + * concrete implementations needed to decrypt OpenPGP messages using a {@link PGPPrivateKey}. + * + * @param decryptionKey private decryption key + * @return public-key data decryptor factory + */ + public abstract PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory( + PGPPrivateKey decryptionKey); + + /** + * Return an instance of the {@link PGPDigestCalculatorProvider}, which is responsible for providing + * concrete {@link org.bouncycastle.openpgp.operator.PGPDigestCalculator} implementations. + * + * @return pgp digest calculator provider + * @throws PGPException if the provider cannot be instantiated + */ + public abstract PGPDigestCalculatorProvider pgpDigestCalculatorProvider() + throws PGPException; +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java new file mode 100644 index 0000000000..af61448fb0 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -0,0 +1,303 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An {@link OpenPGPKey} (TSK - transferable secret key) is the pendant to an {@link OpenPGPCertificate}, + * but containing the secret key material in addition to the public components. + * It consists of one or multiple {@link OpenPGPSecretKey} objects. + */ +public class OpenPGPKey + extends OpenPGPCertificate +{ + // This class extends OpenPGPCertificate, but also holds secret key components in a dedicated map. + private final Map secretKeys; + + /** + * Create an {@link OpenPGPKey} instance based on a {@link PGPSecretKeyRing}. + * The {@link OpenPGPImplementation} will be acquired by invoking {@link OpenPGPImplementation#getInstance()}. + * + * @param keyRing secret key ring + */ + public OpenPGPKey(PGPSecretKeyRing keyRing) + { + this(keyRing, OpenPGPImplementation.getInstance()); + } + + /** + * Create an {@link OpenPGPKey} instance based on a {@link PGPSecretKeyRing}. + * + * @param keyRing secret key ring + * @param implementation OpenPGP implementation + */ + public OpenPGPKey(PGPSecretKeyRing keyRing, OpenPGPImplementation implementation) + { + super(keyRing, implementation); + + // Process and map secret keys + this.secretKeys = new HashMap<>(); + for (OpenPGPComponentKey key : getKeys()) + { + KeyIdentifier identifier = key.getKeyIdentifier(); + PGPSecretKey secretKey = keyRing.getSecretKey(identifier); + if (secretKey == null) + { + continue; + } + + secretKeys.put(identifier, new OpenPGPSecretKey(key, secretKey, implementation.pbeSecretKeyDecryptorBuilderProvider())); + } + } + + @Override + public List getComponents() + { + // We go through the list of components returned by OpenPGPCertificate and replace those components + // where we have the secret key available + + // contains only public components + List components = super.getComponents(); + for (int i = components.size() - 1 ; i >= 0; i--) + { + OpenPGPCertificateComponent component = components.get(i); + if (component instanceof OpenPGPComponentKey) + { + OpenPGPSecretKey secretKey = getSecretKey((OpenPGPComponentKey) component); + if (secretKey != null) + { + // swap in secret component + components.remove(i); + components.add(i, secretKey); + } + } + } + return components; + } + + public static OpenPGPKey fromAsciiArmor(String armor) + throws IOException + { + return fromAsciiArmor(armor, OpenPGPImplementation.getInstance()); + } + + public static OpenPGPKey fromAsciiArmor( + String armor, + OpenPGPImplementation implementation) + throws IOException + { + return fromBytes( + armor.getBytes(StandardCharsets.UTF_8), + implementation); + } + + public static OpenPGPKey fromBytes( + byte[] bytes, + OpenPGPImplementation implementation) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + + Object object = objectFactory.nextObject(); + if (!(object instanceof PGPSecretKeyRing)) + { + throw new IOException("Not a secret key."); + } + + PGPSecretKeyRing keyRing = (PGPSecretKeyRing) object; + return new OpenPGPKey(keyRing, implementation); + } + + /** + * Return a {@link Map} containing all {@link OpenPGPSecretKey} components (secret subkeys) of the key. + * + * @return secret key components + */ + public Map getSecretKeys() + { + return new HashMap<>(secretKeys); + } + + /** + * Return the {@link OpenPGPSecretKey} identified by the passed {@link KeyIdentifier}. + * + * @param identifier key identifier + * @return corresponding secret key or null + */ + public OpenPGPSecretKey getSecretKey(KeyIdentifier identifier) + { + return secretKeys.get(identifier); + } + + /** + * Return the {@link OpenPGPSecretKey} that corresponds to the passed {@link OpenPGPComponentKey}. + * + * @param key component key + * @return corresponding secret key or null + */ + public OpenPGPSecretKey getSecretKey(OpenPGPComponentKey key) + { + return getSecretKey(key.getKeyIdentifier()); + } + + @Override + public PGPSecretKeyRing getPGPKeyRing() + { + return (PGPSecretKeyRing) super.getPGPKeyRing(); + } + + @Override + public String toAsciiArmoredString() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream.Builder armorBuilder = ArmoredOutputStream.builder() + .clearHeaders(); + + for (String slice : fingerprintComments()) + { + armorBuilder.addComment(slice); + } + + for (OpenPGPUserId userId : getPrimaryKey().getUserIDs()) + { + armorBuilder.addComment(userId.getUserId()); + } + + ArmoredOutputStream aOut = armorBuilder.build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + + getPGPKeyRing().encode(pOut); + pOut.close(); + aOut.close(); + return bOut.toString(); + } + + /** + * Secret key component of a {@link org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPPrimaryKey} or + * {@link org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPSubkey}. + */ + public static class OpenPGPSecretKey + extends OpenPGPComponentKey + { + private final PGPSecretKey rawSecKey; + private final OpenPGPComponentKey pubKey; + private final PBESecretKeyDecryptorBuilderProvider decryptorBuilderProvider; + + /** + * Constructor. + * + * @param pubKey corresponding public key component + * @param secKey secret key + * @param decryptorBuilderProvider for unlocking private keys + */ + public OpenPGPSecretKey(OpenPGPComponentKey pubKey, + PGPSecretKey secKey, + PBESecretKeyDecryptorBuilderProvider decryptorBuilderProvider) + { + super(pubKey.getPGPPublicKey(), pubKey.getCertificate()); + this.decryptorBuilderProvider = decryptorBuilderProvider; + this.rawSecKey = secKey; + this.pubKey = pubKey; + } + + @Override + protected OpenPGPCertificateComponent getPublicComponent() + { + // return the public key component to properly map this secret key to its public key component when + // the public key component is used as key in a map. + return pubKey; + } + + @Override + public String toDetailString() + { + return "Private" + pubKey.toDetailString(); + } + + /** + * Return the underlying {@link PGPSecretKey}. + * + * @return secret key + */ + public PGPSecretKey getPGPSecretKey() + { + return rawSecKey; + } + + /** + * Return the public {@link OpenPGPComponentKey} corresponding to this {@link OpenPGPSecretKey}. + * + * @return public component key + */ + public OpenPGPComponentKey getPublicKey() + { + return pubKey; + } + + /** + * If true, the secret key is not available in plain and likely needs to be decrypted by providing + * a key passphrase. + */ + public boolean isLocked() + { + return getPGPSecretKey().getS2KUsage() != SecretKeyPacket.USAGE_NONE; + } + + /** + * Access the {@link PGPPrivateKey} by unlocking the potentially locked secret key using the provided + * passphrase. Note: If the key is not locked, it is sufficient to pass null as passphrase. + * + * @param passphrase passphrase or null + * @return unlocked private key + * @throws PGPException if the key cannot be unlocked + */ + public PGPPrivateKey unlock(char[] passphrase) + throws PGPException + { + PBESecretKeyDecryptor decryptor = null; + if (passphrase != null) + { + decryptor = decryptorBuilderProvider.provide().build(passphrase); + } + return getPGPSecretKey().extractPrivateKey(decryptor); + } + + public boolean isPassphraseCorrect(char[] passphrase) + { + try + { + PGPPrivateKey privateKey = unlock(passphrase); + return privateKey != null; + } + catch (PGPException e) + { + return false; + } + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java new file mode 100644 index 0000000000..ac32725c8b --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java @@ -0,0 +1,206 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.KeyIdentifier; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Implementation of the {@link OpenPGPKeyMaterialProvider} which caches items in a {@link HashMap}. + * It allows to provide key or certificates dynamically via a {@link #callback} that can be set using + * {@link #setMissingItemCallback(OpenPGPKeyMaterialProvider)}. + * Results from this callback are automatically cached for later access. This behavior can be adjusted via + * {@link #setCacheResultsFromCallback(boolean)}. + * + * @param {@link OpenPGPCertificate} or {@link OpenPGPKey} + */ +public abstract class OpenPGPKeyMaterialPool + implements OpenPGPKeyMaterialProvider +{ + private final Map pool = new HashMap<>(); + private OpenPGPKeyMaterialProvider callback = null; + private boolean cacheResultsFromCallback = true; + + /** + * Create an empty pool. + */ + public OpenPGPKeyMaterialPool() + { + + } + + /** + * Create a pool from the single provided item. + * @param item item + */ + public OpenPGPKeyMaterialPool(M item) + { + addItem(item); + } + + /** + * Create a pool and initialize its contents with the provided collection of items. + * @param items collection of keys or certificates + */ + public OpenPGPKeyMaterialPool(Collection items) + { + for (M item : items) + { + addItem(item); + } + } + + /** + * Set a callback that gets fired whenever an item is requested, which is not found in the pool. + * + * @param callback callback + * @return this + */ + public OpenPGPKeyMaterialPool setMissingItemCallback(OpenPGPKeyMaterialProvider callback) + { + this.callback = Objects.requireNonNull(callback); + return this; + } + + /** + * Decide, whether the implementation should add {@link OpenPGPCertificate certificates} returned by + * {@link #callback} to the pool of cached certificates. + * + * @param cacheResults if true, cache certificates from callback + * @return this + */ + public OpenPGPKeyMaterialPool setCacheResultsFromCallback(boolean cacheResults) + { + this.cacheResultsFromCallback = cacheResults; + return this; + } + + @Override + public M provide(KeyIdentifier componentKeyIdentifier) + { + M result = pool.get(componentKeyIdentifier); + if (result == null && callback != null) + { + // dynamically request certificate or key from callback + result = callback.provide(componentKeyIdentifier); + if (cacheResultsFromCallback) + { + addItem(result); + } + } + return result; + } + + /** + * Add a certificate to the pool. + * Note: If multiple items share the same subkey material, adding an item might overwrite the reference to + * another item for that subkey. + * + * @param item OpenPGP key or certificate that shall be added into the pool + * @return this + */ + public OpenPGPKeyMaterialPool addItem(M item) + { + if (item != null) + { + for (KeyIdentifier identifier : item.getAllKeyIdentifiers()) + { + pool.put(identifier, item); + } + } + return this; + } + + /** + * Return all items from the pool. + * @return all items + */ + public Collection getAllItems() + { + return pool.values().stream() + .distinct() + .collect(Collectors.toList()); + } + + /** + * Implementation of {@link OpenPGPKeyMaterialPool} tailored to provide {@link OpenPGPKey OpenPGPKeys}. + */ + public static class OpenPGPKeyPool + extends OpenPGPKeyMaterialPool + implements OpenPGPKeyProvider + { + public OpenPGPKeyPool() + { + super(); + } + + public OpenPGPKeyPool(Collection items) + { + super(items); + } + + @Override + public OpenPGPKeyPool setMissingItemCallback(OpenPGPKeyMaterialProvider callback) + { + super.setMissingItemCallback(callback); + return this; + } + + @Override + public OpenPGPKeyPool setCacheResultsFromCallback(boolean cacheResults) + { + super.setCacheResultsFromCallback(cacheResults); + return this; + } + + @Override + public OpenPGPKeyPool addItem(OpenPGPKey item) + { + super.addItem(item); + return this; + } + } + + /** + * Implementation of {@link OpenPGPKeyMaterialPool} tailored to providing + * {@link OpenPGPCertificate OpenPGPCertificates}. + */ + public static class OpenPGPCertificatePool + extends OpenPGPKeyMaterialPool + implements OpenPGPCertificateProvider + { + public OpenPGPCertificatePool() + { + super(); + } + + public OpenPGPCertificatePool(Collection items) + { + super(items); + } + + @Override + public OpenPGPCertificatePool setMissingItemCallback(OpenPGPKeyMaterialProvider callback) + { + super.setMissingItemCallback(callback); + return this; + } + + @Override + public OpenPGPCertificatePool setCacheResultsFromCallback(boolean cacheResults) + { + super.setCacheResultsFromCallback(cacheResults); + return this; + } + + @Override + public OpenPGPCertificatePool addItem(OpenPGPCertificate item) + { + super.addItem(item); + return this; + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java new file mode 100644 index 0000000000..c842fcaba5 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java @@ -0,0 +1,40 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.KeyIdentifier; + +/** + * Interface for providing OpenPGP keys or certificates. + * + * @param either {@link OpenPGPCertificate} or {@link OpenPGPKey} + */ +public interface OpenPGPKeyMaterialProvider +{ + /** + * Provide the requested {@link OpenPGPCertificate} or {@link OpenPGPKey} containing the component key identified + * by the passed in {@link KeyIdentifier}. + * + * @param componentKeyIdentifier identifier of a component key (primary key or subkey) + * @return the OpenPGP certificate or key containing the identified component key + */ + M provide(KeyIdentifier componentKeyIdentifier); + + /** + * Interface for requesting {@link OpenPGPCertificate OpenPGPCertificates} by providing a {@link KeyIdentifier}. + * The {@link KeyIdentifier} can either be that of the certificates primary key, or of a subkey. + */ + interface OpenPGPCertificateProvider + extends OpenPGPKeyMaterialProvider + { + + } + + /** + * Interface for requesting {@link OpenPGPKey OpenPGPKeys} by providing a {@link KeyIdentifier}. + * The {@link KeyIdentifier} can either be that of the keys primary key, or of a subkey. + */ + interface OpenPGPKeyProvider + extends OpenPGPKeyMaterialProvider + { + + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java new file mode 100644 index 0000000000..52e817823f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -0,0 +1,797 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPOnePassSignature; +import org.bouncycastle.openpgp.PGPPadding; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Stack; +import java.util.stream.Collectors; + +public class OpenPGPMessageGenerator +{ + public static final int BUFFER_SIZE = 1024; + + private final OpenPGPImplementation implementation; + private final Configuration config = new Configuration(); + + // Literal Data metadata + private Date fileModificationDate = null; + private String filename = null; + private char format = PGPLiteralData.BINARY; + private PGPEncryptedDataGenerator.SessionKeyExtractionCallback sessionKeyExtractionCallback; + + public OpenPGPMessageGenerator() + { + this(OpenPGPImplementation.getInstance()); + } + + public OpenPGPMessageGenerator(OpenPGPImplementation implementation) + { + this.implementation = Objects.requireNonNull(implementation); + } + + /** + * Add a recipients certificate to the set of encryption keys. + * Subkeys will be selected using the default {@link SubkeySelector}, which can be replaced by calling + * {@link Configuration#setEncryptionKeySelector(SubkeySelector)}. + * The recipient will be able to decrypt the message using their corresponding secret key. + * + * @param recipientCertificate recipient certificate (public key) + * @return this + */ + public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate) + { + return addEncryptionCertificate(recipientCertificate, config.encryptionKeySelector); + } + + /** + * Add a recipients certificate to the set of encryption keys. + * Subkeys will be selected using the provided {@link SubkeySelector}. + * The recipient will be able to decrypt the message using their corresponding secret key. + * + * @param recipientCertificate recipient certificate (public key) + * @param subkeySelector selector for encryption subkeys + * @return this + */ + public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate, SubkeySelector subkeySelector) + { + config.recipients.add(new Recipient(recipientCertificate, subkeySelector)); + return this; + } + + /** + * Add a message passphrase. + * In addition to optional public key encryption, the message will be decryptable using the given passphrase. + * + * @param passphrase passphrase + * @return this + */ + public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) + { + config.passphrases.add(passphrase); + return this; + } + + public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) + { + return addSigningKey(signingKey, key -> null); + } + + /** + * Sign the message using a secret signing key. + * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by + * calling {@link Configuration#setSigningKeySelector(SubkeySelector)}. + * + * @param signingKey OpenPGP key + * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. + * @return this + */ + public OpenPGPMessageGenerator addSigningKey( + OpenPGPKey signingKey, + SecretKeyPassphraseProvider signingKeyDecryptorProvider) + { + return addSigningKey(signingKey, signingKeyDecryptorProvider, config.signingKeySelector); + } + + /** + * Sign the message using a secret signing key. + * + * @param signingKey OpenPGP key + * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. + * @param subkeySelector selector for selecting signing subkey(s) + * @return this + */ + public OpenPGPMessageGenerator addSigningKey( + OpenPGPKey signingKey, + SecretKeyPassphraseProvider signingKeyDecryptorProvider, + SubkeySelector subkeySelector) + { + config.signingKeys.add(new Signer(signingKey, signingKeyDecryptorProvider, subkeySelector)); + return this; + } + + /** + * Specify, whether the output OpenPGP message will be ASCII armored or not. + * + * @param armored boolean + * @return this + */ + public OpenPGPMessageGenerator setArmored(boolean armored) + { + this.config.setArmored(armored); + return this; + } + + public OpenPGPMessageGenerator setFileMetadata(File file) + { + this.filename = file.getName(); + this.fileModificationDate = new Date(file.lastModified()); + this.format = PGPLiteralData.BINARY; + return this; + } + + public OpenPGPMessageGenerator setSessionKeyExtractionCallback( + PGPEncryptedDataGenerator.SessionKeyExtractionCallback callback) + { + this.sessionKeyExtractionCallback = callback; + return this; + } + + /** + * Open an {@link OpenPGPMessageOutputStream} over the given output stream. + * @param out output stream + * @return OpenPGP message output stream + * @throws PGPException if the output stream cannot be created + */ + public OpenPGPMessageOutputStream open(OutputStream out) + throws PGPException, IOException + { + OpenPGPMessageOutputStream.Builder streamBuilder = OpenPGPMessageOutputStream.builder(); + + applyOptionalAsciiArmor(streamBuilder); + applyOptionalEncryption(streamBuilder, sessionKeyExtractionCallback); + applySignatures(streamBuilder); + applyOptionalCompression(streamBuilder); + applyLiteralDataWrap(streamBuilder); + + return streamBuilder.build(out); + } + + /** + * Apply ASCII armor if necessary. + * The output will only be wrapped in ASCII armor, if {@link #setArmored(boolean)} is set + * to true (is true by default). + * The {@link ArmoredOutputStream} will be instantiated using the {@link ArmoredOutputStreamFactory} + * which can be replaced using {@link Configuration#setArmorStreamFactory(ArmoredOutputStreamFactory)}. + * + * @param builder OpenPGP message output stream builder + */ + private void applyOptionalAsciiArmor(OpenPGPMessageOutputStream.Builder builder) + { + if (config.isArmored) + { + builder.armor(config.armorStreamFactory); + } + } + + /** + * Optionally apply message encryption. + * If no recipient certificates and no encryption passphrases were supplied, no encryption + * will be applied. + * Otherwise, encryption mode and algorithms will be negotiated and message encryption will be applied. + * + * @param builder OpenPGP message output stream builder + * @param sessionKeyExtractionCallback callback to extract the session key (nullable) + */ + private void applyOptionalEncryption( + OpenPGPMessageOutputStream.Builder builder, + PGPEncryptedDataGenerator.SessionKeyExtractionCallback sessionKeyExtractionCallback) + { + MessageEncryptionMechanism encryption = config.negotiateEncryption(); + if (!encryption.isEncrypted()) + { + return; // No encryption + } + + PGPDataEncryptorBuilder encBuilder = implementation.pgpDataEncryptorBuilder( + encryption.getSymmetricKeyAlgorithm()); + + // Specify container type for the plaintext + switch (encryption.getMode()) + { + case SEIPDv1: + encBuilder.setWithIntegrityPacket(true); + break; + + case SEIPDv2: + encBuilder.setWithAEAD(encryption.getAeadAlgorithm(), 6); + encBuilder.setUseV6AEAD(); + break; + + case LIBREPGP_OED: + encBuilder.setWithAEAD(encryption.getAeadAlgorithm(), 6); + encBuilder.setUseV5AEAD(); + break; + } + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(encBuilder); + // For sake of interoperability and simplicity, we always use a dedicated session key for message encryption + // even if only a single PBE encryption method was added and S2K result could be used as session-key directly. + encGen.setForceSessionKey(true); + encGen.setSessionKeyExtractionCallback(sessionKeyExtractionCallback); + + // Setup asymmetric message encryption + for (Recipient recipient : config.recipients) + { + for (OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey : recipient.encryptionSubkeys()) + { + PublicKeyKeyEncryptionMethodGenerator method = implementation.publicKeyKeyEncryptionMethodGenerator( + encryptionSubkey.getPGPPublicKey()); + encGen.addMethod(method); + } + } + + // Setup symmetric (password-based) message encryption + for (char[] passphrase : config.passphrases) + { + PBEKeyEncryptionMethodGenerator skeskGen; + switch (encryption.getMode()) + { + case SEIPDv1: + case LIBREPGP_OED: + // "v4" and LibrePGP use symmetric-key encrypted session key packets version 4 (SKESKv4) + skeskGen = implementation.pbeKeyEncryptionMethodGenerator(passphrase); + break; + + case SEIPDv2: + // v6 uses symmetric-key encrypted session key packets version 6 (SKESKv6) using AEAD + skeskGen = implementation.pbeKeyEncryptionMethodGenerator(passphrase, S2K.Argon2Params.memoryConstrainedParameters()); + break; + default: continue; + } + + skeskGen.setSecureRandom(CryptoServicesRegistrar.getSecureRandom()); // Prevent NPE + encGen.addMethod(skeskGen); + } + + // Finally apply encryption + builder.encrypt(o -> + { + try + { + return encGen.open(o, new byte[BUFFER_SIZE]); + } + catch (IOException e) + { + throw new PGPException("Could not open encryptor OutputStream", e); + } + }); + + // Optionally, append a padding packet as the last packet inside the SEIPDv2 packet. + if (encryption.getMode() == EncryptedDataPacketType.SEIPDv2 && config.isPadded) + { + builder.padding(o -> new OpenPGPMessageOutputStream.PaddingPacketAppenderOutputStream(o, PGPPadding::new)); + } + } + + /** + * Apply OpenPGP inline-signatures. + * + * @param builder OpenPGP message output stream builder + */ + private void applySignatures(OpenPGPMessageOutputStream.Builder builder) + { + builder.sign(o -> + { + Stack signatureGenerators = new Stack<>(); + for (Signer s : config.signingKeys) + { + for (OpenPGPKey.OpenPGPSecretKey signingSubkey : s.signingSubkeys()) + { + int hashAlgorithm = config.negotiateHashAlgorithm(s.signingKey, signingSubkey); + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), hashAlgorithm), + signingSubkey.getPGPSecretKey().getPublicKey()); + char[] passphrase = signingSubkey.isLocked() ? s.passphraseProvider.providePassphrase(signingSubkey) : null; + PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); + + sigGen.init(PGPSignature.BINARY_DOCUMENT, privateKey); + signatureGenerators.push(sigGen); + } + } + + // One-Pass-Signatures + Iterator sigGens = signatureGenerators.iterator(); + while (sigGens.hasNext()) + { + PGPSignatureGenerator gen = sigGens.next(); + PGPOnePassSignature ops = gen.generateOnePassVersion(sigGens.hasNext()); + ops.encode(o); + } + + return new OpenPGPMessageOutputStream.SignatureGeneratorOutputStream(o, signatureGenerators); + }); + } + + private void applyOptionalCompression(OpenPGPMessageOutputStream.Builder builder) + { + int compressionAlgorithm = config.negotiateCompression(); + if (compressionAlgorithm == CompressionAlgorithmTags.UNCOMPRESSED) + { + return; // Uncompressed + } + + PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(compressionAlgorithm); + + builder.compress(o -> + { + try + { + return compGen.open(o, new byte[BUFFER_SIZE]); + } + catch (IOException e) + { + throw new PGPException("Could not apply compression", e); + } + }); + } + + /** + * Setup wrapping of the message plaintext in a literal data packet. + * + * @param builder OpenPGP message output stream + */ + private void applyLiteralDataWrap(OpenPGPMessageOutputStream.Builder builder) + { + PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator(); + builder.literalData(o -> + { + try + { + return litGen.open(o, + format, + filename != null ? filename : "", + fileModificationDate != null ? fileModificationDate : PGPLiteralData.NOW, + new byte[BUFFER_SIZE]); + } + catch (IOException e) + { + throw new PGPException("Could not apply literal data wrapping", e); + } + }); + } + + public OpenPGPMessageGenerator setIsPadded(boolean isPadded) + { + config.setPadded(isPadded); + return this; + } + + public Configuration getConfiguration() + { + return config; + } + + public interface ArmoredOutputStreamFactory + extends OpenPGPMessageOutputStream.OutputStreamFactory + { + ArmoredOutputStream get(OutputStream out); + } + + public interface CompressionNegotiator + { + /** + * Negotiate a compression algorithm. + * Returning {@link org.bouncycastle.bcpg.CompressionAlgorithmTags#UNCOMPRESSED} will result in no compression. + * + * @param configuration message generator configuration + * @return negotiated compression algorithm ID + */ + int negotiateCompression(Configuration configuration); + } + + public interface EncryptionNegotiator + { + /** + * Negotiate encryption mode and algorithms. + * + * @param configuration message generator configuration + * @return negotiated encryption mode and algorithms + */ + MessageEncryptionMechanism negotiateEncryption(Configuration configuration); + } + + public interface HashAlgorithmNegotiator + { + int negotiateHashAlgorithm(OpenPGPKey key, OpenPGPCertificate.OpenPGPComponentKey subkey); + } + + public static class Configuration + { + private boolean isArmored = true; + public boolean isPadded = true; + private final List recipients = new ArrayList<>(); + private final List signingKeys = new ArrayList<>(); + private final List passphrases = new ArrayList<>(); + + // Factory for creating ASCII armor + private ArmoredOutputStreamFactory armorStreamFactory = + outputStream -> ArmoredOutputStream.builder() + .clearHeaders() // Hide version + .enableCRC(false) // Disable CRC sum + .build(outputStream); + + private SubkeySelector encryptionKeySelector = OpenPGPCertificate::getEncryptionKeys; + + private SubkeySelector signingKeySelector = OpenPGPCertificate::getSigningKeys; + + // Encryption method negotiator for when only password-based encryption is requested + private EncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB); + + // Encryption method negotiator for when public-key encryption is requested + private EncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> + { + // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. + boolean seipd2Supported = configuration.recipients + .stream() + // ignore keys that can't encrypt at all + .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) + // Make sure all recipients have at least one key that can do SEIPD2 + .allMatch(recipient -> recipient.certificate.getEncryptionKeys() + .stream() + // if some recipient only has keys which DO NOT support SEIPD2 -> downgrade to SEIPD1 + .anyMatch(subkey -> + { + Features features = subkey.getFeatures(); + return features != null && features.supportsFeature(Features.FEATURE_SEIPD_V2); + }) + ); + + if (seipd2Supported) + { + PreferredAEADCiphersuites commonDenominator = configuration.recipients + .stream() + // Ignore certificates that cannot encrypt + .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) + // Ignore subkeys on recipients certificates that do not support SEIPDv2 + .map(recipient -> + { + List encKeys = recipient.encryptionSubkeys(); + return encKeys.stream().filter(it -> it.getFeatures().supportsSEIPDv2()); + }) + // go from List> to List + .flatMap(it -> it) + // Extract AEAD preferences per key + .map(OpenPGPCertificate.OpenPGPComponentKey::getAEADCipherSuitePreferences) + // Take the intersection of combinations to find commonly preferred combination + .reduce((current, next) -> + { + List nextPreferences = Arrays.asList(next.getAlgorithms()); + return new PreferredAEADCiphersuites(false, Arrays.stream(current.getAlgorithms()) + .filter(nextPreferences::contains).toArray(PreferredAEADCiphersuites.Combination[]::new)); + }) + // If no common combination was found, fall back to implicitly supported algorithms + .orElse(PreferredAEADCiphersuites.builder(false) + // Default combination + .addCombination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB) + .build() + ); + PreferredAEADCiphersuites.Combination[] combinations = commonDenominator.getAlgorithms(); + // Select best combo from common combinations + // TODO: Make sure this is actually the best + PreferredAEADCiphersuites.Combination best = combinations[0]; + return MessageEncryptionMechanism.aead(best.getSymmetricAlgorithm(), best.getAeadAlgorithm()); + } + else + { + PreferredAlgorithms commonDenominator = configuration.recipients + .stream() + // Ignore certificates that cannot encrypt + .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) + .map(Recipient::encryptionSubkeys) + .map(List::stream) + // go from List> to List + .flatMap(it -> it) + // Extract sym. cipher preferences per key + .map(OpenPGPCertificate.OpenPGPComponentKey::getSymmetricCipherPreferences) + // Take the intersection of combinations to find commonly preferred combination + .reduce((current, next) -> + new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, + Arrays.stream(current.getPreferences()) + .filter(alg -> Arrays.stream(next.getPreferences()).anyMatch(it -> alg == it)) + .toArray())) + // If no common combination was found, fall back to implicitly supported algorithms + .orElse(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, + new int[] { + SymmetricKeyAlgorithmTags.AES_128 + } // AES128 is "MUST implement" + )); + // TODO: Algorithm selection + int bestCipherPreference = commonDenominator.getPreferences()[0]; + + return MessageEncryptionMechanism.integrityProtected(bestCipherPreference); + } + }; + + // Primary encryption method negotiator + private final EncryptionNegotiator encryptionNegotiator = + configuration -> + { + // No encryption methods provided -> Unencrypted message + if (configuration.recipients.isEmpty() && configuration.passphrases.isEmpty()) + { + return MessageEncryptionMechanism.unencrypted(); + } + + // No public-key encryption requested -> password-based encryption + else if (configuration.recipients.isEmpty()) + { + // delegate negotiation to pbe negotiator + return passwordBasedEncryptionNegotiator.negotiateEncryption(configuration); + } + else + { + // delegate negotiation to pkbe negotiator + return publicKeyBasedEncryptionNegotiator.negotiateEncryption(configuration); + } + }; + + // TODO: Implement properly, taking encryption into account (sign-only should not compress) + private CompressionNegotiator compressionNegotiator = + configuration -> CompressionAlgorithmTags.UNCOMPRESSED; + + private HashAlgorithmNegotiator hashAlgorithmNegotiator = + (key, subkey) -> + { + // TODO: Take into consideration hash preferences of recipients, not the sender + PreferredAlgorithms hashPreferences = subkey.getHashAlgorithmPreferences(); + if (hashPreferences == null) + { + return HashAlgorithmTags.SHA512; + } + return hashPreferences.getPreferences()[0]; + }; + + /** + * Replace the default {@link EncryptionNegotiator} that gets to decide, which {@link MessageEncryptionMechanism} mode + * to use if only password-based encryption is used. + * + * @param pbeNegotiator custom PBE negotiator. + * @return this + */ + public Configuration setPasswordBasedEncryptionNegotiator(EncryptionNegotiator pbeNegotiator) + { + this.passwordBasedEncryptionNegotiator = Objects.requireNonNull(pbeNegotiator); + return this; + } + + /** + * Replace the default {@link EncryptionNegotiator} that decides, which {@link MessageEncryptionMechanism} + * mode to use if public-key encryption is used. + * + * @param pkbeNegotiator custom encryption negotiator that gets to decide if PK-based encryption is used + * @return this + */ + public Configuration setPublicKeyBasedEncryptionNegotiator(EncryptionNegotiator pkbeNegotiator) + { + this.publicKeyBasedEncryptionNegotiator = Objects.requireNonNull(pkbeNegotiator); + return this; + } + + /** + * Replace the default encryption key selector with a custom implementation. + * The encryption key selector is responsible for selecting one or more encryption subkeys from a + * recipient certificate. + * + * @param encryptionKeySelector selector for encryption (sub-)keys + * @return this + */ + public Configuration setEncryptionKeySelector(SubkeySelector encryptionKeySelector) + { + this.encryptionKeySelector = Objects.requireNonNull(encryptionKeySelector); + return this; + } + + /** + * Replace the default signing key selector with a custom implementation. + * The signing key selector is responsible for selecting one or more signing subkeys from a signing key. + * + * @param signingKeySelector selector for signing (sub-)keys + * @return this + */ + public Configuration setSigningKeySelector(SubkeySelector signingKeySelector) + { + this.signingKeySelector = Objects.requireNonNull(signingKeySelector); + return this; + } + + /** + * Replace the default {@link CompressionNegotiator} with a custom implementation. + * The {@link CompressionNegotiator} is used to negotiate, whether and how to compress the literal data packet. + * + * @param compressionNegotiator negotiator + * @return this + */ + public Configuration setCompressionNegotiator(CompressionNegotiator compressionNegotiator) + { + this.compressionNegotiator = Objects.requireNonNull(compressionNegotiator); + return this; + } + + /** + * Replace the default {@link HashAlgorithmNegotiator} with a custom implementation. + * + * @param hashAlgorithmNegotiator custom hash algorithm negotiator + * @return this + */ + public Configuration setHashAlgorithmNegotiator(HashAlgorithmNegotiator hashAlgorithmNegotiator) + { + this.hashAlgorithmNegotiator = Objects.requireNonNull(hashAlgorithmNegotiator); + return this; + } + + /** + * Replace the {@link ArmoredOutputStreamFactory} with a custom implementation. + * + * @param factory factory for {@link ArmoredOutputStream} instances + * @return this + */ + public Configuration setArmorStreamFactory(ArmoredOutputStreamFactory factory) + { + this.armorStreamFactory = Objects.requireNonNull(factory); + return this; + } + + public Configuration setArmored(boolean isArmored) + { + this.isArmored = isArmored; + return this; + } + + public Configuration setPadded(boolean isPadded) + { + this.isPadded = isPadded; + return this; + } + + public int negotiateCompression() + { + return compressionNegotiator.negotiateCompression(this); + } + + public int negotiateHashAlgorithm(OpenPGPKey signingKey, OpenPGPKey.OpenPGPSecretKey signingSubkey) + { + return hashAlgorithmNegotiator.negotiateHashAlgorithm(signingKey, signingSubkey); + } + + public MessageEncryptionMechanism negotiateEncryption() + { + return encryptionNegotiator.negotiateEncryption(this); + } + } + + /** + * Tuple representing a recipients OpenPGP certificate. + */ + static class Recipient + { + private final OpenPGPCertificate certificate; + private final SubkeySelector subkeySelector; + + /** + * Create a {@link Recipient}. + * + * @param certificate OpenPGP certificate (public key) + * @param subkeySelector selector to select encryption-capable subkeys from the certificate + */ + public Recipient(PGPPublicKeyRing certificate, SubkeySelector subkeySelector, OpenPGPImplementation implementation) + { + this(new OpenPGPCertificate(certificate, implementation), subkeySelector); + } + + public Recipient(OpenPGPCertificate certificate, SubkeySelector subkeySelector) + { + this.certificate = certificate; + this.subkeySelector = subkeySelector; + } + + /** + * Return a set of {@link PGPPublicKey subkeys} which will be used for message encryption. + * + * @return encryption capable subkeys for this recipient + */ + public List encryptionSubkeys() + { + return subkeySelector.select(certificate) + .stream() + .distinct() + .collect(Collectors.toList()); + } + } + + /** + * Tuple representing an OpenPGP key used for signing. + */ + static class Signer + { + private final OpenPGPKey signingKey; + private final SecretKeyPassphraseProvider passphraseProvider; + private final SubkeySelector subkeySelector; + + public Signer(OpenPGPKey signingKey, + SecretKeyPassphraseProvider passphraseProvider, + SubkeySelector subkeySelector) + { + this.signingKey = signingKey; + this.passphraseProvider = passphraseProvider; + this.subkeySelector = subkeySelector; + } + + public List signingSubkeys() + { + return subkeySelector.select(signingKey) + .stream() + .map(signingKey::getSecretKey) + .distinct() + .collect(Collectors.toList()); + } + } + + /** + * Interface for selecting a subset of keys from a {@link PGPKeyRing}. + * This is useful e.g. for selecting a signing key from an OpenPGP key, or a for selecting all + * encryption capable subkeys of a certificate. + */ + public interface SubkeySelector + { + /** + * Given a {@link PGPKeyRing}, select a subset of the key rings (sub-)keys and return their + * {@link KeyIdentifier KeyIdentifiers}. + * + * @param certificate OpenPGP key or certificate + * @return non-null list of identifiers + */ + List select(OpenPGPCertificate certificate); + } + + public interface SecretKeyPassphraseProvider + { + char[] providePassphrase(OpenPGPKey.OpenPGPSecretKey key); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java new file mode 100644 index 0000000000..49f0a208bf --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -0,0 +1,743 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.AEADEncDataPacket; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPMarker; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPOnePassSignature; +import org.bouncycastle.openpgp.PGPOnePassSignatureList; +import org.bouncycastle.openpgp.PGPPadding; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An {@link InputStream} that processes an OpenPGP message. + * Its contents are the plaintext from the messages LiteralData packet. + * You can get information about the message (signatures, encryption method, message metadata) + * by reading ALL data from the stream, closing it with {@link #close()} and then retrieving a {@link Result} object + * by calling {@link #getResult()}. + */ +public class OpenPGPMessageInputStream + extends InputStream +{ + public static int MAX_RECURSION = 16; + + private final PGPObjectFactory objectFactory; + private final OpenPGPImplementation implementation; + + private final OpenPGPMessageProcessor processor; + + private final Result.Builder resultBuilder; + private final Layer layer; // the packet layer processed by this input stream + + private InputStream in; + + OpenPGPMessageInputStream(PGPObjectFactory objectFactory, + OpenPGPMessageProcessor processor) + { + this(objectFactory, processor, Result.builder()); + } + + private OpenPGPMessageInputStream(PGPObjectFactory objectFactory, + OpenPGPMessageProcessor processor, + Result.Builder resultBuilder) + { + this.objectFactory = objectFactory; + this.processor = processor; + this.implementation = processor.getImplementation(); + this.resultBuilder = resultBuilder; + try + { + this.layer = resultBuilder.openLayer(); + } + catch (PGPException e) + { + // cannot happen + throw new AssertionError(e); + } + } + + void process() + throws IOException, PGPException + { + Object next; + while ((next = objectFactory.nextObject()) != null) + { + // prefixed packets + + if (next instanceof PGPSignatureList) + { + // prefixed-signed message (SIG MSG) + PGPSignatureList prefixedSigs = (PGPSignatureList) next; + resultBuilder.prefixedSignatures(prefixedSigs); + } + else if (next instanceof PGPOnePassSignatureList) + { + // one-pass-signed message (OPS MSG SIG) + PGPOnePassSignatureList pgpOnePassSignatures = (PGPOnePassSignatureList) next; + resultBuilder.onePassSignatures(pgpOnePassSignatures); + } + else if (next instanceof PGPMarker) + { + // prefixed marker packet (ignore) + } + + else + { + // Init signatures of this layer + resultBuilder.initSignatures(processor); + + if (next instanceof PGPLiteralData) + { + // Literal Data \o/ + PGPLiteralData literalData = (PGPLiteralData) next; + resultBuilder.literalData( + literalData.getFileName(), + (char) literalData.getFormat(), + literalData.getModificationTime()); + + in = literalData.getDataStream(); + return; + } + else if (next instanceof PGPCompressedData) + { + // Compressed Data + PGPCompressedData compressedData = (PGPCompressedData) next; + resultBuilder.compressed(compressedData.getAlgorithm()); + + InputStream decompressed = compressedData.getDataStream(); + InputStream decodeIn = BCPGInputStream.wrap(decompressed); + PGPObjectFactory decFac = implementation.pgpObjectFactory(decodeIn); + OpenPGPMessageInputStream nestedIn = new OpenPGPMessageInputStream(decFac, processor, resultBuilder); + in = nestedIn; + nestedIn.process(); + return; + } + else if (next instanceof PGPEncryptedDataList) + { + // Encrypted Data + PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next; + OpenPGPMessageProcessor.Decrypted decrypted = processor.decrypt(encryptedDataList); + InputStream decryptedIn = decrypted.inputStream; + resultBuilder.encrypted(decrypted); + InputStream decodeIn = BCPGInputStream.wrap(decryptedIn); + PGPObjectFactory decFac = implementation.pgpObjectFactory(decodeIn); + OpenPGPMessageInputStream nestedIn = new OpenPGPMessageInputStream(decFac, processor, resultBuilder); + in = nestedIn; + nestedIn.process(); + return; + } + else + { + processor.onException(new PGPException("Unexpected packet encountered: " + + next.getClass().getName())); + } + } + } + } + + @Override + public void close() + throws IOException + { + in.close(); + + Object next; + while ((next = objectFactory.nextObject()) != null) + { + if (next instanceof PGPSignatureList) + { + // one-pass-signed message (OPS MSG SIG) + PGPSignatureList signatures = (PGPSignatureList) next; + resultBuilder.last().onePassSignatures.addSignatures(signatures); + } + else if (next instanceof PGPPadding) + { + // padded message + } + else if (next instanceof PGPMarker) + { + // postfixed marker packet (ignore) + } + else + { + // unknown/unexpected packet + processor.onException(new PGPException("Unexpected trailing packet encountered: " + + next.getClass().getName())); + } + } + + resultBuilder.verifySignatures(processor); + resultBuilder.closeLayer(); + } + + @Override + public int read() + throws IOException + { + int i = in.read(); + if (i >= 0) + { + layer.onePassSignatures.update(i); + layer.prefixedSignatures.update(i); + } + return i; + } + + @Override + public int read(byte[] b) + throws IOException + { + int i = in.read(b); + if (i >= 0) + { + layer.onePassSignatures.update(b, 0, i); + layer.prefixedSignatures.update(b, 0, i); + } + return i; + } + + @Override + public int read(byte[] b, int off, int len) + throws IOException + { + int i = in.read(b, off, len); + if (i >= 0) + { + layer.onePassSignatures.update(b, off, len); + layer.prefixedSignatures.update(b, off, len); + } + return i; + } + + public Result getResult() + { + return resultBuilder.build(); + } + + public static class Result + { + private final List documentSignatures = new ArrayList<>(); + private OpenPGPCertificate.OpenPGPComponentKey decryptionKey; + private char[] decryptionPassphrase; + private PGPSessionKey sessionKey; + private MessageEncryptionMechanism encryptionMethod = MessageEncryptionMechanism.unencrypted(); + private int compressionAlgorithm = 0; + private String filename; + private char fileFormat; + private Date fileModificationTime; + + private Result(List layers) + { + for (Layer l : layers) + { + if (l.signatures != null) + documentSignatures.addAll(l.signatures); + + if (l.nested instanceof EncryptedData) + { + EncryptedData encryptedData = (EncryptedData) l.nested; + encryptionMethod = encryptedData.encryption; + sessionKey = encryptedData.sessionKey; + decryptionKey = encryptedData.decryptionKey; + decryptionPassphrase = encryptedData.decryptionPassphrase; + } + else if (l.nested instanceof CompressedData) + { + CompressedData compressedData = (CompressedData) l.nested; + compressionAlgorithm = compressedData.compressionAlgorithm; + } + else if (l.nested instanceof LiteralData) + { + LiteralData literalData = (LiteralData) l.nested; + filename = literalData.filename; + fileFormat = literalData.encoding; + fileModificationTime = literalData.modificationTime; + } + } + } + + static Builder builder() + { + return new Builder(); + } + + public MessageEncryptionMechanism getEncryptionMethod() + { + return encryptionMethod; + } + + public OpenPGPCertificate.OpenPGPComponentKey getDecryptionKey() + { + return decryptionKey; + } + + public char[] getDecryptionPassphrase() + { + return decryptionPassphrase; + } + + public PGPSessionKey getSessionKey() + { + return sessionKey; + } + + public int getCompressionAlgorithm() + { + return compressionAlgorithm; + } + + public String getFilename() + { + return filename; + } + + public char getFileFormat() + { + return fileFormat; + } + + public Date getFileModificationTime() + { + return fileModificationTime; + } + + public List getSignatures() + { + return new ArrayList<>(documentSignatures); + } + + static class Builder + { + private final List layers = new ArrayList<>(); + + private Builder() + { + + } + + Layer last() + { + return layers.get(layers.size() - 1); + } + + /** + * Enter a nested OpenPGP packet layer. + * + * @return the new layer + * @throws PGPException if the parser exceeded the maximum nesting depth ({@link #MAX_RECURSION}). + */ + Layer openLayer() + throws PGPException + { + if (layers.size() >= MAX_RECURSION) + { + throw new PGPException("Exceeded maximum packet nesting depth."); + } + Layer layer = new Layer(); + layers.add(layer); + return layer; + } + + /** + * Close a nested OpenPGP packet layer. + */ + void closeLayer() + { + for (int i = layers.size() - 1; i >= 0; i--) + { + Layer l = layers.get(i); + if (l.isOpen()) + { + l.close(); + return; + } + } + } + + /** + * Set the nested packet type of the current layer to {@link CompressedData}. + * + * @param compressionAlgorithm compression algorithm ID + */ + void compressed(int compressionAlgorithm) + { + last().setNested(new CompressedData(compressionAlgorithm)); + } + + /** + * Add One-Pass-Signature packets on the current layer. + * + * @param pgpOnePassSignatures one pass signature packets + */ + void onePassSignatures(PGPOnePassSignatureList pgpOnePassSignatures) + { + last().onePassSignatures.addOnePassSignatures(pgpOnePassSignatures); + } + + /** + * Build the {@link Result}. + * + * @return result + */ + Result build() + { + return new Result(layers); + } + + /** + * Add prefixed signatures on the current layer. + * + * @param prefixedSigs prefixed signatures + */ + void prefixedSignatures(PGPSignatureList prefixedSigs) + { + last().prefixedSignatures.addAll(prefixedSigs); + } + + /** + * Initialize any signatures on the current layer, prefixed and one-pass-signatures. + * + * @param processor message processor + */ + void initSignatures(OpenPGPMessageProcessor processor) + { + last().onePassSignatures.init(processor); + last().prefixedSignatures.init(processor); + } + + /** + * Verify all signatures on the current layer, prefixed and one-pass-signatures. + * + * @param processor message processor + */ + void verifySignatures(OpenPGPMessageProcessor processor) + { + Layer last = last(); + if (last.signatures != null) + { + return; + } + + last.signatures = new ArrayList<>(); + last.signatures.addAll(last.onePassSignatures.verify(processor)); + last.signatures.addAll(last.prefixedSignatures.verify(processor)); + } + + /** + * Set literal data metadata on the current layer. + * + * @param fileName filename + * @param format data format + * @param modificationTime modification time + */ + void literalData(String fileName, char format, Date modificationTime) + { + last().setNested(new LiteralData(fileName, format, modificationTime)); + } + + /** + * Set metadata from an encrypted data packet on the current layer. + * + * @param decrypted decryption result + */ + void encrypted(OpenPGPMessageProcessor.Decrypted decrypted) + { + last().setNested(new EncryptedData(decrypted)); + } + } + } + + static class Layer + { + private final OnePassSignatures onePassSignatures = new OnePassSignatures(); + private final PrefixedSignatures prefixedSignatures = new PrefixedSignatures(); + + private List signatures = null; + + private Nested nested; + private boolean open = true; + + void setNested(Nested nested) + { + this.nested = nested; + } + + void close() + { + this.open = false; + } + + boolean isOpen() + { + return open; + } + } + + static class Nested + { + + } + + static class CompressedData + extends Nested + { + private final int compressionAlgorithm; + + public CompressedData(int algorithm) + { + this.compressionAlgorithm = algorithm; + } + } + + static class LiteralData + extends Nested + { + private final String filename; + private final char encoding; + private final Date modificationTime; + + LiteralData(String filename, char encoding, Date modificationTime) + { + this.filename = filename; + this.encoding = encoding; + this.modificationTime = modificationTime; + } + } + + static class EncryptedData + extends Nested + { + private final OpenPGPCertificate.OpenPGPComponentKey decryptionKey; + private final char[] decryptionPassphrase; + private final PGPSessionKey sessionKey; + private final MessageEncryptionMechanism encryption; + + EncryptedData(OpenPGPMessageProcessor.Decrypted decrypted) + { + this.decryptionKey = decrypted.decryptionKey; + this.decryptionPassphrase = decrypted.decryptionPassphrase; + this.sessionKey = decrypted.sessionKey; + if (decrypted.dataPacket instanceof SymmetricEncIntegrityPacket) + { + SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) decrypted.dataPacket; + if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_2) + { + encryption = MessageEncryptionMechanism.aead( + seipd.getCipherAlgorithm(), seipd.getAeadAlgorithm()); + } + else + { + encryption = MessageEncryptionMechanism.integrityProtected(sessionKey.getAlgorithm()); + } + } + else if (decrypted.dataPacket instanceof AEADEncDataPacket) + { + encryption = MessageEncryptionMechanism.librePgp(sessionKey.getAlgorithm()); + } + else + { + throw new RuntimeException("Unexpected encrypted data packet type: " + decrypted.dataPacket.getClass().getName()); + } + } + } + + static class OnePassSignatures + { + private final List onePassSignatures = new ArrayList<>(); + private final List signatures = new ArrayList<>(); + private final Map issuers = new HashMap<>(); + + OnePassSignatures() + { + + } + + void addOnePassSignatures(PGPOnePassSignatureList onePassSignatures) + { + for (PGPOnePassSignature ops : onePassSignatures) + { + this.onePassSignatures.add(ops); + } + } + + void addSignatures(PGPSignatureList signatures) + { + for (PGPSignature signature : signatures) + { + this.signatures.add(signature); + } + } + + void init(OpenPGPMessageProcessor processor) + { + + for (PGPOnePassSignature ops : onePassSignatures) + { + KeyIdentifier identifier = ops.getKeyIdentifier(); + OpenPGPCertificate cert = processor.provideCertificate(identifier); + if (cert == null) + { + continue; + } + + try + { + OpenPGPCertificate.OpenPGPComponentKey key = cert.getKey(identifier); + issuers.put(ops, key); + ops.init(processor.getImplementation().pgpContentVerifierBuilderProvider(), + key.getPGPPublicKey()); + } + catch (PGPException e) + { + processor.onException(e); + } + } + } + + void update(int i) + { + for (PGPOnePassSignature onePassSignature : onePassSignatures) + { + onePassSignature.update((byte) i); + } + } + + void update(byte[] b, int off, int len) + { + for (PGPOnePassSignature onePassSignature : onePassSignatures) + { + onePassSignature.update(b, off, len); + } + } + + List verify( + OpenPGPMessageProcessor processor) + { + List dataSignatures = new ArrayList<>(); + int num = onePassSignatures.size(); + for (int i = 0; i < signatures.size(); i++) + { + PGPSignature signature = signatures.get(i); + PGPOnePassSignature ops = onePassSignatures.get(num - i - 1); + OpenPGPCertificate.OpenPGPComponentKey key = issuers.get(ops); + if (key == null) + { + continue; + } + + OpenPGPSignature.OpenPGPDocumentSignature dataSignature = + new OpenPGPSignature.OpenPGPDocumentSignature(signature, key); + try + { + dataSignature.verify(ops); + } + catch (PGPException e) + { + processor.onException(e); + } + dataSignatures.add(dataSignature); + } + return dataSignatures; + } + } + + static class PrefixedSignatures + { + private final List prefixedSignatures = new ArrayList<>(); + private final List dataSignatures = new ArrayList<>(); + + PrefixedSignatures() + { + + } + + void addAll(PGPSignatureList signatures) + { + for (PGPSignature signature : signatures) + { + this.prefixedSignatures.add(signature); + } + } + + void init(OpenPGPMessageProcessor processor) + { + for (PGPSignature sig : prefixedSignatures) + { + KeyIdentifier identifier = OpenPGPSignature.getMostExpressiveIdentifier(sig.getKeyIdentifiers()); + if (identifier == null) + { + dataSignatures.add(new OpenPGPSignature.OpenPGPDocumentSignature(sig, null)); + continue; + } + OpenPGPCertificate cert = processor.provideCertificate(identifier); + if (cert == null) + { + dataSignatures.add(new OpenPGPSignature.OpenPGPDocumentSignature(sig, null)); + continue; + } + + OpenPGPCertificate.OpenPGPComponentKey key = cert.getKey(identifier); + OpenPGPSignature.OpenPGPDocumentSignature signature = new OpenPGPSignature.OpenPGPDocumentSignature(sig, key); + dataSignatures.add(signature); + try + { + signature.signature.init( + processor.getImplementation().pgpContentVerifierBuilderProvider(), + cert.getKey(identifier).getPGPPublicKey()); + } + catch (PGPException e) + { + processor.onException(e); + } + } + } + + void update(int i) + { + for(PGPSignature signature : prefixedSignatures) + { + signature.update((byte) i); + } + } + + void update(byte[] buf, int off, int len) + { + for (PGPSignature signature : prefixedSignatures) + { + signature.update(buf, off, len); + } + } + + List verify(OpenPGPMessageProcessor processor) + { + for (OpenPGPSignature.OpenPGPDocumentSignature sig : dataSignatures) + { + try + { + sig.verify(); + } + catch (PGPException e) + { + processor.onException(e); + } + } + return dataSignatures; + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java new file mode 100644 index 0000000000..d52db76900 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java @@ -0,0 +1,470 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPadding; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.util.io.TeeOutputStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Stack; + +/** + * Implementation of an {@link OutputStream} tailored to creating OpenPGP messages. + * Since not all OpenPGP-related OutputStreams forward {@link #close()} calls, we need to keep track of nested streams + * and close them in order. + * This stream can create OpenPGP messages following the following EBNF (which is a subset of the EBNF defined in RFC9580): + *
    + *
  • OpenPGP Message := ASCII-Armor(Optionally Encrypted Message) | Optionally Encrypted Message
  • + *
  • Literal Message := LiteralDataPacket
  • + *
  • Optionally Compressed Message := Literal Message | + * CompressedDataPacket(Literal Message)
  • + *
  • Optionally Signed Message := Optionally Compressed Message | + * OnePassSignaturePacket + Optionally Signed Message + SignaturePacket
  • + *
  • Optionally Padded Message := Optionally Signed Message | Optionally Signed Message + PaddingPacket
  • + *
  • Encrypted Message := SEIPDv1(Optionally Padded Message) | + * SEIPDv2(Optionally Padded Message) | + * OED(Optionally Padded Message)
  • + *
  • Optionally Encrypted Message := Optionally Padded Message | Encrypted Message
  • + *
+ * Therefore, this stream is capable of creating a wide variety of OpenPGP message, from simply + * LiteralDataPacket-wrapped plaintext over signed messages to encrypted, signed and padded messages. + * The following considerations lead to why this particular subset was chosen: + *
    + *
  • An encrypted message is not distinguishable from randomness, so it makes no sense to compress it
  • + *
  • Since signatures also consist of data which is not distinguishable from randomness, + * it makes no sense to compress them either
  • + *
  • Padding packets are used to prevent traffic analysis. + * Since they contain random data, we do not compress them. + * If a message is encrypted, we want to encrypt the padding packet to prevent an intermediate from stripping it
  • + *
  • Since (v4) signatures leak some metadata about the message plaintext (the hash and the issuer), + * for encrypted messages we always place signatures inside the encryption container (sign-then-encrypt)
  • + *
  • Prefix-signed messages (where a SignaturePacket is prefixed to an OpenPGP message) are + * inferior to One-Pass-Signed messages, so this stream cannot produce those.
  • + *
  • Messages using the Cleartext-Signature Framework are "different enough" to deserve their own + * message generator / stream.
  • + *
+ */ +public class OpenPGPMessageOutputStream + extends OutputStream +{ + // sink for the OpenPGP message + private final OutputStream baseOut; // non-null + + private final OutputStream armorOut; // nullable + private final OutputStream encodeOut; // non-null + private final OutputStream encryptOut; // nullable + private final OutputStream paddingOut; // nullable + private final OutputStream signOut; // nullable + private final OutputStream compressOut; // nullable + private final OutputStream literalOut; // non-null + + // pipe plaintext data into this stream + private final OutputStream plaintextOut; // non-null + + OpenPGPMessageOutputStream(OutputStream baseOut, Builder builder) + throws PGPException, IOException + { + this.baseOut = baseOut; + OutputStream innermostOut = baseOut; + + // ASCII ARMOR + if (builder.armorFactory != null) + { + armorOut = builder.armorFactory.get(innermostOut); + innermostOut = armorOut; + } + else + { + armorOut = null; + } + + // BCPG (decide packet length encoding format) + encodeOut = new BCPGOutputStream(innermostOut, PacketFormat.CURRENT); + innermostOut = encodeOut; + + // ENCRYPT + if (builder.encryptionStreamFactory != null) + { + encryptOut = builder.encryptionStreamFactory.get(innermostOut); + innermostOut = encryptOut; + } + else + { + encryptOut = null; + } + + // PADDING + if (builder.paddingStreamFactory != null) + { + paddingOut = builder.paddingStreamFactory.get(innermostOut); + innermostOut = paddingOut; + } + else + { + paddingOut = null; + } + + // SIGN + if (builder.signatureStreamFactory != null) + { + signOut = builder.signatureStreamFactory.get(innermostOut); + // signOut does not forward write() calls down, so we do *not* set innermostOut to it + } + else + { + signOut = null; + } + + // COMPRESS + if (builder.compressionStreamFactory != null) + { + compressOut = builder.compressionStreamFactory.get(innermostOut); + innermostOut = compressOut; + } + else + { + compressOut = null; + } + + // LITERAL DATA + if (builder.literalDataStreamFactory == null) + { + throw new PGPException("Missing instructions for LiteralData encoding."); + } + literalOut = builder.literalDataStreamFactory.get(innermostOut); + + if (signOut != null) + { + this.plaintextOut = new TeeOutputStream(literalOut, signOut); + } + else + { + this.plaintextOut = literalOut; + } + } + + @Override + public void write(int i) + throws IOException + { + plaintextOut.write(i); + } + + @Override + public void write(byte[] b) + throws IOException + { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) + throws IOException + { + plaintextOut.write(b, off, len); + } + + @Override + public void flush() + throws IOException + { + literalOut.flush(); + if (compressOut != null) + { + compressOut.flush(); + } + if (signOut != null) + { + signOut.flush(); + } + if (paddingOut != null) + { + paddingOut.flush(); + } + if (encryptOut != null) + { + encryptOut.flush(); + } + encodeOut.flush(); + if (armorOut != null) + { + armorOut.flush(); + } + baseOut.flush(); + } + + @Override + public void close() + throws IOException + { + literalOut.close(); + if (compressOut != null) + { + compressOut.close(); + } + if (signOut != null) + { + signOut.close(); + } + if (paddingOut != null) + { + paddingOut.close(); + } + if (encryptOut != null) + { + encryptOut.close(); + } + encodeOut.close(); + if (armorOut != null) + { + armorOut.close(); + } + baseOut.close(); + } + + /** + * Factory class for wrapping output streams. + */ + public interface OutputStreamFactory + { + /** + * Wrap the given base stream with another {@link OutputStream} and return the result. + * @param base base output stream + * @return wrapped output stream + * @throws PGPException if the wrapping stream cannot be instantiated + */ + OutputStream get(OutputStream base) throws PGPException, IOException; + } + + static Builder builder() + { + return new Builder(); + } + + /** + * Builder class for {@link OpenPGPMessageOutputStream} instances. + */ + static class Builder + { + private OpenPGPMessageGenerator.ArmoredOutputStreamFactory armorFactory; + private OutputStreamFactory paddingStreamFactory; + private OutputStreamFactory encryptionStreamFactory; + private OutputStreamFactory signatureStreamFactory; + private OutputStreamFactory compressionStreamFactory; + private OutputStreamFactory literalDataStreamFactory; + + /** + * Specify a factory for ASCII armoring the message. + * + * @param factory armor stream factory + * @return this + */ + public Builder armor(OpenPGPMessageGenerator.ArmoredOutputStreamFactory factory) + { + this.armorFactory = factory; + return this; + } + + /** + * Specify a factory for encrypting the message. + * + * @param factory encryption stream factory + * @return this + */ + public Builder encrypt(OutputStreamFactory factory) + { + this.encryptionStreamFactory = factory; + return this; + } + + /** + * Specify a factory for padding the message. + * + * @param factory padding stream factory + * @return this + */ + public Builder padding(OutputStreamFactory factory) + { + this.paddingStreamFactory = factory; + return this; + } + + /** + * Specify a factory for signing the message. + * + * @param factory signing stream factory + * @return this + */ + public Builder sign(OutputStreamFactory factory) + { + this.signatureStreamFactory = factory; + return this; + } + + /** + * Specify a factory for compressing the message. + * ' + * @param factory compression stream factory + * @return this + */ + public Builder compress(OutputStreamFactory factory) + { + this.compressionStreamFactory = factory; + return this; + } + + /** + * Specify a factory for literal-data-wrapping the message. + * + * @param factory literal data stream factory + * @return this + */ + public Builder literalData(OutputStreamFactory factory) + { + this.literalDataStreamFactory = factory; + return this; + } + + /** + * Construct the {@link OpenPGPMessageOutputStream} over the base output stream. + * + * @param baseOut base output stream + * @return OpenPGP message output stream + * @throws PGPException if a stream cannot be created + * @throws IOException if a signature cannot be generated + */ + public OpenPGPMessageOutputStream build(OutputStream baseOut) + throws PGPException, IOException + { + return new OpenPGPMessageOutputStream(baseOut, this); + } + } + + /** + * OutputStream which updates {@link PGPSignatureGenerator} instances with data that is written to it. + * Note: Data written to this stream will *NOT* be forwarded to the underlying {@link OutputStream}. + * Once {@link #close()} is called, it will generate {@link PGPSignature} objects from the generators and write + * those to the underlying {@link OutputStream}. + */ + static class SignatureGeneratorOutputStream + extends OutputStream + { + + private final OutputStream out; + private final Stack signatureGenerators; + + public SignatureGeneratorOutputStream(OutputStream out, Stack signatureGenerators) + { + this.out = out; + this.signatureGenerators = signatureGenerators; + } + + @Override + public void write(int i) + throws IOException + { + for (PGPSignatureGenerator sigGen : signatureGenerators) + { + sigGen.update((byte) i); + } + } + + @Override + public void write(byte[] b) + throws IOException + { + for (PGPSignatureGenerator sigGen : signatureGenerators) + { + sigGen.update(b); + } + } + + @Override + public void write(byte[] b, int off, int len) + throws IOException + { + for (PGPSignatureGenerator sigGen : signatureGenerators) + { + sigGen.update(b, off, len); + } + } + + @Override + public void close() + throws IOException + { + while (!signatureGenerators.isEmpty()) + { + PGPSignatureGenerator gen = signatureGenerators.pop(); + PGPSignature sig = null; + try + { + sig = gen.generate(); + } + catch (PGPException e) + { + throw new RuntimeException(e); + } + sig.encode(out); + } + } + } + + /** + * OutputStream which appends a {@link org.bouncycastle.bcpg.PaddingPacket} to the data + * once {@link #close()} is called. + */ + static class PaddingPacketAppenderOutputStream + extends OutputStream + { + private final OutputStream out; + private final PaddingPacketFactory packetFactory; + + public PaddingPacketAppenderOutputStream(OutputStream out, PaddingPacketFactory packetFactory) + { + this.out = out; + this.packetFactory = packetFactory; + } + + @Override + public void write(byte[] b) + throws IOException + { + out.write(b); + } + + @Override + public void write(byte[] b, int off, int len) + throws IOException + { + out.write(b, off, len); + } + + @Override + public void write(int i) + throws IOException + { + out.write(i); + } + + @Override + public void close() + throws IOException + { + packetFactory.providePaddingPacket().encode(out); + out.close(); + } + } + + /** + * Factory interface for creating PGPPadding objects. + */ + public interface PaddingPacketFactory + { + PGPPadding providePaddingPacket(); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java new file mode 100644 index 0000000000..b5f0288f13 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -0,0 +1,500 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.InputStreamPacket; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPEncryptedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPBEEncryptedData; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +import org.bouncycastle.util.Arrays; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class OpenPGPMessageProcessor +{ + private final OpenPGPImplementation implementation; + private final Configuration configuration; + + /** + * Create a new {@link OpenPGPMessageProcessor} using the default {@link OpenPGPImplementation}. + */ + public OpenPGPMessageProcessor() + { + this(OpenPGPImplementation.getInstance()); + } + + /** + * Create a new {@link OpenPGPMessageProcessor} using the given {@link OpenPGPImplementation}. + * + * @param implementation openpgp implementation + */ + public OpenPGPMessageProcessor(OpenPGPImplementation implementation) + { + this.implementation = implementation; + this.configuration = new Configuration(); + } + + /** + * Add an {@link OpenPGPCertificate} for signature verification. + * If the message contains any signatures, the provided certificate will be considered as a candidate to verify + * the signature. + * + * @param issuerCertificate OpenPGP certificate + * @return this + */ + public OpenPGPMessageProcessor addVerificationCertificate(OpenPGPCertificate issuerCertificate) + { + configuration.certificatePool.addItem(issuerCertificate); + return this; + } + + /** + * Add an {@link OpenPGPKey} as potential decryption key. + * If the message is encrypted for an {@link OpenPGPKey}, this key can be tried to decrypt the message. + * Keys added via this method will also be available for message decryption if the message was encrypted + * to an anonymous recipient (wildcard key-id / fingerprint). + * + * @param key OpenPGP key + * @return this + */ + public OpenPGPMessageProcessor addDecryptionKey(OpenPGPKey key) + { + configuration.keyPool.addItem(key); + return this; + } + + /** + * Add an {@link OpenPGPKey} as potential decryption key, along with a {@link KeyPassphraseProvider} dedicated + * to this key. + * If the message is encrypted for an {@link OpenPGPKey}, this key can be tried to decrypt the message. + * Keys added via this method will also be available for message decryption if the message was encrypted + * to an anonymous recipient (wildcard key-id / fingerprint). + * + * @param key OpenPGP key + * @return this + */ + public OpenPGPMessageProcessor addDecryptionKey(OpenPGPKey key, char[] passphrase) + { + configuration.keyPool.addItem(key); + configuration.keyPassphraseProvider.addPassphrase(key, passphrase); + return this; + } + + /** + * Add a passphrase for secret key decryption. + * If the corresponding {@link OpenPGPKey} which key this passphrase is for is known in advance, + * it is highly advised to call {@link #addDecryptionKey(OpenPGPKey, char[])} instead, due to performance reasons. + * + * @param passphrase key-passphrase + * @return this + */ + public OpenPGPMessageProcessor addDecryptionKeyPassphrase(char[] passphrase) + { + configuration.keyPassphraseProvider.addPassphrase(passphrase); + return this; + } + + /** + * Set a provider for dynamically requesting missing passphrases used to unlock encrypted + * {@link OpenPGPKey OpenPGPKeys}. + * This provider is called, if a key cannot be unlocked using any passphrase provided via + * {@link #addDecryptionKey(OpenPGPKey, char[])}. + * + * @param keyPassphraseProvider key passphrase provider + * @return this + */ + public OpenPGPMessageProcessor setMissingOpenPGPKeyPassphraseProvider( + KeyPassphraseProvider keyPassphraseProvider) + { + this.configuration.keyPassphraseProvider.setMissingPassphraseCallback(keyPassphraseProvider); + return this; + } + + /** + * Set a {@link OpenPGPKeyMaterialProvider.OpenPGPCertificateProvider} to allow dynamic requesting certificates + * for signature verification. + * This provider is called if the requested {@link OpenPGPCertificate} has not yet been added explicitly + * via {@link #addVerificationCertificate(OpenPGPCertificate)}. + * This allows lazily requesting verification certificates at runtime. + * + * @param certificateProvider provider for OpenPGP certificates + * @return this + */ + public OpenPGPMessageProcessor setMissingOpenPGPCertificateProvider( + OpenPGPKeyMaterialProvider.OpenPGPCertificateProvider certificateProvider) + { + configuration.certificatePool.setMissingItemCallback(certificateProvider); + return this; + } + + /** + * Set a provider for {@link OpenPGPKey OpenPGPKeys}, which can be used to decrypt encrypted messages. + * This provider is called if an {@link OpenPGPKey} required to decrypt the message has not yet been + * explicitly added via {@link #addDecryptionKey(OpenPGPKey)}. + * This allows lazily requesting decryption keys at runtime. + * + * @param keyProvider provider for OpenPGP keys + * @return this + */ + public OpenPGPMessageProcessor setMissingOpenPGPKeyProvider( + OpenPGPKeyMaterialProvider.OpenPGPKeyProvider keyProvider) + { + configuration.keyPool.setMissingItemCallback(keyProvider); + return this; + } + + /** + * Set a passphrase to decrypt a symmetrically encrypted OpenPGP message. + * + * @param messagePassphrase passphrase for message decryption + * @return this + */ + public OpenPGPMessageProcessor addMessagePassphrase(char[] messagePassphrase) + { + this.configuration.addMessagePassphrase(messagePassphrase); + return this; + } + + /** + * Set a {@link MissingPassphraseCallback} which will be invoked if the message is encrypted using a passphrase, + * but no working passphrase was provided. + * + * @param callback callback + * @return this + */ + public OpenPGPMessageProcessor setMissingMessagePassphraseCallback( + MissingPassphraseCallback callback) + { + this.configuration.missingMessagePassphraseCallback = callback; + return this; + } + + /** + * Set a {@link PGPSessionKey} with which an encrypted OpenPGP message can be decrypted without the need for + * using a private key or passphrase. + * Typically, this method can be used, if the {@link PGPSessionKey} of a message is already known (e.g. because + * the message has already been decrypted before). + * The benefit of this is, that public-key operations can be costly. + * + * @param sessionKey session key + * @return this + */ + public OpenPGPMessageProcessor setSessionKey(PGPSessionKey sessionKey) + { + configuration.sessionKey = sessionKey; + return this; + } + + /** + * Process an OpenPGP message. + * + * @param messageIn input stream of the OpenPGP message + * @return plaintext input stream + * @throws IOException + * @throws PGPException + */ + public OpenPGPMessageInputStream process(InputStream messageIn) + throws IOException, PGPException + { + // Remove potential ASCII armoring + InputStream packetInputStream = PGPUtil.getDecoderStream(messageIn); + + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(packetInputStream); + OpenPGPMessageInputStream in = new OpenPGPMessageInputStream(objectFactory, this); + in.process(); + return in; + } + + /** + * Bundle together metadata about the decryption result. + * That includes the encrypted data packet itself, the passphrase or (sub-)key that was used to decrypt the + * session-key, the session-key itself and lastly the resulting decrypted packet input stream. + */ + static class Decrypted + { + final InputStream inputStream; + final PGPSessionKey sessionKey; + final InputStreamPacket dataPacket; + OpenPGPCertificate.OpenPGPComponentKey decryptionKey; + char[] decryptionPassphrase; + + public Decrypted(InputStreamPacket encryptedData, + PGPSessionKey decryptedSessionKey, + InputStream decryptedIn) + { + this.dataPacket = encryptedData; + this.sessionKey = decryptedSessionKey; + this.inputStream = decryptedIn; + } + } + + /** + * Decrypt an encrypted data packet by trying passphrases and/or decryption keys. + * + * @param encDataList encrypted data + * @return decrypted data + * @throws PGPException in case of an error + */ + Decrypted decrypt(PGPEncryptedDataList encDataList) + throws PGPException + { + // Since decryption using session key is the most "deliberate" and "specific", we'll try that first + if (configuration.sessionKey != null) + { + // decrypt with provided session key + SessionKeyDataDecryptorFactory decryptorFactory = + implementation.sessionKeyDataDecryptorFactory(configuration.sessionKey); + InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() + .getDataStream(decryptorFactory); + + return new Decrypted(encDataList.getEncryptedData(), configuration.sessionKey, decryptedIn); + } + + List skesks = skesks(encDataList); + List pkesks = pkesks(encDataList); + + PGPException exception = null; + + // If the user explicitly provided a message passphrase, we'll try that next + if (!skesks.isEmpty() && !configuration.messagePassphrases.isEmpty()) + { + for (PGPPBEEncryptedData skesk : skesks) + { + for (char[] passphrase : configuration.messagePassphrases) + { + try + { + // Extract message session key with passphrase + PBEDataDecryptorFactory passphraseDecryptorFactory = + implementation.pbeDataDecryptorFactory(passphrase); + PGPSessionKey decryptedSessionKey = skesk.getSessionKey(passphraseDecryptorFactory); + + // Decrypt the message with the decrypted session key + SessionKeyDataDecryptorFactory skDecryptorFactory = + implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); + InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() + .getDataStream(skDecryptorFactory); + + Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + decrypted.decryptionPassphrase = passphrase; + + return decrypted; + } + catch (PGPException e) + { + onException(e); + // cache first exception, then continue to try next skesk if present + exception = exception != null ? exception : e; + } + } + } + } + + // Then we'll try decryption using secret key(s) + for (PGPPublicKeyEncryptedData pkesk : pkesks) + { + KeyIdentifier identifier = pkesk.getKeyIdentifier(); + OpenPGPKey key = configuration.keyPool.provide(identifier); + if (key == null) + { + continue; + } + + OpenPGPKey.OpenPGPSecretKey decryptionKey = key.getSecretKeys().get(identifier); + if (decryptionKey == null) + { + continue; + } + + try + { + if (!decryptionKey.isEncryptionKey()) + { + throw new PGPException("Key is not an encryption key and can therefore not decrypt."); + } + + char[] keyPassphrase = configuration.keyPassphraseProvider.getKeyPassword(decryptionKey); + PGPPrivateKey privateKey = decryptionKey.unlock(keyPassphrase); + + // Decrypt the message session key using the private key + PublicKeyDataDecryptorFactory pkDecryptorFactory = + implementation.publicKeyDataDecryptorFactory(privateKey); + PGPSessionKey decryptedSessionKey = pkesk.getSessionKey(pkDecryptorFactory); + + // Decrypt the message using the decrypted session key + SessionKeyDataDecryptorFactory skDecryptorFactory = + implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); + InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() + .getDataStream(skDecryptorFactory); + Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + decrypted.decryptionKey = decryptionKey; + return decrypted; + } + catch (PGPException e) + { + onException(e); + } + } + + // And lastly, we'll prompt the user dynamically for a message passphrase + if (!skesks.isEmpty() && configuration.missingMessagePassphraseCallback != null) + { + char[] passphrase; + while ((passphrase = configuration.missingMessagePassphraseCallback.getPassphrase()) != null) + { + for (PGPPBEEncryptedData skesk : skesks) + { + try + { + // Decrypt the message session key using a passphrase + PBEDataDecryptorFactory passphraseDecryptorFactory = implementation.pbeDataDecryptorFactory(passphrase); + PGPSessionKey decryptedSessionKey = skesk.getSessionKey(passphraseDecryptorFactory); + + // Decrypt the data using the decrypted session key + SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); + InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData().getDataStream(skDecryptorFactory); + Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + decrypted.decryptionPassphrase = passphrase; + return decrypted; + } + catch (PGPException e) + { + onException(e); + // cache first exception, then continue to try next skesk if present + exception = exception != null ? exception : e; + } + } + } + + if (exception != null) + { + throw exception; + } + } + + throw new PGPException("No working decryption method found."); + } + + /** + * Return all symmetric-key-encrypted-session-key (SKESK) packets leading the encrypted data packet. + * + * @param encDataList encrypted data list + * @return list of skesk packets (might be empty) + */ + private List skesks(PGPEncryptedDataList encDataList) + { + List list = new ArrayList<>(); + for (PGPEncryptedData encData : encDataList) + { + if (encData instanceof PGPPBEEncryptedData) + { + list.add((PGPPBEEncryptedData) encData); + } + } + return list; + } + + /** + * Return all public-key-encrypted-session-key (PKESK) packets leading the encrypted data packet. + * + * @param encDataList encrypted data list + * @return list of pkesk packets (might be empty) + */ + private List pkesks(PGPEncryptedDataList encDataList) + { + List list = new ArrayList<>(); + for (PGPEncryptedData encData : encDataList) + { + if (encData instanceof PGPPublicKeyEncryptedData) + { + list.add((PGPPublicKeyEncryptedData) encData); + } + } + return list; + } + + OpenPGPCertificate provideCertificate(KeyIdentifier identifier) + { + return configuration.certificatePool.provide(identifier); + } + + OpenPGPImplementation getImplementation() + { + return implementation; + } + + /** + * Method that can be called if a {@link PGPException} is thrown. + * If the user provided a {@link PGPExceptionCallback} ({@link Configuration#exceptionCallback} is not null), + * the exception will be passed along to that callback. + * Otherwise, nothing happens. + * + * @param e exception + */ + void onException(PGPException e) + { + if (configuration.exceptionCallback != null) + { + configuration.exceptionCallback.onException(e); + } + } + + public static class Configuration + { + private final OpenPGPKeyMaterialPool.OpenPGPCertificatePool certificatePool; + private final OpenPGPKeyMaterialPool.OpenPGPKeyPool keyPool; + private final KeyPassphraseProvider.DefaultKeyPassphraseProvider keyPassphraseProvider; + public final List messagePassphrases = new ArrayList<>(); + private MissingPassphraseCallback missingMessagePassphraseCallback; + private PGPExceptionCallback exceptionCallback = null; + private PGPSessionKey sessionKey; + + public Configuration() + { + this.certificatePool = new OpenPGPKeyMaterialPool.OpenPGPCertificatePool(); + this.keyPool = new OpenPGPKeyMaterialPool.OpenPGPKeyPool(); + this.keyPassphraseProvider = new KeyPassphraseProvider.DefaultKeyPassphraseProvider(); + } + + /** + * Add a passphrase that will be tried when a symmetric-key-encrypted-session-key packet is found + * during the decryption process. + * + * @param messagePassphrase passphrase to decrypt the message with + * @return this + */ + public Configuration addMessagePassphrase(char[] messagePassphrase) + { + boolean found = false; + for (char[] existing : messagePassphrases) + { + found |= Arrays.areEqual(existing, messagePassphrase); + } + + if (!found) + { + messagePassphrases.add(messagePassphrase); + } + return this; + } + } + + /** + * Callback to handle {@link PGPException PGPExceptions}. + */ + public interface PGPExceptionCallback + { + void onException(PGPException e); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java new file mode 100644 index 0000000000..fa9f0406af --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java @@ -0,0 +1,19 @@ +package org.bouncycastle.openpgp.api; + +import java.util.HashSet; +import java.util.Set; + +public class OpenPGPNotationRegistry +{ + private final Set knownNotations = new HashSet<>(); + + public boolean isNotationKnown(String notationName) + { + return knownNotations.contains(notationName); + } + + public void addKnownNotation(String notationName) + { + this.knownNotations.add(notationName); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java new file mode 100644 index 0000000000..c6c1cbb19f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -0,0 +1,535 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.SignaturePacket; +import org.bouncycastle.bcpg.SignatureSubpacket; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPOnePassSignature; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; +import org.bouncycastle.openpgp.api.util.UTCUtil; +import org.bouncycastle.util.encoders.Hex; + +import java.util.Date; +import java.util.List; + +/** + * An OpenPGP signature. + * This is a wrapper around {@link PGPSignature} which tracks the verification state of the signature. + */ +public abstract class OpenPGPSignature +{ + protected final PGPSignature signature; + protected final OpenPGPCertificate.OpenPGPComponentKey issuer; + protected boolean isTested = false; + protected boolean isCorrect = false; + + /** + * Create an {@link OpenPGPSignature}. + * + * @param signature signature + * @param issuer issuer subkey + */ + public OpenPGPSignature(PGPSignature signature, OpenPGPCertificate.OpenPGPComponentKey issuer) + { + this.signature = signature; + this.issuer = issuer; + } + + /** + * Return the {@link PGPSignature}. + * + * @return signature + */ + public PGPSignature getSignature() + { + return signature; + } + + /** + * Return the {@link OpenPGPCertificate.OpenPGPComponentKey} subkey that issued this signature. + * This method might return null, if the issuer certificate is not available. + * + * @return issuer subkey or null + */ + public OpenPGPCertificate.OpenPGPComponentKey getIssuer() + { + return issuer; + } + + /** + * Return the {@link OpenPGPCertificate} that contains the subkey that issued this signature. + * This method might return null if the issuer certificate is not available + * + * @return issuer certificate or null + */ + public OpenPGPCertificate getIssuerCertificate() + { + return issuer != null ? issuer.getCertificate() : null; + } + + /** + * Return a {@link List} of possible {@link KeyIdentifier} candidates. + * + * @return key identifier candidates + */ + public List getKeyIdentifiers() + { + return signature.getKeyIdentifiers(); + } + + /** + * Return the most expressive {@link KeyIdentifier} from available candidates. + * + * @return most expressive key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + List identifiers = getKeyIdentifiers(); + return getMostExpressiveIdentifier(identifiers); + } + + /** + * Return the most expressive issuer {@link KeyIdentifier}. + * Due to historic reasons, signatures MAY contain more than one issuer packet, which might contain inconsistent + * information (issuer key-ids / issuer fingerprints). + * Throw wildcards (anonymous issuers) into the mix, and it becomes apparent, that there needs to be a way to + * select the "best" issuer identifier. + * If there are more than one issuer packet, this method returns the most expressive (prefer fingerprints over + * key-ids, prefer non-wildcard over wildcard) and returns that. + * + * @param identifiers list of available identifiers + * @return the best identifier + */ + public static KeyIdentifier getMostExpressiveIdentifier(List identifiers) + { + if (identifiers.isEmpty()) + { + // none + return null; + } + if (identifiers.size() == 1) + { + // single + return identifiers.get(0); + } + + // Find most expressive identifier + for (KeyIdentifier identifier : identifiers) + { + // non-wildcard and has fingerprint + if (!identifier.isWildcard() && identifier.getFingerprint() != null) + { + return identifier; + } + } + + // Find non-wildcard identifier + for (KeyIdentifier identifier : identifiers) + { + // non-wildcard (and no fingerprint) + if (!identifier.isWildcard()) + { + return identifier; + } + } + // else return first identifier + return identifiers.get(0); + } + + /** + * Return true, if this signature has been tested and is correct. + * + * @return true if the signature is tested and is correct, false otherwise + */ + public boolean isTestedCorrect() + { + return isTested && isCorrect; + } + + /** + * Return the creation time of the signature. + * + * @return signature creation time + */ + public Date getCreationTime() + { + return signature.getCreationTime(); + } + + /** + * Return the expiration time of the signature. + * If no expiration time was included (or if the signature was explicitly marked as non-expiring), + * return null, otherwise return the time of expiration. + * The signature is no longer valid, once the expiration time is exceeded. + * + * @return expiration time + */ + public Date getExpirationTime() + { + PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); + if (hashed == null) + { + // v3 sigs have no expiration + return null; + } + long exp = hashed.getSignatureExpirationTime(); + if (exp < 0) + { + throw new RuntimeException("Negative expiration time"); + } + + if (exp == 0L) + { + // Explicit or implicit no expiration + return null; + } + + return new Date(getCreationTime().getTime() + 1000 * exp); + } + + /** + * Return true, if the signature is not a hard revocation, and if the evaluation time falls into the period + * between signature creation time and expiration or revocation. + * + * @param evaluationTime time for which you want to determine effectiveness of the signature + * @return true if the signature is effective at the given evaluation time + */ + public boolean isEffectiveAt(Date evaluationTime) + { + if (isHardRevocation()) + { + // hard revocation is valid at all times + return true; + } + + // creation <= eval < expiration + Date creation = getCreationTime(); + Date expiration = getExpirationTime(); + return !evaluationTime.before(creation) && (expiration == null || evaluationTime.before(expiration)); + } + + /** + * Return true, if this signature is a hard revocation. + * Contrary to soft revocations (the key / signature / user-id was gracefully retired), a hard revocation + * has a serious reason, like key compromise, or no reason at all. + * Hard revocations invalidate the key / signature / user-id retroactively, while soft revocations only + * invalidate from the time of revocation signature creation onwards. + * + * @return true if the signature is a hard revocation + */ + public boolean isHardRevocation() + { + return signature.isHardRevocation(); + } + + /** + * Return true, if this signature is a certification. + * Certification signatures are used to bind user-ids to a key. + * + * @return true if the signature is a certification + */ + public boolean isCertification() + { + return signature.isCertification(); + } + + + /** + * Check certain requirements for OpenPGP signatures. + * + * @param issuer signature issuer + * @throws MalformedPGPSignatureException if the signature is malformed + */ + void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer) + throws MalformedPGPSignatureException + { + PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); + if (hashed == null) + { + throw new MalformedPGPSignatureException("Missing hashed signature subpacket area."); + } + PGPSignatureSubpacketVector unhashed = signature.getUnhashedSubPackets(); + + if (hashed.getSignatureCreationTime() == null) + { + // Signatures MUST have hashed creation time subpacket + throw new MalformedPGPSignatureException("Signature does not have a hashed SignatureCreationTime subpacket."); + } + + if (hashed.getSignatureCreationTime().before(issuer.getCreationTime())) + { + throw new MalformedPGPSignatureException("Signature predates issuer key creation time."); + } + + for (NotationData notation : hashed.getNotationDataOccurrences()) + { + if (notation.isCritical()) + { + throw new MalformedPGPSignatureException("Critical unknown NotationData encountered: " + notation.getNotationName()); + } + } + + for (SignatureSubpacket unknownSubpacket : hashed.toArray()) + { + // SignatureSubpacketInputStream returns unknown subpackets as SignatureSubpacket + if (unknownSubpacket.isCritical() && + unknownSubpacket.getClass().equals(SignatureSubpacket.class)) + { + throw new MalformedPGPSignatureException("Critical hashed unknown SignatureSubpacket encountered: " + + unknownSubpacket.getType()); + } + } + + switch (signature.getVersion()) + { + case SignaturePacket.VERSION_4: + if (hashed.getIssuerFingerprint() == null && + unhashed.getIssuerFingerprint() == null && + hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null && + unhashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null) + { + throw new MalformedPGPSignatureException("Missing IssuerKeyID and IssuerFingerprint subpacket."); + } + break; + + case SignaturePacket.VERSION_5: + // TODO: Implement + break; + + case SignaturePacket.VERSION_6: + if (hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) != null) + { + throw new MalformedPGPSignatureException("v6 signature MUST NOT contain IssuerKeyID subpacket."); + } + if (hashed.getIssuerFingerprint() == null && unhashed.getIssuerFingerprint() == null) + { + throw new MalformedPGPSignatureException("v6 signature MUST contain IssuerFingerprint subpacket."); + } + break; + + default: + } + } + + /** + * Return true, if this signature is a revocation, false otherwise. + * @return true if signature is revocation + */ + public boolean isRevocation() + { + return PGPSignature.isRevocation(signature.getSignatureType()); + } + + @Override + public String toString() + { + String issuerInfo = getIssuerDisplay(); + String period = UTCUtil.format(getCreationTime()) + + (getExpirationTime() == null ? "" : ">" + UTCUtil.format(getExpirationTime())); + String validity = isTested ? (isCorrect ? "✓" : "✗") : "❓"; + // -DM Hex.toHexString + return getType() + (signature.isHardRevocation() ? "(hard)" : "") + " " + Hex.toHexString(signature.getDigestPrefix()) + + " " + issuerInfo + " -> " + getTargetDisplay() + " (" + period + ") " + validity; + } + + protected String getIssuerDisplay() + { + if (issuer != null) + { + return issuer.toString(); + } + + KeyIdentifier issuerIdentifier = getKeyIdentifier(); + if (issuerIdentifier == null) + { + return "External[unknown]"; + } + + if (issuerIdentifier.isWildcard()) + { + return "Anonymous"; + } + return "External[" + Long.toHexString(issuerIdentifier.getKeyId()).toUpperCase() + "]"; + } + + protected abstract String getTargetDisplay(); + + protected String getType() + { + switch (signature.getSignatureType()) + { + case PGPSignature.BINARY_DOCUMENT: + return "BINARY_DOCUMENT"; + case PGPSignature.CANONICAL_TEXT_DOCUMENT: + return "CANONICAL_TEXT_DOCUMENT"; + case PGPSignature.STAND_ALONE: + return "STANDALONE"; + case PGPSignature.DEFAULT_CERTIFICATION: + return "DEFAULT_CERTIFICATION"; + case PGPSignature.NO_CERTIFICATION: + return "NO_CERTIFICATION"; + case PGPSignature.CASUAL_CERTIFICATION: + return "CASUAL_CERTIFICATION"; + case PGPSignature.POSITIVE_CERTIFICATION: + return "POSITIVE_CERTIFICATION"; + case PGPSignature.SUBKEY_BINDING: + return "SUBKEY_BINDING"; + case PGPSignature.PRIMARYKEY_BINDING: + return "PRIMARYKEY_BINDING"; + case PGPSignature.DIRECT_KEY: + return "DIRECT_KEY"; + case PGPSignature.KEY_REVOCATION: + return "KEY_REVOCATION"; + case PGPSignature.SUBKEY_REVOCATION: + return "SUBKEY_REVOCATION"; + case PGPSignature.CERTIFICATION_REVOCATION: + return "CERTIFICATION_REVOCATION"; + case PGPSignature.TIMESTAMP: + return "TIMESTAMP"; + case PGPSignature.THIRD_PARTY_CONFIRMATION: + return "THIRD_PARTY_CONFIRMATION"; + default: + return "UNKNOWN (" + signature.getSignatureType() + ")"; + } + } + + /** + * An {@link OpenPGPSignature} made over a binary or textual document (e.g. a message). + * Also known as a Data Signature. + * An {@link OpenPGPDocumentSignature} CANNOT live on a {@link OpenPGPCertificate}. + */ + public static class OpenPGPDocumentSignature + extends OpenPGPSignature + { + protected final OpenPGPDocumentSignature attestedSignature; + + /** + * Create a document signature of level 0 (signature is made directly over the document). + * + * @param signature signature + * @param issuer public issuer-signing-key-component (or null if not available) + */ + public OpenPGPDocumentSignature(PGPSignature signature, OpenPGPCertificate.OpenPGPComponentKey issuer) + { + super(signature, issuer); + this.attestedSignature = null; + } + + @Override + protected String getTargetDisplay() + { + return ""; + } + + /** + * Create a document signature of level greater than 0 (signature is made as an attestation over + * other signature(s) + document). + * If the attested signature is itself an attestation, it will recursively contain its attested signature. + * + * @param signature attestation signature + * @param issuer public issuer signing-key-component (or null if not available) + * @param attestedSignature the attested signature + */ + public OpenPGPDocumentSignature(PGPSignature signature, + OpenPGPCertificate.OpenPGPComponentKey issuer, + OpenPGPDocumentSignature attestedSignature) + { + super(signature, issuer); + this.attestedSignature = attestedSignature; + } + + /** + * Return the signature attestation level of this signature. + * If this signature was created directly over a document, this method returns 0. + * A level greater than 0 indicates that the signature is an attestation over at least one other signature. + * + * @return signature attestation level + */ + public int getSignatureLevel() + { + if (attestedSignature == null) + { + return 0; // signature over data + } + else + { + return 1 + attestedSignature.getSignatureLevel(); + } + } + + /** + * Return the attested signature (or null if this is not an attestation signature). + * + * @return attested signature or null + */ + public OpenPGPDocumentSignature getAttestedSignature() + { + return attestedSignature; + } + + /** + * Verify the correctness of an inline signature by evaluating the corresponding {@link PGPOnePassSignature}. + * + * @param ops one-pass-signature packet + * @return true if the signature is correct, false otherwise + * @throws PGPException if the signature cannot be verified + */ + public boolean verify(PGPOnePassSignature ops) + throws PGPException + { + isTested = true; + isCorrect = ops.verify(signature); + return isCorrect; + } + + /** + * Verify the correctness of a prefixed-signature. + * + * @return true if the signature is correct, false otherwise + * @throws PGPException if the signature cannot be verified + */ + public boolean verify() + throws PGPException + { + isTested = true; + isCorrect = signature.verify(); + return isCorrect; + } + + /** + * Return true, if the signature is valid at this moment. + * A valid signature is effective, correct and was issued by a valid signing key. + * + * @return true if the signature is valid now. + */ + public boolean isValid() + { + return isValidAt(getCreationTime()); + } + + /** + * Return true, if th signature is valid at the given date. + * A valid signature is effective, correct and was issued by a valid signing key. + * + * @param date evaluation time + * @return true if the signature is valid at the given date + * @throws IllegalStateException if the signature has not yet been tested using a
verify()
method. + */ + public boolean isValidAt(Date date) + { + if (!isTested) + { + throw new IllegalStateException("Signature has not yet been verified."); + } + if (!isTestedCorrect()) + { + return false; + } + return issuer.isSigningKey(date); + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java new file mode 100644 index 0000000000..675b6932f4 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java @@ -0,0 +1,107 @@ +package org.bouncycastle.openpgp.api; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Implementation of {@link InputStream} that withholds a number of bytes from the end of the original + * message until the message has been processed entirely. + * Furthermore, upon reaching the end of the underlying data stream, the underlying data stream is + * automatically closed. + * This is done in order to minimize the risk of emitting unauthenticated plaintext, while at the same + * time being somewhat resource-efficient. + * The number of bytes to withhold can be configured ({@link #CIRCULAR_BUFFER_SIZE} by default). + */ +public class RetainingInputStream + extends InputStream +{ + private static final int CIRCULAR_BUFFER_SIZE = 1024 * 1024 * 32; // 32 MiB + + private final byte[] circularBuffer; + private int lastWrittenPos = 0; + private int bufReadPos = 0; + private final I in; + private boolean closed = false; + + public RetainingInputStream(I in) + { + this(in, CIRCULAR_BUFFER_SIZE); + } + + public RetainingInputStream(I in, int bufferSize) + { + if (bufferSize <= 0) + { + throw new IllegalArgumentException("Buffer size cannot be null nor negative."); + } + this.circularBuffer = new byte[bufferSize]; + this.in = in; + } + + public I getInputStream() + { + return in; + } + + private void fill() + throws IOException + { + if (closed) + { + return; + } + + // readerPos - 1 % buf.len + int lastAvailPos = (circularBuffer.length + bufReadPos - 1) % circularBuffer.length; + int read; + if (lastWrittenPos < lastAvailPos) + { + read = in.read(circularBuffer, lastWrittenPos, lastAvailPos - lastWrittenPos); + } + else + { + read = in.read(circularBuffer, lastWrittenPos, circularBuffer.length - lastWrittenPos); + if (read >= 0) + { + lastWrittenPos += read; + } + read = in.read(circularBuffer, 0, lastAvailPos); + } + + if (read >= 0) + { + lastWrittenPos += read; + } + else + { + close(); + } + + lastWrittenPos %= circularBuffer.length; + } + + @Override + public void close() + throws IOException + { + if (!closed) + { + closed = true; + in.close(); + } + } + + @Override + public int read() + throws IOException + { + fill(); + if (bufReadPos == lastWrittenPos) + { + return -1; + } + int i = circularBuffer[bufReadPos++]; + bufReadPos %= circularBuffer.length; + return i; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java new file mode 100644 index 0000000000..8d7bd5c601 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPSignatureException; + +/** + * An OpenPGP signature is not correct. + */ +public class IncorrectPGPSignatureException + extends PGPSignatureException +{ + public IncorrectPGPSignatureException(String message) + { + super(message); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java new file mode 100644 index 0000000000..f83695359d --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java @@ -0,0 +1,16 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPSignatureException; + +/** + * An OpenPGP Signature is malformed (missing required subpackets, etc.). + */ +public class MalformedPGPSignatureException + extends PGPSignatureException +{ + + public MalformedPGPSignatureException(String message) + { + super(message); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java new file mode 100644 index 0000000000..ded366a9ef --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPSignatureException; + +/** + * The OpenPGP certificate (public key) required to verify a signature is not available. + */ +public class MissingIssuerCertException + extends PGPSignatureException +{ + public MissingIssuerCertException(String message) + { + super(message); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java new file mode 100644 index 0000000000..7ef3b9fc94 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java @@ -0,0 +1,203 @@ +package org.bouncycastle.openpgp.api.util; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; + +import java.io.IOException; +import java.util.Date; + +public class DebugPrinter +{ + + private static final String hardRevokedPrimaryKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwN8EIAEKAJMFglwqrYAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "ZzUC0OZfTpIdwlwf0ObCTwna1jQBSX993ccnmOrNte5LIx0CS2V5IG1hdGVyaWFs\n" + + "IGhhcyBiZWVuIGNvbXByb21pc2VkFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAAJA5\n" + + "CACTlymVijD9/t/SUBh3QihI9xjk+l2dGcFN64qkYEoplAJKedpO3z9niE9ejByF\n" + + "4tqn5BklxUGaRjq3Sgy0EQAi/nkgSq0cQX/aG2UoIs+OYbqzSktZAXIPUiQI5Ir5\n" + + "OYyALBJo03TxHHMOIBrLERVJiDGGoFNY58jQ7kUD6/XtRvpXNuQnfpRH4sAX+VQo\n" + + "fC5WojyWsiIv1aXwOJOA1IXSCHmK7lFuWVyZ6f/SGYpMnIROE1hzaRAVaaMhjcw1\n" + + "2gr5fKi/3Sd2agzwLbLfqvvYD9BI4yKkysTMp6t2ZbwcpvlWp/8Yu1Zrmf5moLJY\n" + + "6BveLKJdm/Th6Tik4dDP/WvCwsDEBB8BCgB4BYJeC+EACRAIrVHK5HDwBkcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeHLGXtWodbY9gI8X3Q\n" + + "zLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqCApG3522xqAitUcrk\n" + + "cPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySjTyk+ytK1Q5E8NSUY\n" + + "k3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ/At+Bw3OPeWZ68hz\n" + + "QfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/jCEYM5Kfg4NC1yVZ\n" + + "w7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8EQq9veCfHYPwqMAH\n" + + "5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIFzvwpgKbkzb2m3Lfg\n" + + "OyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWCWkl6AAkQCK1RyuRw\n" + + "8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn1WXYy2Gc\n" + + "Q19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEWIQTjLLbaggKRt+dt\n" + + "sagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsUG65rE8R4QGFvHhhX\n" + + "M/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZuflYRmct3t0B+CfxN\n" + + "9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwkdOKkm6VVAiAKZ4QR\n" + + "8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23ZvFL1TxVx/rhxM04\n" + + "Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+eatJt1bXsNioiFIuMC\n" + + "ouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1bGlldEBleGFtcGxl\n" + + "Lm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3Rh\n" + + "dGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa5AImO40vTfrIbkXR\n" + + "2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAn/UIALMbXwG8\n" + + "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\n" + + "=x/EN\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + private static final String v6SecretKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB\n" + + "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ\n" + + "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh\n" + + "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe\n" + + "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/\n" + + "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG\n" + + "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE\n" + + "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr\n" + + "k0mXubZvyl4GBg==\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + public static void main(String[] args) + throws IOException + { + + OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(v6SecretKey); + // -DM System.out.println + System.out.println(toString(certificate, new Date())); + } + + public static String toString(OpenPGPCertificate certificate, Date evaluationTime) + { + StringBuilder sb = new StringBuilder(); + for (OpenPGPCertificate.OpenPGPCertificateComponent component : certificate.getComponents()) + { + if (component.isBoundAt(evaluationTime)) + { + green(sb, component.toDetailString()).append("\n"); + } + else + { + red(sb, component.toDetailString()).append("\n"); + } + + OpenPGPCertificate.OpenPGPSignatureChains chains = component.getSignatureChains(); + for (OpenPGPCertificate.OpenPGPSignatureChain chain : chains) + { + boolean revocation = chain.isRevocation(); + boolean isHardRevocation = chain.isHardRevocation(); + String indent = ""; + for (OpenPGPCertificate.OpenPGPSignatureChain.Link link : chain) + { + indent = indent + " "; + sb.append(indent); + try + { + link.verify(new BcPGPContentVerifierBuilderProvider()); + if (revocation) + { + if (isHardRevocation) + { + red(sb, link.toString()).append("\n"); + } + else + { + yellow(sb, link.toString()).append("\n"); + } + } + else + { + green(sb, link.toString()).append("\n"); + } + } + catch (PGPException e) + { + red(sb, link.toString()).append("\n"); + } + } + } + } + + return sb.toString(); + } + + private static StringBuilder red(StringBuilder sb, String text) + { + return sb.append("\033[31m").append(text).append("\033[0m"); + } + + private static StringBuilder redBg(StringBuilder sb, String text) + { + return sb.append("\033[41m").append(text).append("\033[0m"); + } + + private static StringBuilder green(StringBuilder sb, String text) + { + return sb.append("\033[32m").append(text).append("\033[0m"); + } + + private static StringBuilder greenBg(StringBuilder sb, String text) + { + return sb.append("\033[42m").append(text).append("\033[0m"); + } + + private static StringBuilder yellow(StringBuilder sb, String text) + { + return sb.append("\033[33m").append(text).append("\033[0m"); + } + + private static StringBuilder yellowBg(StringBuilder sb, String text) + { + return sb.append("\033[43m").append(text).append("\033[0m"); + } + +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java new file mode 100644 index 0000000000..d2013cc6e5 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java @@ -0,0 +1,48 @@ +package org.bouncycastle.openpgp.api.util; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class UTCUtil +{ + private static SimpleDateFormat utc() + { + // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. + // See https://stackoverflow.com/a/6840856/11150851 + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + + /** + * Format a {@link Date} as UTC timestamp. + * + * @param timestamp date + * @return formatted timestamp + */ + public static String format(Date timestamp) + { + return utc().format(timestamp); + } + + /** + * Parse a UTC timestamp. + * The timestamp needs to be provided in the form 'yyyy-MM-dd HH:mm:ss z'. + * + * @param utcTimestamp timestamp + * @return date + */ + public static Date parse(String utcTimestamp) + { + try + { + return utc().parse(utcTimestamp); + } + catch (ParseException e) + { + throw new IllegalArgumentException("Malformed UTC timestamp: " + utcTimestamp, e); + } + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/OpenPGPTestKeys.java b/pg/src/test/java/org/bouncycastle/openpgp/OpenPGPTestKeys.java new file mode 100644 index 0000000000..28c19bd875 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/OpenPGPTestKeys.java @@ -0,0 +1,453 @@ +package org.bouncycastle.openpgp; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class OpenPGPTestKeys +{ + /** + * Alice's Ed25519 OpenPGP key. + * + * @see + * Alice's OpenPGP Secret Key Material + */ + public static final String ALICE_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP Transferable Secret Key\n" + + "Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html\n" + + "\n" + + "lFgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RtCZBbGlj\n" + + "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPoiQBBMWCAA4AhsDBQsJ\n" + + "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l\n" + + "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf\n" + + "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICnF0EXEcE6RIKKwYB\n" + + "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA\n" + + "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK6IeAQYFggAIBYhBOuF\n" + + "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM\n" + + "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb\n" + + "Pnn+We1aTBhaGa86AQ==\n" + + "=n8OM\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + /** + * Alice's Ed25519 OpenPGP v4 certificate. + * + * @see + * Alice's OpenPGP Certificate + */ + public static final String ALICE_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP certificate\n" + + "Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html\n" + + "\n" + + "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=iIGO\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + /** + * Alice's Ed25519 OpenPGP v4 revocation certificate. + * + * @see + * Alice's Revocation Certificate + */ + public static final String ALICE_REVOCATION_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Alice's revocation certificate\n" + + "Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html\n" + + "\n" + + "iHgEIBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXaWkOwIdAAAKCRDyMVUM\n" + + "T0fjjoBlAQDA9ukZFKRFGCooVcVoDVmxTaHLUXlIg9TPh2f7zzI9KgD/SLNXUOaH\n" + + "O6TozOS7C9lwIHwwdHdAxgf5BzuhLT9iuAM=\n" + + "=Tm8h\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + /** + * Bob's RSA-3072 OpenPGP v4 Secret Key Material. + * + * @see + * Bob's OpenPGP Secret Key Material + */ + public static final String BOB_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "=FAzO\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + /** + * Bob's RSA-3072 OpenPGP v4 Certificate. + * @see + * Bob's OpenPGP Certificate + */ + public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "NEJd3XZRzaXZE2aAMQ==\n" + + "=F9yX\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + /** + * Bob's RSA-3072 Revocation Certificate. + * @see + * Bob's Revocation Certificate + */ + public static final String BOB_REVOCATION_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Bob's revocation certificate\n" + + "Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html\n" + + "\n" + + "iQG2BCABCgAgFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAFAl2lnQQCHQAACgkQ+/zI\n" + + "KgFeczAIHAv/RrlGlPFKsW0BShC8sVtPfbT1N9lUqyrsgBhrUryM/i+rBtkbnSjp\n" + + "28R5araupt0og1g2L5VsCRM+ql0jf0zrZXOorKfAO70HCP3X+MlEquvztMUZGJRZ\n" + + "7TSMgIY1MeFgLmOw9pDKf3tSoouBOpPe5eVfXviEDDo2zOfdntjPyCMlxHgAcjZo\n" + + "XqMaurV+nKWoIx0zbdpNLsRy4JZcmnOSFdPw37R8U2miPi2qNyVwcyCxQy0LjN7Y\n" + + "AWadrs9vE0DrneSVP2OpBhl7g+Dj2uXJQRPVXcq6w9g5Fir6DnlhekTLsa78T5cD\n" + + "n8q7aRusMlALPAOosENOgINgsVcjuILkPN1eD+zGAgHgdiKaep1+P3pbo5n0CLki\n" + + "UCAsLnCEo8eBV9DCb/n1FlI5yhQhgQyMYlp/49H0JSc3IY9KHhv6f0zIaRWs0JuD\n" + + "ajcXTJ9AyB+SA6GBb9Q+XsNXjZ1gj75ekUD1sQ3ezTvVfovgP5bD+vPvILhSImKB\n" + + "aU6V3zld/x/1\n" + + "=mMwU\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + /** + * Carol's OpenPGP v4 key. + */ + public static final String CAROL_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "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\n" + + "1FkOSekLi8WNMdUx3XMyvP8nJ65P2Q==\n" + + "=Xj8h\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + /** + * Carol's OpenPGP v4 certificate. + */ + public static final String CAROL_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=pa/S\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + /** + * Minimal OpenPGP v6 key. + * @see + * Sample Version 6 Secret Key + */ + public static final String V6_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB\n" + + "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ\n" + + "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh\n" + + "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe\n" + + "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/\n" + + "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG\n" + + "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE\n" + + "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr\n" + + "k0mXubZvyl4GBg==\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + /** + * Locked, minimal OpenPGP v6 key. + * @see + * Sample Locked Version 6 Secret Key + */ + public static final String V6_KEY_LOCKED = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC\n" + + "FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS\n" + + "3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC\n" + + "Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW\n" + + "cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin\n" + + "7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/\n" + + "0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0\n" + + "gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf\n" + + "9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR\n" + + "v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr\n" + + "DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki\n" + + "Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt\n" + + "ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + /** + * Passphrase to unlock {@link #V6_KEY_LOCKED} with. + */ + public static final String V6_KEY_LOCKED_PASSPHRASE = "correct horse battery staple"; + /** + * Sample Version 6 Certificate. + * @see + * Sample Version 6 Certificate + */ + public static final String V6_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf\n" + + "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy\n" + + "KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw\n" + + "gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE\n" + + "QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn\n" + + "+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh\n" + + "BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8\n" + + "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805\n" + + "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + public static PGPPublicKeyRing readPGPPublicKeyRing(String armor) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(armor.getBytes()); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + PGPPublicKeyRing publicKeys = (PGPPublicKeyRing) objFac.nextObject(); + pIn.close(); + aIn.close(); + return publicKeys; + } + + public static PGPSecretKeyRing readPGPSecretKeyRing(String armor) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(armor.getBytes()); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) objFac.nextObject(); + pIn.close(); + aIn.close(); + return secretKeys; + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java new file mode 100644 index 0000000000..56b38916a7 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -0,0 +1,846 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.util.UTCUtil; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class OpenPGPCertificateTest + extends AbstractPacketTest +{ + @Override + public String getName() + { + return "OpenPGPCertificateTest"; + } + + @Override + public void performTest() + throws Exception + { + testOpenPGPv6Key(); + + testBaseCasePrimaryKeySigns(); + testBaseCaseSubkeySigns(); + testPKSignsPKRevokedNoSubpacket(); + testSKSignsPKRevokedNoSubpacket(); + testPKSignsPKRevocationSuperseded(); + } + + private void testOpenPGPv6Key() + throws IOException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + + isTrue("Test key has no identities", key.getIdentities().isEmpty()); + + OpenPGPCertificate.OpenPGPPrimaryKey primaryKey = key.getPrimaryKey(); + isEquals("Primary key identifier mismatch", + new KeyIdentifier("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"), + primaryKey.getKeyIdentifier()); + OpenPGPKey.OpenPGPSecretKey secretPrimaryKey = key.getSecretKey(primaryKey); + isTrue("Secret Primary key MUST have reference to its public component", + primaryKey == secretPrimaryKey.getPublicKey()); + isTrue("Primary key is expected to be signing key", primaryKey.isSigningKey()); + isTrue("Primary secret key is expected to be signing key", secretPrimaryKey.isSigningKey()); + isTrue("Primary secret key is expected to be certification key", secretPrimaryKey.isCertificationKey()); + isTrue("Primary key is expected to be certification key", primaryKey.isCertificationKey()); + + List signingKeys = key.getSigningKeys(); + isEquals("Expected exactly 1 signing key", 1, signingKeys.size()); + OpenPGPCertificate.OpenPGPPrimaryKey signingKey = (OpenPGPCertificate.OpenPGPPrimaryKey) signingKeys.get(0); + isEquals("Signing key is expected to be the same as primary key", primaryKey, signingKey); + + Features signingKeyFeatures = signingKey.getFeatures(); + // Features are extracted from direct-key signature + isEquals("Signing key features mismatch. Expect features to be extracted from DK signature.", + Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2, + signingKeyFeatures.getFeatures()); + + List encryptionKeys = key.getEncryptionKeys(); + isEquals("Expected exactly 1 encryption key", 1, encryptionKeys.size()); + OpenPGPCertificate.OpenPGPSubkey encryptionKey = (OpenPGPCertificate.OpenPGPSubkey) encryptionKeys.get(0); + isTrue("Subkey MUST be encryption key", encryptionKey.isEncryptionKey()); + isEquals("Encryption subkey identifier mismatch", + new KeyIdentifier("12C83F1E706F6308FE151A417743A1F033790E93E9978488D1DB378DA9930885"), + encryptionKey.getKeyIdentifier()); + + KeyFlags encryptionKeyFlags = encryptionKey.getKeyFlags(); + // Key Flags are extracted from subkey-binding signature + isEquals("Encryption key flag mismatch. Expected key flags to be extracted from SB sig.", + KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, + encryptionKeyFlags.getFlags()); + + Features encryptionKeyFeatures = encryptionKey.getFeatures(); + // Features are extracted from direct-key signature + isEquals("Encryption key features mismatch. Expected features to be extracted from DK sig.", + Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2, + encryptionKeyFeatures.getFeatures()); + } + + private void testBaseCasePrimaryKeySigns() + throws IOException + { + // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__primary_key_signs_and_is_not_revoked__base_case_ + String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "/3vqxdhYHw==\n" + + "=Ric2\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + TestSignature t0 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJYaEaACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmeoPMfalw2oS7uyOKnOXJSN8Gx7pr/BMlo3Xn8nTgx6\n" + + "ORYhBOMsttqCApG3522xqAitUcrkcPAGAABXbAf/WfWaQYNuATAKwxYrJx4fd5kt\n" + + "0M6sn1q7wK1MIxursG2+FuKafV25O9+pde8Nog77OEgegwk+HokOVFpVXfOzHQjs\n" + + "8dwWTtTQlX5NIBNvtqS7cvCKhjsqaHKgmzsenMjCEbpDZ3C5CoqcYicykqEU/Ia0\n" + + "ZGC4lzRByrgNy/w+/iLN748S707bzBLVc/sE73k9N5pANAlE+cA/sHI1Gp2WxJR9\n" + + "t2Fk4x6/85PEnF1RHI16p/wSEeuRaBpyw9QGZBbVDVt5wvgttxZjteGGSwBM3WI/\n" + + "gPfC0LW+JQ2W+dwY0PN/7yuARVRhXpKiBI4xqp7x3OanQX6quU77g3B8nXAt3A==\n" + + "=StqT\n" + + "-----END PGP SIGNATURE-----\n", false, "Sig predates primary key"); + TestSignature t1 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfM0EN4Ei0bQv6UO9BRq2wtUfV948cRynRMBb8TSGCG\n" + + "tBYhBOMsttqCApG3522xqAitUcrkcPAGAAAlNwf+L0KQK9i/xmYKOMV2EX13QUoZ\n" + + "vvb/pHGZaCQ9JtvEF2l2DT0DqByZ+tOv5Y4isU+un7CraoyvyajAwR0Yqk937B6C\n" + + "HQHKMkmIl+5R4/xqSoWYmOidbrgilojPMBEhB3INQ8/THjjFijtLzitVhnWBd7+u\n" + + "s0kcqnWnOdx2By4aDe+UEiyCfSE02e/0tIsM71RqiU91zH6dl6+q8nml7PsYuTFV\n" + + "V09oQTbBuuvUe+YgN/uvyKVIsA64lQ+YhqEeIA8Quek7fHhW+du9OIhSPsbYodyx\n" + + "VWMTXwSWKGNvZNAkpmgUYqFjS2Cx5ZUWblZLjrNKBwnnmt50qvUN7+o2pjlnfA==\n" + + "=UuXb\n" + + "-----END PGP SIGNATURE-----\n", true); + TestSignature t2 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJdP4iACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfFzYGoiuSjN+gz1IDD4ZvRXGuPTHks0/pIiGY90mrZ\n" + + "WxYhBOMsttqCApG3522xqAitUcrkcPAGAABGPAf/ck7tJAFoPIDd9fTPZANpNGoW\n" + + "Fq6VuNfy/nLjz2gkHFX/lLAxQ0N3McIdRA++Ik/omb0lis3R2DVNgwqNm2OF34HE\n" + + "qxmPmrQHBgk2q0fDH4NCE0XnYQjQT65V99IfiaQu+oS3Mq8MuYsDYvRVvRKMwt49\n" + + "fcDnvFtAtCqEETdv6wV5cUZmdQ3L9NU9bApJ0jk+EHVdpfTUIbOYYGnsIe/4Aa0d\n" + + "jgzu4Em79ynosOn//953XJ7OO8LCDi1EKt+nFuZARUlt/Jwwull6zzp7HUPw6HPt\n" + + "Upp7os8TIPC4STwoSeEKaxEkrbMGFnDcoDajnKKRt5+MkB24Oq7PHvnzgnPpVg==\n" + + "=Ljv7\n" + + "-----END PGP SIGNATURE-----\n", true); + TestSignature t3 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJmhTYiCRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfbjQf/zfoJQT0hhna4RDjOESBLgGaCbc5HLeo751F4\n" + + "NxYhBOMsttqCApG3522xqAitUcrkcPAGAABqBQgAkkNmYf6yLPvox+ZayrLtMb9D\n" + + "ghgt0nau72DSazsJ6SAq2QqIdr0RRhRa2gCETkp4PpeoDWmIvoVj35ZnfyeO/jqy\n" + + "HECvRwO0WPA5FXQM6uG7s40vDTRFjlJMpPyHWnn2igcR64iDxBGmc40xi9CcmJP9\n" + + "tmA26+1Nzj1LcfNvknKZ2UIOmnXiZY0QssIdyqsmJrdFpXs4UCLUzdXkfFLoxksU\n" + + "mk4B6hig2IKMj5mnbWy/JQSXtjjI+HHmtzgWfXs7d9iQ61CklbtCOiPeWxvoqlGG\n" + + "oK1wV1olcSar/RPKTlMmQpAg9dztQgrNs1oF7EF3i9kwNP7I5JzekPiOLH6oMw==\n" + + "=5KMU\n" + + "-----END PGP SIGNATURE-----\n", true); + + signatureValidityTest(cert, t0, t1, t2, t3); + } + + private void testBaseCaseSubkeySigns() + throws IOException + { + // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__subkey_signs__primary_key_is_not_revoked__base_case_ + String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "/3vqxdhYHw==\n" + + "=Ric2\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + TestSignature t0 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJYaEaACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmdVa4OG6WfRoRlj5+Zb6avhJUIZFvcIFiLuvrJp8Hio\n" + + "iBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAAAbaQgAjhBh0dLO0Sqiqkb2M3KWc25V\n" + + "hJlcP3isFROJ0jikmXxkG9W04AvlA78tSxEP2n8a0CbxH/hT4g8mFb/qM5FKZcKf\n" + + "HQxjbjUxBmVHa3EfMkwT7u1mVRmoWtJ59oVsKoqRb/kZ14i6VZ9NzfK8MRlL0e24\n" + + "oNjkksZQ8ImjwwtvxSinxhezA6BtWi+dDnXAnG5Vva+6N/GRNPAAd8kFTPrlEqEz\n" + + "uRbpq76r4taPjRjzMNcwZJoRVHSahWhDcXxNTalVUwt0DZFAskZ3gI+0VgU11bK1\n" + + "QmIw2iR4itQY5f10HFNcl7uHLKnul0YyuvA5509HwCuEpdYUV/OxtlpVRaJ+yg==\n" + + "=Rc6K\n" + + "-----END PGP SIGNATURE-----\n", false, "Signature predates primary key"); + TestSignature t1 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfcG7Iqn3OOKVjeJ61MlgERt08kcxh0x+BZFD7a8K7V\n" + + "VBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAACBIwf9EoS24IFeT3cPFf/nWxLFkbZK\n" + + "fiy9WzyK4wlpO3VTyWPbXi6zpC4I5Rbp2jDk/c7Q3DnOZqFDv6TriTwuLYTJGPxr\n" + + "U3dtDsFcKp4FcbgFyCDKIuLB+3kLaNpMXqdttEkY3Wd5m33XrBB7M0l5xZCk56Jm\n" + + "H5L1sGNNNkCzG6P44qu69o5fkWxbYuX22fyhdeyxucJHMztqiMQYDwT7eSA92A1v\n" + + "5OwA5D/k7GeyYFBFisxRijkdVtxstC9zkagC19VnZo7MRekA9gXj7kIna4XYRhfb\n" + + "uQnN47HXdiWQytwypLvZ8JEJpRruyMAaHjX5OBXh0SK11xYWb6wB93+QfOahtg==\n" + + "=UlUZ\n" + + "-----END PGP SIGNATURE-----\n", false, "Subkey is not bound at this time"); + TestSignature t2 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJdP4iACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcgkZw3ZSg8CZCKqJw2r4VqCpTuUhz6N0zX43d+1xop\n" + + "2hYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAADnqAgAq+m6dDZpNOBaXH9nwv8/+HgR\n" + + "MvRjnuLoa6zB5tcUhGPPVS0gg1PW0wfxlo1GPmgW3QDlV1zvcfYAZmV9uEC61wn/\n" + + "+FkqN0Tceo487UvkWARE/mmRj5L8OgUTfqm1eebFQlMu/MeG9YOg+tXBy7XS7hy3\n" + + "UdntIbtsv5oRTcybTnn5oiU2OFDlFC6sBNzOQt7wpyB1TKp2BdcsAv1RwmyCCCK4\n" + + "bnmrpYH6woWMyVEVeMYfOHAx9vHD+od8Vf/v5L1M2N0nHzRWjjkobTVUr+xt/CyW\n" + + "nq8SoazKYu3ETpZLeWX6Bciuv9+pzUCeClOSmBB1MFyyrTgbkOacHgrYnLvvtQ==\n" + + "=WCKA\n" + + "-----END PGP SIGNATURE-----\n", true); + TestSignature t3 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJmhTYiCRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmdi3dCpJ4nZincNH5owv8+fJ5YpXljqtegtoBEnbbHP\n" + + "thYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAAD0cQf/e8RHocRESJPbosqUuvC3ELnD\n" + + "oSsJomDMUDfSfgpS5EhkOyJhvcrHkCbsHH2xlUEQ+zjJWY/dwM3FUkoj+p3kb/JC\n" + + "Rn5cqQYlME+uJzjdHMyQCSOI1SvYwKCLCGPARDbCpeINrV++Oy29e6cv6/IcPlgo\n" + + "k/0A7XuNq0YNxC7oopCj5ye3yVUvUmSCG2iV4oiWW5GhhPRzMeW7MFQmS0NUkAI8\n" + + "hzJ8juTG4xP8SXnHCMakasZhJmtpMDd2BDZ7CrhWiWUQGrtd0eYkuyodreqVMGIF\n" + + "BN80YgTNFW2MrblhDRRmxAqWzD9FedBwwSdgYbtkDwjsSq0S1jQV6aPndJqiLw==\n" + + "=CIl0\n" + + "-----END PGP SIGNATURE-----\n", true); + + signatureValidityTest(cert, t0, t1, t2, t3); + } + + private void testPKSignsPKRevokedNoSubpacket() + throws IOException + { + String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "6sXYWB8=\n" + + "=13Sf\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + TestSignature t0 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJYaEaACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmeoPMfalw2oS7uyOKnOXJSN8Gx7pr/BMlo3Xn8nTgx6\n" + + "ORYhBOMsttqCApG3522xqAitUcrkcPAGAABXbAf/WfWaQYNuATAKwxYrJx4fd5kt\n" + + "0M6sn1q7wK1MIxursG2+FuKafV25O9+pde8Nog77OEgegwk+HokOVFpVXfOzHQjs\n" + + "8dwWTtTQlX5NIBNvtqS7cvCKhjsqaHKgmzsenMjCEbpDZ3C5CoqcYicykqEU/Ia0\n" + + "ZGC4lzRByrgNy/w+/iLN748S707bzBLVc/sE73k9N5pANAlE+cA/sHI1Gp2WxJR9\n" + + "t2Fk4x6/85PEnF1RHI16p/wSEeuRaBpyw9QGZBbVDVt5wvgttxZjteGGSwBM3WI/\n" + + "gPfC0LW+JQ2W+dwY0PN/7yuARVRhXpKiBI4xqp7x3OanQX6quU77g3B8nXAt3A==\n" + + "=StqT\n" + + "-----END PGP SIGNATURE-----\n", false, "Signature predates primary key"); + TestSignature t1 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfM0EN4Ei0bQv6UO9BRq2wtUfV948cRynRMBb8TSGCG\n" + + "tBYhBOMsttqCApG3522xqAitUcrkcPAGAAAlNwf+L0KQK9i/xmYKOMV2EX13QUoZ\n" + + "vvb/pHGZaCQ9JtvEF2l2DT0DqByZ+tOv5Y4isU+un7CraoyvyajAwR0Yqk937B6C\n" + + "HQHKMkmIl+5R4/xqSoWYmOidbrgilojPMBEhB3INQ8/THjjFijtLzitVhnWBd7+u\n" + + "s0kcqnWnOdx2By4aDe+UEiyCfSE02e/0tIsM71RqiU91zH6dl6+q8nml7PsYuTFV\n" + + "V09oQTbBuuvUe+YgN/uvyKVIsA64lQ+YhqEeIA8Quek7fHhW+du9OIhSPsbYodyx\n" + + "VWMTXwSWKGNvZNAkpmgUYqFjS2Cx5ZUWblZLjrNKBwnnmt50qvUN7+o2pjlnfA==\n" + + "=UuXb\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + TestSignature t2 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJdP4iACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfFzYGoiuSjN+gz1IDD4ZvRXGuPTHks0/pIiGY90mrZ\n" + + "WxYhBOMsttqCApG3522xqAitUcrkcPAGAABGPAf/ck7tJAFoPIDd9fTPZANpNGoW\n" + + "Fq6VuNfy/nLjz2gkHFX/lLAxQ0N3McIdRA++Ik/omb0lis3R2DVNgwqNm2OF34HE\n" + + "qxmPmrQHBgk2q0fDH4NCE0XnYQjQT65V99IfiaQu+oS3Mq8MuYsDYvRVvRKMwt49\n" + + "fcDnvFtAtCqEETdv6wV5cUZmdQ3L9NU9bApJ0jk+EHVdpfTUIbOYYGnsIe/4Aa0d\n" + + "jgzu4Em79ynosOn//953XJ7OO8LCDi1EKt+nFuZARUlt/Jwwull6zzp7HUPw6HPt\n" + + "Upp7os8TIPC4STwoSeEKaxEkrbMGFnDcoDajnKKRt5+MkB24Oq7PHvnzgnPpVg==\n" + + "=Ljv7\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + TestSignature t3 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJmhTYiCRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfbjQf/zfoJQT0hhna4RDjOESBLgGaCbc5HLeo751F4\n" + + "NxYhBOMsttqCApG3522xqAitUcrkcPAGAABqBQgAkkNmYf6yLPvox+ZayrLtMb9D\n" + + "ghgt0nau72DSazsJ6SAq2QqIdr0RRhRa2gCETkp4PpeoDWmIvoVj35ZnfyeO/jqy\n" + + "HECvRwO0WPA5FXQM6uG7s40vDTRFjlJMpPyHWnn2igcR64iDxBGmc40xi9CcmJP9\n" + + "tmA26+1Nzj1LcfNvknKZ2UIOmnXiZY0QssIdyqsmJrdFpXs4UCLUzdXkfFLoxksU\n" + + "mk4B6hig2IKMj5mnbWy/JQSXtjjI+HHmtzgWfXs7d9iQ61CklbtCOiPeWxvoqlGG\n" + + "oK1wV1olcSar/RPKTlMmQpAg9dztQgrNs1oF7EF3i9kwNP7I5JzekPiOLH6oMw==\n" + + "=5KMU\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + + signatureValidityTest(cert, t0, t1, t2, t3); + } + + private void testSKSignsPKRevokedNoSubpacket() + throws IOException + { + // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__subkey_signs__primary_key_is_revoked__revoked__no_subpacket + String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "6sXYWB8=\n" + + "=13Sf\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + TestSignature t0 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJYaEaACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmdVa4OG6WfRoRlj5+Zb6avhJUIZFvcIFiLuvrJp8Hio\n" + + "iBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAAAbaQgAjhBh0dLO0Sqiqkb2M3KWc25V\n" + + "hJlcP3isFROJ0jikmXxkG9W04AvlA78tSxEP2n8a0CbxH/hT4g8mFb/qM5FKZcKf\n" + + "HQxjbjUxBmVHa3EfMkwT7u1mVRmoWtJ59oVsKoqRb/kZ14i6VZ9NzfK8MRlL0e24\n" + + "oNjkksZQ8ImjwwtvxSinxhezA6BtWi+dDnXAnG5Vva+6N/GRNPAAd8kFTPrlEqEz\n" + + "uRbpq76r4taPjRjzMNcwZJoRVHSahWhDcXxNTalVUwt0DZFAskZ3gI+0VgU11bK1\n" + + "QmIw2iR4itQY5f10HFNcl7uHLKnul0YyuvA5509HwCuEpdYUV/OxtlpVRaJ+yg==\n" + + "=Rc6K\n" + + "-----END PGP SIGNATURE-----\n", false, "Signature predates primary key"); + TestSignature t1 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfcG7Iqn3OOKVjeJ61MlgERt08kcxh0x+BZFD7a8K7V\n" + + "VBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAACBIwf9EoS24IFeT3cPFf/nWxLFkbZK\n" + + "fiy9WzyK4wlpO3VTyWPbXi6zpC4I5Rbp2jDk/c7Q3DnOZqFDv6TriTwuLYTJGPxr\n" + + "U3dtDsFcKp4FcbgFyCDKIuLB+3kLaNpMXqdttEkY3Wd5m33XrBB7M0l5xZCk56Jm\n" + + "H5L1sGNNNkCzG6P44qu69o5fkWxbYuX22fyhdeyxucJHMztqiMQYDwT7eSA92A1v\n" + + "5OwA5D/k7GeyYFBFisxRijkdVtxstC9zkagC19VnZo7MRekA9gXj7kIna4XYRhfb\n" + + "uQnN47HXdiWQytwypLvZ8JEJpRruyMAaHjX5OBXh0SK11xYWb6wB93+QfOahtg==\n" + + "=UlUZ\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + TestSignature t2 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJdP4iACRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcgkZw3ZSg8CZCKqJw2r4VqCpTuUhz6N0zX43d+1xop\n" + + "2hYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAADnqAgAq+m6dDZpNOBaXH9nwv8/+HgR\n" + + "MvRjnuLoa6zB5tcUhGPPVS0gg1PW0wfxlo1GPmgW3QDlV1zvcfYAZmV9uEC61wn/\n" + + "+FkqN0Tceo487UvkWARE/mmRj5L8OgUTfqm1eebFQlMu/MeG9YOg+tXBy7XS7hy3\n" + + "UdntIbtsv5oRTcybTnn5oiU2OFDlFC6sBNzOQt7wpyB1TKp2BdcsAv1RwmyCCCK4\n" + + "bnmrpYH6woWMyVEVeMYfOHAx9vHD+od8Vf/v5L1M2N0nHzRWjjkobTVUr+xt/CyW\n" + + "nq8SoazKYu3ETpZLeWX6Bciuv9+pzUCeClOSmBB1MFyyrTgbkOacHgrYnLvvtQ==\n" + + "=WCKA\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + TestSignature t3 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJmhTYiCRAQ/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmdi3dCpJ4nZincNH5owv8+fJ5YpXljqtegtoBEnbbHP\n" + + "thYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAAD0cQf/e8RHocRESJPbosqUuvC3ELnD\n" + + "oSsJomDMUDfSfgpS5EhkOyJhvcrHkCbsHH2xlUEQ+zjJWY/dwM3FUkoj+p3kb/JC\n" + + "Rn5cqQYlME+uJzjdHMyQCSOI1SvYwKCLCGPARDbCpeINrV++Oy29e6cv6/IcPlgo\n" + + "k/0A7XuNq0YNxC7oopCj5ye3yVUvUmSCG2iV4oiWW5GhhPRzMeW7MFQmS0NUkAI8\n" + + "hzJ8juTG4xP8SXnHCMakasZhJmtpMDd2BDZ7CrhWiWUQGrtd0eYkuyodreqVMGIF\n" + + "BN80YgTNFW2MrblhDRRmxAqWzD9FedBwwSdgYbtkDwjsSq0S1jQV6aPndJqiLw==\n" + + "=CIl0\n" + + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); + + signatureValidityTest(cert, t0, t1, t2, t3); + } + + private void testPKSignsPKRevocationSuperseded() + throws IOException + { + // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__primary_key_signs_and_is_revoked__revoked__superseded + String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwM8EIAEKAIMFglwqrYAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z1X0jZPeNNpSsn78ulDPJNHa0QaeI5oAUdBGbIKSOT0uEx0BS2V5IGlzIHN1cGVy\n" + + "c2VkZWQWIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAr2QIAKAY5bHFbRkoItYBJBN1\n" + + "aV1jjrpYdwLM+0LHf8GcRCeO1Pt9I1J021crwTw14sTCxi6WH4qbQSBxRqAEej/A\n" + + "wfk1kmkm4WF7zTUT+fXIHDJxFJJXqFZ+LWldYYEVqSi02gpbYkyLm9hxoLDoAxS2\n" + + "bj/sFaH4Bxr/eUCqjOiEsGzdY1m65+cp5jv8cJK05jwqxO5/3KZcF/ShA7AN3dJi\n" + + "NAokoextBtXBWlGvrDIfFafOy/uCnsO6NeORWbgZ88TOXPD816ff5Y8kMwkDkIk2\n" + + "9dK4m0aL/MDI+Fgx78zRYwn5xHbTMaFz+hex+gjo4grx3KYXeoxBAchUuTsVNoo4\n" + + "kbfCwMQEHwEKAHgFgl4L4QAJEAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRp\n" + + "b25zLnNlcXVvaWEtcGdwLm9yZ4csZe1ah1tj2AjxfdDMsH2wvSEwZjb/73ICKnm7\n" + + "BySQAhUKApsDAh4BFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAAGYFCACiKnCb2NBZ\n" + + "a/Jj1aJe4R2rxPZj2ERXWe3bJKNPKT7K0rVDkTw1JRiTfCsuAY2lY9sKJdhQZl+a\n" + + "zXm64vvTc6hEGRQ/+XssDlE2DIn8C34HDc495ZnryHNB8Dd5l1HdjqxfGIY6HBPJ\n" + + "Udx0dedwP42Oisg9t5KsC8zld/+MIRgzkp+Dg0LXJVnDuwWEPoo2N6WhAr5ReLvX\n" + + "xALX5ht9Lb3lP0DASZvAKy9BO/wRCr294J8dg/CowAfloyf0Ko+JjyjanmZn3acy\n" + + "5CGkVN2mc+PFUekGZDDy5ooYkgXO/CmApuTNvabct+A7IVVdWWM5SWb90JvaV9SW\n" + + "ji6nQphVm7StwsDEBB8BCgB4BYJaSXoACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0\n" + + "QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfVZdjLYZxDX2hvy3aGrsE4i0avLDMz\n" + + "f3e9kVHmaD6PAgIVCgKbAwIeARYhBOMsttqCApG3522xqAitUcrkcPAGAABQYwgA\n" + + "rfIRxq95npUKAOPXs25nZlvy+xQbrmsTxHhAYW8eGFcz82QwumoqrR8VfrojxM+e\n" + + "CZdTI85nM5kzznYDU2+cMhsZVm5+VhGZy3e3QH4J/E31D7t1opCvj5g1eRJ4Lgyw\n" + + "B+cYGcZBYp/bQT9SUYuhZH2OXCR04qSbpVUCIApnhBHxKNtOlqjAkHeaOdW/8XeP\n" + + "sbfvrtVOLGYgrZXfY7Nqy3+Wzbdm8UvVPFXH+uHEzTgyvYbnJBYkjORmCqUKs860\n" + + "PL8ekeg+sL4PHSRj1UUfwcQD55q0m3Vtew2KiIUi4wKi5LceDtprjoO5utU/1YfE\n" + + "AiNMeSQHXKq83dpazvjrUs0SanVsaWV0QGV4YW1wbGUub3JnwsDEBBMBCgB4BYJa\n" + + "SXoACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBn\n" + + "cC5vcmc6Rix7CeIfWwnaQjk3bBrkAiY7jS9N+shuRdHZ0gKKsgIVCgKbAwIeARYh\n" + + "BOMsttqCApG3522xqAitUcrkcPAGAACf9QgAsxtfAbyGbtofjrXTs9lsKEWvGgk0\n" + + "2fSYyKjPbyaRqh72MlIlUXwqq1ih2TJc3vwF8aNVDrcb9DnBabdt2M1vI3PUaeG3\n" + + "1BmakC/XZCNCrbbJkyd/vdMLqw7prLrp0auVNNhLYxOK9usXbClNxluo4i/lSFVo\n" + + "5B9ai+ne1kKKiplzqy2qqhdeplomcwGHbB1CkZ04DmCMbSSFAGxYqUC/bBm0bolC\n" + + "ebw/KIz9sEojNKt6mvsFN67/hMYeJS0HVlwwc6i8iKSzC2D53iywhtvkdiKECXQe\n" + + "XDf9zNXAn1wpK01SLJ0iig7cDFrtoqkfPYzbNfC0bt34fNx9iz3w9aEH8c7ATQRa\n" + + "SsuAAQgAu5yau9psltmWiUn7fsRSqbQInO0iWnu4DK9IXB3ghNYMcii3JJEjHzgI\n" + + "xGf3GiJEjzubyRQaX5J/p7yB1fOH8z7FYUuax1saGf9c1/b02N9gyXNlHam31hNa\n" + + "aL3ffFczI95p7MNrTtroTt5oZqsc+i+oKLZn7X0YAI4tEYwhSnUQYB/F7YqkkI4e\n" + + "V+7CxZPA8pBhXiAOK/zn416PsZ6JS5wsM65yCtOHcAAIBnKDnC+bQi+f1WZesSoc\n" + + "y/rXx3QEQmodDu3ojhS+VxcYGeZCUcFF0FyZBIkGjHIVQLyOfjP3FRJ4qFXMz9/Y\n" + + "IVoM4Y6guTERMTEj/KDG4BP7RfJHTQARAQABwsI8BBgBCgHwBYJeC+EACRAIrVHK\n" + + "5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfcAa1Z\n" + + "PWTtg60w3Oo4dt4Fa8cKFYbZYsqDSHV5pwEfMwKbAsC8oAQZAQoAbwWCXgvhAAkQ\n" + + "EPy8/w6Op5FHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn\n" + + "L6I2+VyN5T1FoVgj3cdnMLYCpcB5i/FRSCVKybuLzrgWIQTOphDQhPpR8hHhxGwQ\n" + + "/Lz/Do6nkQAArk8H/AhjM9lqbffFL6RRR4HTjelspy4A3nyTicCljrDuXDUh23Gf\n" + + "LvajTR5h16ZBqAF7cpb9rrlz1C1WcS5JLVxzXAe7f+KOfXu+eyLhpTzZ8VT3pK3h\n" + + "HGaYwlVlXrBZP0JXgL8hm6hDSXZQZtcpsnQ1uIHC9ONxUB4liNFhTqQCQYdQJFiF\n" + + "s1umUbo/C4KdzlDI08bM3CqEKat9vUFuGG68mDg0CrRZEWt946L5i8kZmBUkSShI\n" + + "m2k5e2qE/muYeM6qKQNsxlx3VIf5eUhtxCi9fg7SjvHkdUSFstYcxAdaohWCFCEs\n" + + "DJI12hzcKQazSjvtKF4BNBKgX/wLsbVQnYLd9ggWIQTjLLbaggKRt+dtsagIrVHK\n" + + "5HDwBgAANjMH/1MY7DJyxkiTjc/jzmnVxqtHOZDCSmUqk0eh/6BHs+ostWqkGC6+\n" + + "7dfxDnptwcqandYey4KF2ajt4nOwu0xQw/NEF3i81h7IiewY7G+YT69DUd+DvVUQ\n" + + "emfKNYVOrMqoH7QU5o4YojdJiDeIp2d/JyJrqyof78JFAHnNZgHC2T2zo9E54dnO\n" + + "TY9VNUNCOUct5Rby0GXjTIURO0f485eGuZxVWdLRllDYOiCrQHPSHhrxHVXVMbYJ\n" + + "oroPy+IyaJanVoAWgyipBmmIDV8aINM2RLMsGkuPTRtITI2ZlGOQN7xgy4LqWzjP\n" + + "nrzMXfwBEDx/nrwdG6zEGMK8AkVkMT5uJJvCwjwEGAEKAfAFglro/4AJEAitUcrk\n" + + "cPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ/Q0Z6WD\n" + + "H2+8/F1xEEuiApsjnn2lGNZ2DeIaklJzdqQOApsCwLygBBkBCgBvBYJa6P+ACRAQ\n" + + "/Lz/Do6nkUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfr\n" + + "VATyX3tgcM2z41fqYquxVhJRavN6+w2SU4xEG++SqBYhBM6mENCE+lHyEeHEbBD8\n" + + "vP8OjqeRAABGVggAsB8M2KI5cxXKKgVHL1dEfzg9halVavktfcT6ZVC/+aDp94tv\n" + + "BCL16Guhq4ccN7DATrWx430/GecY6E77qvhDzmCclSbdLbiZmsrVX9kCmTfrJzFQ\n" + + "64KfvIS5GgbL21+ZJ+pKW2HOMBGn6sgAPmTqM5UsDCpsEKDt5CJcJr3sTc8D9NhE\n" + + "nc0dKsQ91+n9ms3W5tyyE6r9pyM6ThBCMhbQkR7hE9XWAQeO1ILSFGnie0aFcTU0\n" + + "Oo0wL1MaiSyA/8XpKq23xfx1kNS9hQkdq0aWehNoTJdCt1Nq1cWABy2rQR0x+qhG\n" + + "WowfsAjnBautxvet28t2kPCAIMniYpWc89BwfhYhBOMsttqCApG3522xqAitUcrk\n" + + "cPAGAACq1gf/Q7H9Re5SWk+UOn/NQPRedf544YJ/YdQnve/hSaPGL33cUzf4yxzF\n" + + "ILnK19Ird5f8/mTT1pg99L3ixE3N5031JJKwFpCB69Rsysg88ZLDL2VLc3xdsAQd\n" + + "UbVaCqeRHKwtMtpBvbAFvF9plwam0SSXHHr/JkYm5ufXN6I8ib/nwr1bFbf/Se0W\n" + + "uk9RG4ne9JUBCrGxakyVd+OgLLhvzOmJa7fDC0uUZhTKFbjMxLhaas4HFYiRbfz2\n" + + "T0xz9gyDytDWsEFM+XoKHlEH8Fx/U2B5/8N0Q+pIFoEuOmBO+5EPvPIlxNByHgia\n" + + "NIuKt1Mu+UAb2Spl6D5zbDfX/3vqxdhYHw==\n" + + "=9epL\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + TestSignature t0 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJYaEaACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmeoPMfalw2oS7uyOKnOXJSN8Gx7pr/BMlo3Xn8nTgx6\n" + + "ORYhBOMsttqCApG3522xqAitUcrkcPAGAABXbAf/WfWaQYNuATAKwxYrJx4fd5kt\n" + + "0M6sn1q7wK1MIxursG2+FuKafV25O9+pde8Nog77OEgegwk+HokOVFpVXfOzHQjs\n" + + "8dwWTtTQlX5NIBNvtqS7cvCKhjsqaHKgmzsenMjCEbpDZ3C5CoqcYicykqEU/Ia0\n" + + "ZGC4lzRByrgNy/w+/iLN748S707bzBLVc/sE73k9N5pANAlE+cA/sHI1Gp2WxJR9\n" + + "t2Fk4x6/85PEnF1RHI16p/wSEeuRaBpyw9QGZBbVDVt5wvgttxZjteGGSwBM3WI/\n" + + "gPfC0LW+JQ2W+dwY0PN/7yuARVRhXpKiBI4xqp7x3OanQX6quU77g3B8nXAt3A==\n" + + "=StqT\n" + + "-----END PGP SIGNATURE-----\n", false, "Signature predates primary key"); + TestSignature t1 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfM0EN4Ei0bQv6UO9BRq2wtUfV948cRynRMBb8TSGCG\n" + + "tBYhBOMsttqCApG3522xqAitUcrkcPAGAAAlNwf+L0KQK9i/xmYKOMV2EX13QUoZ\n" + + "vvb/pHGZaCQ9JtvEF2l2DT0DqByZ+tOv5Y4isU+un7CraoyvyajAwR0Yqk937B6C\n" + + "HQHKMkmIl+5R4/xqSoWYmOidbrgilojPMBEhB3INQ8/THjjFijtLzitVhnWBd7+u\n" + + "s0kcqnWnOdx2By4aDe+UEiyCfSE02e/0tIsM71RqiU91zH6dl6+q8nml7PsYuTFV\n" + + "V09oQTbBuuvUe+YgN/uvyKVIsA64lQ+YhqEeIA8Quek7fHhW+du9OIhSPsbYodyx\n" + + "VWMTXwSWKGNvZNAkpmgUYqFjS2Cx5ZUWblZLjrNKBwnnmt50qvUN7+o2pjlnfA==\n" + + "=UuXb\n" + + "-----END PGP SIGNATURE-----\n", true); + TestSignature t2 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJdP4iACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfFzYGoiuSjN+gz1IDD4ZvRXGuPTHks0/pIiGY90mrZ\n" + + "WxYhBOMsttqCApG3522xqAitUcrkcPAGAABGPAf/ck7tJAFoPIDd9fTPZANpNGoW\n" + + "Fq6VuNfy/nLjz2gkHFX/lLAxQ0N3McIdRA++Ik/omb0lis3R2DVNgwqNm2OF34HE\n" + + "qxmPmrQHBgk2q0fDH4NCE0XnYQjQT65V99IfiaQu+oS3Mq8MuYsDYvRVvRKMwt49\n" + + "fcDnvFtAtCqEETdv6wV5cUZmdQ3L9NU9bApJ0jk+EHVdpfTUIbOYYGnsIe/4Aa0d\n" + + "jgzu4Em79ynosOn//953XJ7OO8LCDi1EKt+nFuZARUlt/Jwwull6zzp7HUPw6HPt\n" + + "Upp7os8TIPC4STwoSeEKaxEkrbMGFnDcoDajnKKRt5+MkB24Oq7PHvnzgnPpVg==\n" + + "=Ljv7\n" + + "-----END PGP SIGNATURE-----\n", false, "Key is revoked at this time"); + TestSignature t3 = new TestSignature("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJmhTYiCRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfbjQf/zfoJQT0hhna4RDjOESBLgGaCbc5HLeo751F4\n" + + "NxYhBOMsttqCApG3522xqAitUcrkcPAGAABqBQgAkkNmYf6yLPvox+ZayrLtMb9D\n" + + "ghgt0nau72DSazsJ6SAq2QqIdr0RRhRa2gCETkp4PpeoDWmIvoVj35ZnfyeO/jqy\n" + + "HECvRwO0WPA5FXQM6uG7s40vDTRFjlJMpPyHWnn2igcR64iDxBGmc40xi9CcmJP9\n" + + "tmA26+1Nzj1LcfNvknKZ2UIOmnXiZY0QssIdyqsmJrdFpXs4UCLUzdXkfFLoxksU\n" + + "mk4B6hig2IKMj5mnbWy/JQSXtjjI+HHmtzgWfXs7d9iQ61CklbtCOiPeWxvoqlGG\n" + + "oK1wV1olcSar/RPKTlMmQpAg9dztQgrNs1oF7EF3i9kwNP7I5JzekPiOLH6oMw==\n" + + "=5KMU\n" + + "-----END PGP SIGNATURE-----\n", true); + + signatureValidityTest(CERT, t0, t1, t2, t3); + } + + private void signatureValidityTest(String cert, TestSignature... testSignatures) + throws IOException + { + OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(cert); + + for (TestSignature test : testSignatures) + { + PGPSignature signature = test.getSignature(); + OpenPGPCertificate.OpenPGPComponentKey signingKey = certificate.getSigningKeyFor(signature); + + boolean valid = signingKey.isBoundAt(signature.getCreationTime()); + if (valid != test.isExpectValid()) + { + StringBuilder sb = new StringBuilder("Key validity mismatch. Expected " + signingKey.toString() + + (test.isExpectValid() ? (" to be valid at ") : (" to be invalid at ")) + UTCUtil.format(signature.getCreationTime())); + if (test.getMsg() != null) + { + sb.append(" because:\n").append(test.getMsg()); + } + sb.append("\n").append(signingKey.getSignatureChains()); + fail(sb.toString()); + } + } + } + + public static class TestSignature + { + private final PGPSignature signature; + private final boolean expectValid; + private final String msg; + + public TestSignature(String armoredSignature, boolean expectValid) + throws IOException + { + this(armoredSignature, expectValid, null); + } + + public TestSignature(String armoredSignature, boolean expectValid, String msg) + throws IOException + { + this.signature = parseSignature(armoredSignature); + this.expectValid = expectValid; + this.msg = msg; + } + + private static PGPSignature parseSignature(String armoredSignature) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(armoredSignature.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + + PGPSignatureList sigs = (PGPSignatureList) objFac.nextObject(); + + pIn.close(); + aIn.close(); + bIn.close(); + + return sigs.get(0); + } + + public PGPSignature getSignature() + { + return signature; + } + + public boolean isExpectValid() + { + return expectValid; + } + + public String getMsg() + { + return msg; + } + } + + public static void main(String[] args) + { + runTest(new OpenPGPCertificateTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java new file mode 100644 index 0000000000..826788a7f3 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -0,0 +1,180 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; +import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class OpenPGPMessageGeneratorTest + extends AbstractPacketTest +{ + @Override + public String getName() + { + return "OpenPGPMessageGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + armoredLiteralDataPacket(); + unarmoredLiteralDataPacket(); + + armoredCompressedLiteralDataPacket(); + unarmoredCompressedLiteralDataPacket(); + + seipd1EncryptedMessage(); + seipd2EncryptedMessage(); + + seipd2EncryptedSignedMessage(); + } + + private void armoredLiteralDataPacket() + throws PGPException, IOException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.setIsPadded(false); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream msgOut = gen.open(bOut); + + // Only write a LiteralData packet with "Hello, World!" as content + msgOut.write("Hello, World!".getBytes(StandardCharsets.UTF_8)); + + msgOut.close(); + + isEquals( + "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "yxNiAAAAAABIZWxsbywgV29ybGQh\n" + + "-----END PGP MESSAGE-----\n", + bOut.toString()); + } + + private void unarmoredLiteralDataPacket() + throws PGPException, IOException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.setArmored(false); // disable ASCII armor + gen.setIsPadded(false); // disable padding + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream msgOut = gen.open(bOut); + + // Only write a LiteralData packet with "Hello, World!" as content + msgOut.write("Hello, World!".getBytes(StandardCharsets.UTF_8)); + + msgOut.close(); + + isEncodingEqual(Hex.decode("cb1362000000000048656c6c6f2c20576f726c6421"), bOut.toByteArray()); + } + + private void armoredCompressedLiteralDataPacket() + throws PGPException, IOException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.setIsPadded(false); + OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); + configuration.setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream msgOut = gen.open(bOut); + + // Only write a LiteralData packet with "Hello, World!" as content + msgOut.write("Hello, World!".getBytes(StandardCharsets.UTF_8)); + + msgOut.close(); + + isEquals("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "yBUBOy2cxAACHqk5Ofk6CuH5RTkpigA=\n" + + "-----END PGP MESSAGE-----\n", + bOut.toString()); + } + + private void unarmoredCompressedLiteralDataPacket() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.setArmored(false); // no armor + gen.setIsPadded(false); + OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); + configuration.setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream msgOut = gen.open(bOut); + + // Only write a LiteralData packet with "Hello, World!" as content + msgOut.write("Hello, World!".getBytes(StandardCharsets.UTF_8)); + + msgOut.close(); + + isEncodingEqual(Hex.decode("c815013b2d9cc400021ea93939f93a0ae1f94539298a00"), bOut.toByteArray()); + } + + private void seipd2EncryptedMessage() + throws IOException, PGPException + { + OpenPGPCertificate cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT); + + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(cert); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream encOut = gen.open(bOut); + encOut.write("Hello World!\n".getBytes(StandardCharsets.UTF_8)); + encOut.close(); + + System.out.println(bOut); + } + + private void seipd1EncryptedMessage() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(key); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream encOut = gen.open(bOut); + encOut.write("Hello World!\n".getBytes(StandardCharsets.UTF_8)); + encOut.close(); + + System.out.println(bOut); + } + + private void seipd2EncryptedSignedMessage() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setIsPadded(true) + .setArmored(true) + .addSigningKey(key) + .addEncryptionCertificate(key); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream encOut = gen.open(bOut); + encOut.write("Hello, World!\n".getBytes()); + encOut.close(); + + System.out.println(bOut); + } + + public static void main(String[] args) + { + runTest(new OpenPGPMessageGeneratorTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java new file mode 100644 index 0000000000..57234132d8 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -0,0 +1,664 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; +import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor; +import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class OpenPGPMessageProcessorTest + extends AbstractPacketTest +{ + private static final byte[] PLAINTEXT = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + private PGPSessionKey encryptionSessionKey; + + @Override + public String getName() + { + return "OpenPGPMessageProcessorTest"; + } + + @Override + public void performTest() + throws Exception + { + roundtripUnarmoredPlaintextMessage(); + roundtripArmoredPlaintextMessage(); + roundTripCompressedMessage(); + roundTripCompressedSymEncMessageMessage(); + + roundTripSymEncMessageWithMultiplePassphrases(); + + roundTripV4KeyEncryptedMessageAlice(); + roundTripV4KeyEncryptedMessageBob(); + roundTripV4KeyEncryptedMessageCarol(); + + roundTripV6KeyEncryptedMessage(); + encryptWithV4V6KeyDecryptWithV4(); + encryptWithV4V6KeyDecryptWithV6(); + + encryptDecryptWithLockedKey(); + encryptDecryptWithMissingKey(); + + inlineSignWithV4KeyAlice(); + inlineSignWithV4KeyBob(); + inlineSignWithV4KeyCarol(); + inlineSignWithV6Key(); + + verifyMessageByRevokedKey(); + incompleteMessageProcessing(); + } + + private void roundtripUnarmoredPlaintextMessage() + throws PGPException, IOException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setArmored(false) + .setIsPadded(false); + + gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.UNCOMPRESSED); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream msgOut = gen.open(bOut); + msgOut.write(PLAINTEXT); + msgOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageInputStream plainIn = processor.process(bIn); + ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); + Streams.pipeAll(plainIn, plainOut); + plainIn.close(); + isEquals(MessageEncryptionMechanism.unencrypted(), plainIn.getResult().getEncryptionMethod()); + + isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); + } + + private void roundtripArmoredPlaintextMessage() + throws PGPException, IOException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setArmored(true) + .setIsPadded(false); + gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.UNCOMPRESSED); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream msgOut = gen.open(bOut); + msgOut.write(PLAINTEXT); + msgOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageInputStream plainIn = processor.process(bIn); + ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); + Streams.pipeAll(plainIn, plainOut); + plainIn.close(); + OpenPGPMessageInputStream.Result result = plainIn.getResult(); + isEquals(MessageEncryptionMechanism.unencrypted(), result.getEncryptionMethod()); + + isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); + } + + private void roundTripCompressedMessage() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setArmored(true) + .setIsPadded(false); + gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream msgOut = gen.open(bOut); + msgOut.write(PLAINTEXT); + msgOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + InputStream plainIn = processor.process(bIn); + ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); + Streams.pipeAll(plainIn, plainOut); + plainIn.close(); + + isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); + } + + private void roundTripCompressedSymEncMessageMessage() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setArmored(true) + .addEncryptionPassphrase("lal".toCharArray()) + .setSessionKeyExtractionCallback( + sk -> this.encryptionSessionKey = sk + ) + .setIsPadded(false); + gen.getConfiguration() + .setPasswordBasedEncryptionNegotiator(conf -> + MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256)) + .setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream msgOut = gen.open(bOut); + msgOut.write(PLAINTEXT); + msgOut.close(); + isNotNull(encryptionSessionKey); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageInputStream plainIn = new OpenPGPMessageProcessor() + .addMessagePassphrase("lal".toCharArray()) + .process(bIn); + ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); + Streams.pipeAll(plainIn, plainOut); + plainIn.close(); + OpenPGPMessageInputStream.Result result = plainIn.getResult(); + isEquals(CompressionAlgorithmTags.ZIP, result.getCompressionAlgorithm()); + isTrue(Arrays.areEqual("lal".toCharArray(), result.getDecryptionPassphrase())); + isEncodingEqual(encryptionSessionKey.getKey(), result.getSessionKey().getKey()); + + isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); + } + + private void roundTripSymEncMessageWithMultiplePassphrases() + throws PGPException, IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .addEncryptionPassphrase("orange".toCharArray()) + .addEncryptionPassphrase("violet".toCharArray()) + .setSessionKeyExtractionCallback(sk -> this.encryptionSessionKey = sk); + gen.getConfiguration().setPasswordBasedEncryptionNegotiator(configuration -> + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); + + OutputStream encOut = gen.open(bOut); + encOut.write(PLAINTEXT); + encOut.close(); + + byte[] ciphertext = bOut.toByteArray(); + ByteArrayInputStream bIn = new ByteArrayInputStream(ciphertext); + bOut = new ByteArrayOutputStream(); + + // Try decryption with explicitly set message passphrase + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addMessagePassphrase("violet".toCharArray()); + OpenPGPMessageInputStream decIn = processor.process(bIn); + Streams.pipeAll(decIn, bOut); + decIn.close(); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isTrue(Arrays.areEqual("violet".toCharArray(), result.getDecryptionPassphrase())); + isEncodingEqual(encryptionSessionKey.getKey(), result.getSessionKey().getKey()); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + isEquals(result.getEncryptionMethod(), + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); + + // Try decryption with wrong passphrase and then request proper one dynamically + bOut = new ByteArrayOutputStream(); + bIn = new ByteArrayInputStream(ciphertext); + processor = new OpenPGPMessageProcessor(); + decIn = processor.setMissingMessagePassphraseCallback(new StackPassphraseCallback("orange".toCharArray())) + // wrong passphrase, so missing callback is invoked + .addMessagePassphrase("yellow".toCharArray()) + .process(bIn); + + Streams.pipeAll(decIn, bOut); + decIn.close(); + result = decIn.getResult(); + isTrue(Arrays.areEqual("orange".toCharArray(), result.getDecryptionPassphrase())); + isEncodingEqual(encryptionSessionKey.getKey(), result.getSessionKey().getKey()); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + } + + private void roundTripV4KeyEncryptedMessageAlice() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream enc = gen.open(bOut); + enc.write(PLAINTEXT); + enc.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + + OpenPGPMessageInputStream decIn = processor.process(bIn); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, bOut); + isEncodingEqual(bOut.toByteArray(), PLAINTEXT); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), + result.getEncryptionMethod()); + } + + private void roundTripV4KeyEncryptedMessageBob() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT)); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream enc = gen.open(bOut); + enc.write(PLAINTEXT); + enc.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY)); + + OpenPGPMessageInputStream decIn = processor.process(bIn); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, bOut); + decIn.close(); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), + result.getEncryptionMethod()); + isEncodingEqual(bOut.toByteArray(), PLAINTEXT); + } + + private void roundTripV4KeyEncryptedMessageCarol() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.CAROL_CERT)); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream enc = gen.open(bOut); + enc.write(PLAINTEXT); + enc.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.CAROL_KEY)); + + OpenPGPMessageInputStream decIn = processor.process(bIn); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, bOut); + decIn.close(); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), + result.getEncryptionMethod()); + isEncodingEqual(bOut.toByteArray(), PLAINTEXT); + } + + private void roundTripV6KeyEncryptedMessage() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setArmored(true) + .addEncryptionCertificate(key) + .setIsPadded(false); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream msgOut = gen.open(bOut); + msgOut.write(PLAINTEXT); + msgOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addDecryptionKey(key); + + OpenPGPMessageInputStream plainIn = processor.process(bIn); + ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); + Streams.pipeAll(plainIn, plainOut); + plainIn.close(); + OpenPGPMessageInputStream.Result result = plainIn.getResult(); + isEquals(MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB), + result.getEncryptionMethod()); + + isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); + } + + private void encryptWithV4V6KeyDecryptWithV4() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT)); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream enc = gen.open(bOut); + enc.write(PLAINTEXT); + enc.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + + OpenPGPMessageInputStream decIn = processor.process(bIn); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, bOut); + decIn.close(); + isEncodingEqual(bOut.toByteArray(), PLAINTEXT); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), + result.getEncryptionMethod()); + } + + private void encryptWithV4V6KeyDecryptWithV6() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT)); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OutputStream enc = gen.open(bOut); + enc.write(PLAINTEXT); + enc.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY)); + + OpenPGPMessageInputStream decIn = processor.process(bIn); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, bOut); + isEncodingEqual(bOut.toByteArray(), PLAINTEXT); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), + result.getEncryptionMethod()); + } + + private void encryptDecryptWithLockedKey() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY_LOCKED); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OpenPGPMessageOutputStream encOut = new OpenPGPMessageGenerator() + .addEncryptionCertificate(key) + .open(bOut); + + encOut.write(PLAINTEXT); + encOut.close(); + + byte[] ciphertext = bOut.toByteArray(); + + // Provide passphrase and key together + ByteArrayInputStream bIn = new ByteArrayInputStream(ciphertext); + bOut = new ByteArrayOutputStream(); + OpenPGPMessageInputStream decIn = new OpenPGPMessageProcessor() + .addDecryptionKey(key, OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) + .process(bIn); + Streams.pipeAll(decIn, bOut); + decIn.close(); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + OpenPGPMessageInputStream.Result result = decIn.getResult(); + PGPSessionKey sk = result.getSessionKey(); + + // Provide passphrase and key separate from another + bIn = new ByteArrayInputStream(ciphertext); + bOut = new ByteArrayOutputStream(); + decIn = new OpenPGPMessageProcessor() + .addDecryptionKey(key) + .addDecryptionKeyPassphrase(OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) + .process(bIn); + Streams.pipeAll(decIn, bOut); + decIn.close(); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + result = decIn.getResult(); + isEncodingEqual(sk.getKey(), result.getSessionKey().getKey()); + + // Provide passphrase dynamically + bIn = new ByteArrayInputStream(ciphertext); + bOut = new ByteArrayOutputStream(); + decIn = new OpenPGPMessageProcessor() + .addDecryptionKey(key) + .setMissingOpenPGPKeyPassphraseProvider(k -> + OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) + .process(bIn); + Streams.pipeAll(decIn, bOut); + decIn.close(); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + + result = decIn.getResult(); + isEncodingEqual(sk.getKey(), result.getSessionKey().getKey()); + } + + private void encryptDecryptWithMissingKey() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OutputStream encOut = new OpenPGPMessageGenerator() + .addEncryptionCertificate(key) + .open(bOut); + + encOut.write(PLAINTEXT); + encOut.close(); + + byte[] ciphertext = bOut.toByteArray(); + + // Provide passphrase and key together + ByteArrayInputStream bIn = new ByteArrayInputStream(ciphertext); + bOut = new ByteArrayOutputStream(); + OpenPGPMessageInputStream decIn = new OpenPGPMessageProcessor() + .setMissingOpenPGPKeyProvider(id -> key) + .process(bIn); + Streams.pipeAll(decIn, bOut); + decIn.close(); + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + + OpenPGPMessageInputStream.Result result = decIn.getResult(); + isEquals(key, result.getDecryptionKey().getCertificate()); + isNotNull(result.getSessionKey()); + } + + private void inlineSignWithV4KeyAlice() + throws IOException, PGPException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPKey aliceKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY); + gen.addSigningKey(aliceKey); + + OutputStream signOut = gen.open(bOut); + signOut.write(PLAINTEXT); + signOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + bOut = new ByteArrayOutputStream(); + + OpenPGPCertificate aliceCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addVerificationCertificate(aliceCert); + + OpenPGPMessageInputStream verifIn = processor.process(bIn); + Streams.pipeAll(verifIn, bOut); + verifIn.close(); + OpenPGPMessageInputStream.Result result = verifIn.getResult(); + isEquals(MessageEncryptionMechanism.unencrypted(), result.getEncryptionMethod()); + List signatures = result.getSignatures(); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature sig = signatures.get(0); + isEquals(aliceCert, sig.getIssuerCertificate()); + + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + } + + private void inlineSignWithV4KeyBob() + throws IOException, PGPException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPKey bobKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + gen.addSigningKey(bobKey); + + OutputStream signOut = gen.open(bOut); + signOut.write(PLAINTEXT); + signOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + bOut = new ByteArrayOutputStream(); + + OpenPGPCertificate bobCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addVerificationCertificate(bobCert); + + OpenPGPMessageInputStream verifIn = processor.process(bIn); + Streams.pipeAll(verifIn, bOut); + verifIn.close(); + OpenPGPMessageInputStream.Result result = verifIn.getResult(); + List signatures = result.getSignatures(); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature sig = signatures.get(0); + isEquals(bobCert, sig.getIssuerCertificate()); + + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + } + + private void inlineSignWithV4KeyCarol() + throws PGPException, IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPKey carolKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.CAROL_KEY); + gen.addSigningKey(carolKey); + + OutputStream signOut = gen.open(bOut); + signOut.write(PLAINTEXT); + signOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + bOut = new ByteArrayOutputStream(); + + OpenPGPCertificate carolCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.CAROL_CERT); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addVerificationCertificate(carolCert); + + OpenPGPMessageInputStream verifIn = processor.process(bIn); + Streams.pipeAll(verifIn, bOut); + verifIn.close(); + OpenPGPMessageInputStream.Result result = verifIn.getResult(); + List signatures = result.getSignatures(); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature sig = signatures.get(0); + isEquals(carolCert, sig.getIssuerCertificate()); + + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + } + + private void inlineSignWithV6Key() + throws PGPException, IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPKey v6Key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + gen.addSigningKey(v6Key); + + OutputStream signOut = gen.open(bOut); + signOut.write(PLAINTEXT); + signOut.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + bOut = new ByteArrayOutputStream(); + + OpenPGPCertificate v6Cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addVerificationCertificate(v6Cert); + + OpenPGPMessageInputStream verifIn = processor.process(bIn); + Streams.pipeAll(verifIn, bOut); + verifIn.close(); + OpenPGPMessageInputStream.Result result = verifIn.getResult(); + List signatures = result.getSignatures(); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature sig = signatures.get(0); + isEquals(v6Cert, sig.getIssuerCertificate()); + + isEncodingEqual(PLAINTEXT, bOut.toByteArray()); + } + + private void verifyMessageByRevokedKey() + throws PGPException, IOException + { + // Create a minimal signed message + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY); + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + gen.addSigningKey(key); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream oOut = gen.open(bOut); + oOut.write("Hello, World!\n".getBytes()); + oOut.close(); + + // Load the certificate and import its revocation signature + OpenPGPCertificate cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT); + cert = OpenPGPCertificate.join(cert, OpenPGPTestKeys.ALICE_REVOCATION_CERT); + + // Process the signed message using the revoked key + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addVerificationCertificate(cert); + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageInputStream oIn = processor.process(bIn); + Streams.drain(oIn); + oIn.close(); + + OpenPGPMessageInputStream.Result result = oIn.getResult(); + OpenPGPSignature.OpenPGPDocumentSignature sig = result.getSignatures().get(0); + // signature is no valid + isFalse(sig.isValid()); + } + + private void incompleteMessageProcessing() + throws IOException, PGPException + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)) + .addSigningKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY)); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream out = gen.open(bOut); + + out.write("Some Data".getBytes(StandardCharsets.UTF_8)); + out.close(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + .addVerificationCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT)) + .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + OpenPGPMessageInputStream in = processor.process(bIn); + + // read a single byte (not the entire message) + in.read(); + + in.close(); + OpenPGPMessageInputStream.Result result = in.getResult(); + OpenPGPSignature.OpenPGPDocumentSignature sig = result.getSignatures().get(0); + isFalse(sig.isValid()); + } + + public static void main(String[] args) + { + runTest(new OpenPGPMessageProcessorTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java new file mode 100644 index 0000000000..33c8463ba8 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java @@ -0,0 +1,38 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.openpgp.api.MissingPassphraseCallback; + +import java.util.Collection; +import java.util.Collections; +import java.util.Stack; + +/** + * Test implementation of {@link MissingPassphraseCallback} which provides passphrases by popping + * them from a provided {@link Stack}. + */ +public class StackPassphraseCallback + implements MissingPassphraseCallback +{ + private final Stack passphases; + + public StackPassphraseCallback(char[] passphrase) + { + this(Collections.singleton(passphrase)); + } + + public StackPassphraseCallback(Collection passphrases) + { + this.passphases = new Stack<>(); + this.passphases.addAll(passphrases); + } + + @Override + public char[] getPassphrase() + { + if (passphases.isEmpty()) + { + return null; + } + return passphases.pop(); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java new file mode 100644 index 0000000000..c838f34ce9 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java @@ -0,0 +1,92 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; +import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +public class StaticV6OpenPGPMessageGeneratorTest + extends AbstractPacketTest +{ + KeyIdentifier signingKeyIdentifier = new KeyIdentifier( + Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9")); + KeyIdentifier encryptionKeyIdentifier = new KeyIdentifier( + Hex.decode("12C83F1E706F6308FE151A417743A1F033790E93E9978488D1DB378DA9930885")); + + @Override + public String getName() + { + return "StaticV6OpenPGPMessageGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + staticEncryptedMessage(); + staticSignedMessage(); + } + + private void staticEncryptedMessage() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + + OpenPGPMessageGenerator gen = getStaticGenerator() + .addEncryptionCertificate(key); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream pgOut = (OpenPGPMessageOutputStream) gen.open(bOut); + pgOut.write("Hello, World!\n".getBytes(StandardCharsets.UTF_8)); + pgOut.close(); + + System.out.println(bOut); + } + + private void staticSignedMessage() + throws IOException, PGPException + { + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPMessageGenerator gen = getStaticGenerator() + .addSigningKey(key); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + OpenPGPMessageOutputStream pgOut = (OpenPGPMessageOutputStream) gen.open(bOut); + pgOut.write("Hello, World!\n".getBytes(StandardCharsets.UTF_8)); + pgOut.close(); + + System.out.println(bOut); + } + + /** + * Return a pre-configured {@link OpenPGPMessageGenerator} which has the complex logic of evaluating + * recipient keys to determine suitable subkeys, algorithms etc. swapped out for static configuration + * tailored to the V6 test key. + * + * @return static message generator + */ + public OpenPGPMessageGenerator getStaticGenerator() + { + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + + gen.getConfiguration() + .setEncryptionKeySelector(keyRing -> Collections.singletonList(keyRing.getKey(encryptionKeyIdentifier))) + .setSigningKeySelector(keyRing -> Collections.singletonList(keyRing.getKey(signingKeyIdentifier))); + + return gen; + } + + public static void main(String[] args) + { + runTest(new StaticV6OpenPGPMessageGeneratorTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index 270336aec4..e7db0b0388 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,7 +3,10 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; +import org.bouncycastle.openpgp.api.test.OpenPGPMessageGeneratorTest; +import org.bouncycastle.openpgp.api.test.OpenPGPMessageProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; +import org.bouncycastle.openpgp.api.test.StaticV6OpenPGPMessageGeneratorTest; import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; @@ -88,7 +91,11 @@ public class RegressionTest new PGPv6SignatureTest(), new PGPKeyPairGeneratorTest(), new OpenPGPV6KeyGeneratorTest(), - new PGPKeyRingGeneratorTest() + new PGPKeyRingGeneratorTest(), + + new OpenPGPMessageGeneratorTest(), + new OpenPGPMessageProcessorTest(), + new StaticV6OpenPGPMessageGeneratorTest() }; public static void main(String[] args) From 4629848a1b13e71d2ebba2b3133d1284e5f3a378 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 13:13:59 +0100 Subject: [PATCH 010/154] Adapt KeyIdentifier changes --- .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 12 ++++-------- .../org/bouncycastle/openpgp/api/OpenPGPKey.java | 2 +- .../openpgp/api/OpenPGPKeyMaterialPool.java | 2 +- .../openpgp/api/OpenPGPKeyMaterialProvider.java | 2 +- .../openpgp/api/OpenPGPMessageGenerator.java | 2 +- .../openpgp/api/OpenPGPMessageInputStream.java | 2 +- .../openpgp/api/OpenPGPMessageProcessor.java | 2 +- .../bouncycastle/openpgp/api/OpenPGPSignature.java | 2 +- .../openpgp/api/test/OpenPGPCertificateTest.java | 2 +- .../test/StaticV6OpenPGPMessageGeneratorTest.java | 2 +- 10 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index c34d3c2207..76d933b41c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -4,24 +4,20 @@ import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.openpgp.*; import org.bouncycastle.openpgp.api.exception.IncorrectPGPSignatureException; -import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.util.Iterable; -import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -96,7 +92,7 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati { PGPPublicKey rawSubkey = rawKeys.next(); OpenPGPSubkey subkey = new OpenPGPSubkey(rawSubkey, this); - subkeys.put(new KeyIdentifier(rawSubkey), subkey); + subkeys.put(rawSubkey.getKeyIdentifier(), subkey); processSubkey(subkey); } } @@ -215,7 +211,7 @@ public List getKeys() */ public OpenPGPComponentKey getKey(KeyIdentifier identifier) { - if (identifier.matches(getPrimaryKey().getPGPPublicKey())) + if (identifier.matches(getPrimaryKey().getPGPPublicKey().getKeyIdentifier())) { return primaryKey; } @@ -1028,7 +1024,7 @@ public PGPPublicKey getPGPPublicKey() */ public KeyIdentifier getKeyIdentifier() { - return new KeyIdentifier(rawPubkey); + return rawPubkey.getKeyIdentifier(); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index af61448fb0..baa09c8f29 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -3,9 +3,9 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.SecretKeyPacket; -import org.bouncycastle.openpgp.KeyIdentifier; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPrivateKey; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java index ac32725c8b..ea5c5d77e4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialPool.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.bcpg.KeyIdentifier; import java.util.Collection; import java.util.HashMap; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java index c842fcaba5..61b0af8c73 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyMaterialProvider.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.bcpg.KeyIdentifier; /** * Interface for providing OpenPGP keys or certificates. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 52e817823f..205afcd974 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -4,6 +4,7 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; @@ -11,7 +12,6 @@ import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.crypto.CryptoServicesRegistrar; -import org.bouncycastle.openpgp.KeyIdentifier; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPException; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 49f0a208bf..80c6d7b9ea 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -3,7 +3,7 @@ import org.bouncycastle.bcpg.AEADEncDataPacket; import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; -import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index b5f0288f13..f62899db8c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -1,7 +1,7 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.InputStreamPacket; -import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index c6c1cbb19f..f705c01759 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -1,10 +1,10 @@ package org.bouncycastle.openpgp.api; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.openpgp.KeyIdentifier; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPSignature; diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index 56b38916a7..b866a270e7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -2,10 +2,10 @@ import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.openpgp.KeyIdentifier; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPSignature; diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java index c838f34ce9..39570c6402 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java @@ -1,7 +1,7 @@ package org.bouncycastle.openpgp.api.test; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.openpgp.KeyIdentifier; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPKey; From fc6a625d922ca4211346e623899e3dffc1de1015 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 11 Dec 2024 13:42:46 +0100 Subject: [PATCH 011/154] Add fromInputStream() methods for OpenPGPKey and OpenPGPCertificate --- .../openpgp/api/OpenPGPCertificate.java | 20 +++++++++++++++++++ .../bouncycastle/openpgp/api/OpenPGPKey.java | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 76d933b41c..a3c76d257b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -18,6 +18,7 @@ import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.bouncycastle.util.io.Streams; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -126,6 +127,25 @@ public static OpenPGPCertificate fromAsciiArmor( implementation); } + public static OpenPGPCertificate fromInputStream(InputStream inputStream) + throws IOException + { + return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); + } + + public static OpenPGPCertificate fromInputStream(InputStream inputStream, OpenPGPImplementation implementation) + throws IOException + { + byte[] bytes = Streams.readAll(inputStream); + return fromBytes(bytes, implementation); + } + + public static OpenPGPCertificate fromBytes(byte[] bytes) + throws IOException + { + return fromBytes(bytes, OpenPGPImplementation.getInstance()); + } + public static OpenPGPCertificate fromBytes( byte[] bytes, OpenPGPImplementation implementation) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index baa09c8f29..be4ac05457 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -14,6 +14,7 @@ import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.util.io.Streams; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -112,6 +113,25 @@ public static OpenPGPKey fromAsciiArmor( implementation); } + public static OpenPGPKey fromInputStream(InputStream inputStream) + throws IOException + { + return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); + } + + public static OpenPGPKey fromInputStream(InputStream inputStream, OpenPGPImplementation implementation) + throws IOException + { + return fromBytes(Streams.readAll(inputStream), implementation); + } + + public static OpenPGPKey fromBytes( + byte[] bytes) + throws IOException + { + return fromBytes(bytes, OpenPGPImplementation.getInstance()); + } + public static OpenPGPKey fromBytes( byte[] bytes, OpenPGPImplementation implementation) From 90855b0152e0058109a3ae58c9d8b8ec369d104b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:05:26 +0100 Subject: [PATCH 012/154] PGPEncryptedDataGenerator: Allow extraction of session-key --- .../org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java index 1cb794fc77..05cdbb6047 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java @@ -142,6 +142,11 @@ public void addMethod(PGPKeyEncryptionMethodGenerator method) } + public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) + { + this.sessionKeyExtractionCallback = callback; + } + public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) { this.sessionKeyExtractionCallback = callback; From 6e7381a833a1125e75b2a9c838cdc215fca27356 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Dec 2024 14:35:18 +0100 Subject: [PATCH 013/154] OpenPGPV6KeyGenerator: Return OpenPGPKeys --- .../openpgp/api/BcOpenPGPImplementation.java | 40 ++++++ .../openpgp/api/JcaOpenPGPImplementation.java | 49 +++++++ .../openpgp/api/OpenPGPCertificate.java | 2 +- .../openpgp/api/OpenPGPImplementation.java | 12 ++ .../openpgp/api/OpenPGPV6KeyGenerator.java | 134 +++++++++--------- .../api/bc/BcOpenPGPV6KeyGenerator.java | 33 +---- .../api/jcajce/JcaOpenPGPV6KeyGenerator.java | 37 +---- .../api/test/OpenPGPV6KeyGeneratorTest.java | 43 +++--- 8 files changed, 211 insertions(+), 139 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java index b826ea6c26..10b99c822e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java @@ -7,23 +7,32 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcCFBSecretKeyEncryptorFactory; +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.BcPBESecretKeyDecryptorBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; 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.BcPGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory; @@ -106,4 +115,35 @@ public PGPDigestCalculatorProvider pgpDigestCalculatorProvider() { return new BcPGPDigestCalculatorProvider(); } + + @Override + public PGPKeyPairGeneratorProvider pgpKeyPairGeneratorProvider() + { + return new BcPGPKeyPairGeneratorProvider(); + } + + @Override + public PGPContentSignerBuilderProvider pgpContentSignerBuilderProvider(int hashAlgorithmId) + { + return new BcPGPContentSignerBuilderProvider(hashAlgorithmId); + } + + @Override + public KeyFingerPrintCalculator keyFingerPrintCalculator() + { + return new BcKeyFingerprintCalculator(); + } + + @Override + public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) + { + if (aead) + { + return new BcAEADSecretKeyEncryptorFactory(); + } + else + { + return new BcCFBSecretKeyEncryptorFactory(); + } + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java index c1db969bbd..273c06dd93 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java @@ -9,19 +9,28 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaCFBSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilderProvider; @@ -154,4 +163,44 @@ public PGPDigestCalculatorProvider pgpDigestCalculatorProvider() .setProvider(provider) .build(); } + + @Override + public PGPKeyPairGeneratorProvider pgpKeyPairGeneratorProvider() + { + return new JcaPGPKeyPairGeneratorProvider() + .setProvider(provider) + .setSecureRandom(secureRandom); + } + + @Override + public PGPContentSignerBuilderProvider pgpContentSignerBuilderProvider(int hashAlgorithmId) + { + return new JcaPGPContentSignerBuilderProvider(hashAlgorithmId) + .setSecurityProvider(provider) + .setDigestProvider(provider) + .setSecureRandom(secureRandom); + } + + @Override + public KeyFingerPrintCalculator keyFingerPrintCalculator() + { + return new JcaKeyFingerprintCalculator() + .setProvider(provider); + } + + @Override + public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) + throws PGPException + { + if (aead) + { + return new JcaAEADSecretKeyEncryptorFactory() + .setProvider(provider); + } + else + { + return new JcaCFBSecretKeyEncryptorFactory() + .setProvider(provider); + } + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index a3c76d257b..f32df16fea 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -53,7 +53,7 @@ */ public class OpenPGPCertificate { - private final OpenPGPImplementation implementation; + final OpenPGPImplementation implementation; private final PGPKeyRing keyRing; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java index 94e96021b4..b91f9fbbda 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -6,13 +6,17 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; @@ -180,4 +184,12 @@ public abstract PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory( */ public abstract PGPDigestCalculatorProvider pgpDigestCalculatorProvider() throws PGPException; + + public abstract PGPKeyPairGeneratorProvider pgpKeyPairGeneratorProvider(); + + public abstract PGPContentSignerBuilderProvider pgpContentSignerBuilderProvider(int hashAlgorithmId); + + public abstract KeyFingerPrintCalculator keyFingerPrintCalculator(); + + public abstract PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) throws PGPException; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index e512b3a3e8..d625f5b505 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -177,8 +177,23 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa } }; - private final Implementation impl; // contains BC or JCA/JCE implementations - private final Configuration conf; + private final OpenPGPImplementation implementationProvider; + private final Configuration configuration; // contains BC or JCA/JCE implementations + + public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, + int signatureHashAlgorithmId, + boolean aead, + Date creationTime) throws PGPException { + this( + implementationProvider, + implementationProvider.pgpKeyPairGeneratorProvider(), + implementationProvider.pgpContentSignerBuilderProvider(signatureHashAlgorithmId), + implementationProvider.pgpDigestCalculatorProvider(), + implementationProvider.pbeSecretKeyEncryptorFactory(aead), + implementationProvider.keyFingerPrintCalculator(), + creationTime + ); + } /** * Generate a new OpenPGP key generator for v6 keys. @@ -191,6 +206,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa * @param creationTime key creation time */ public OpenPGPV6KeyGenerator( + OpenPGPImplementation implementationProvider, PGPKeyPairGeneratorProvider kpGenProvider, PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, @@ -198,8 +214,8 @@ public OpenPGPV6KeyGenerator( KeyFingerPrintCalculator keyFingerPrintCalculator, Date creationTime) { - this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); - this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); + this.implementationProvider = implementationProvider; + this.configuration = new Configuration(creationTime, kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); } /** @@ -215,7 +231,7 @@ public OpenPGPV6KeyGenerator( * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing classicKey(String userId, char[] passphrase) + public OpenPGPKey classicKey(String userId, char[] passphrase) throws PGPException { return withPrimaryKey() @@ -235,7 +251,7 @@ public PGPSecretKeyRing classicKey(String userId, char[] passphrase) * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing ed25519x25519Key(String userId, char[] passphrase) + public OpenPGPKey ed25519x25519Key(String userId, char[] passphrase) throws PGPException { return withPrimaryKey(new KeyPairGeneratorCallback() @@ -277,7 +293,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing ed448x448Key(String userId, char[] passphrase) + public OpenPGPKey ed448x448Key(String userId, char[] passphrase) throws PGPException { return withPrimaryKey(new KeyPairGeneratorCallback() @@ -317,7 +333,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) * @return sign-only (+certify) OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing signOnlyKey(char[] passphrase) + public OpenPGPKey signOnlyKey(char[] passphrase) throws PGPException { return signOnlyKey(passphrase, null); @@ -334,14 +350,14 @@ public PGPSecretKeyRing signOnlyKey(char[] passphrase) * @return sign-only (+certify) OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing signOnlyKey( + public OpenPGPKey signOnlyKey( char[] passphrase, SignatureSubpacketsFunction userSubpackets) throws PGPException { - PGPKeyPair primaryKeyPair = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime) + PGPKeyPair primaryKeyPair = configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime) .generatePrimaryKey(); - PBESecretKeyEncryptor encryptor = impl.keyEncryptorBuilderProvider + PBESecretKeyEncryptor encryptor = configuration.keyEncryptorBuilderProvider .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); return signOnlyKey(primaryKeyPair, encryptor, userSubpackets); } @@ -358,7 +374,7 @@ public PGPSecretKeyRing signOnlyKey( * @return sign-only (+certify) OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing signOnlyKey( + public OpenPGPKey signOnlyKey( PGPKeyPair primaryKeyPair, PBESecretKeyEncryptor keyEncryptor, SignatureSubpacketsFunction userSubpackets) @@ -499,8 +515,8 @@ public WithPrimaryKey withPrimaryKey( throws PGPException { PGPKeyPair primaryKeyPair = keyGenCallback.generateFrom( - impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); - PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider + configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); + PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); return withPrimaryKey(primaryKeyPair, directKeySubpackets, keyEncryptor); } @@ -544,7 +560,7 @@ public WithPrimaryKey withPrimaryKey( public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) { subpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.keyCreationTime); + subpackets.setSignatureCreationTime(configuration.keyCreationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); @@ -579,7 +595,7 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( { // DK sig PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(primaryKeyPair.getPublicKey()), + configuration.contentSignerBuilderProvider.get(primaryKeyPair.getPublicKey()), primaryKeyPair.getPublicKey()); dkSigGen.init(PGPSignature.DIRECT_KEY, primaryKeyPair.getPrivateKey()); @@ -606,7 +622,7 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( Key primaryKey = new Key(primaryKeyPair, keyEncryptor); - return new WithPrimaryKey(impl, conf, primaryKey); + return new WithPrimaryKey(implementationProvider, configuration, primaryKey); } /** @@ -615,9 +631,8 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( */ public static class WithPrimaryKey { - - private final Implementation impl; - private final Configuration conf; + private final OpenPGPImplementation implementation; + private final Configuration configuration; private Key primaryKey; private final List subkeys = new ArrayList(); @@ -625,13 +640,12 @@ public static class WithPrimaryKey * Builder. * * @param implementation cryptographic implementation - * @param configuration key configuration * @param primaryKey specified primary key */ - private WithPrimaryKey(Implementation implementation, Configuration configuration, Key primaryKey) + private WithPrimaryKey(OpenPGPImplementation implementation, Configuration configuration, Key primaryKey) { - this.impl = implementation; - this.conf = configuration; + this.implementation = implementation; + this.configuration = configuration; this.primaryKey = primaryKey; } @@ -692,13 +706,13 @@ public WithPrimaryKey addUserId( } PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + configuration.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), primaryKey.pair.getPublicKey()); uidSigGen.init(certificationType, primaryKey.pair.getPrivateKey()); PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.keyCreationTime); + subpackets.setSignatureCreationTime(configuration.keyCreationTime); if (userIdSubpackets != null) { @@ -762,9 +776,9 @@ public WithPrimaryKey addEncryptionSubkey( SignatureSubpacketsFunction bindingSubpacketsCallback) throws PGPException { - PGPKeyPairGenerator generator = impl.kpGenProvider.get( + PGPKeyPairGenerator generator = configuration.kpGenProvider.get( primaryKey.pair.getPublicKey().getVersion(), - conf.keyCreationTime + configuration.keyCreationTime ); PGPKeyPair subkey = generatorCallback.generateFrom(generator); @@ -840,9 +854,9 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac throws PGPException { PGPKeyPair subkey = keyGenCallback.generateFrom( - impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); - subkey = subkey.asSubkey(impl.keyFingerprintCalculator); - PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); + configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); + subkey = subkey.asSubkey(configuration.keyFingerprintCalculator); + PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addEncryptionSubkey(subkey, bindingSignatureCallback, keyEncryptor); } @@ -878,7 +892,7 @@ public WithPrimaryKey addEncryptionSubkey( // generate binding signature PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.keyCreationTime); + subpackets.setSignatureCreationTime(configuration.keyCreationTime); subpackets = ENCRYPTION_SUBKEY_SUBPACKETS.apply(subpackets); // allow subpacket customization @@ -995,9 +1009,9 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, char[] passphrase) throws PGPException { - PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); - subkey = subkey.asSubkey(impl.keyFingerprintCalculator); - PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); + PGPKeyPair subkey = keyGenCallback.generateFrom(configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); + subkey = subkey.asSubkey(configuration.keyFingerprintCalculator); + PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); } @@ -1037,7 +1051,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); backSigSubpackets.setIssuerFingerprint(true, signingSubkey.getPublicKey()); - backSigSubpackets.setSignatureCreationTime(conf.keyCreationTime); + backSigSubpackets.setSignatureCreationTime(configuration.keyCreationTime); if (backSignatureCallback != null) { backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); @@ -1045,12 +1059,12 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - bindingSigSubpackets.setSignatureCreationTime(conf.keyCreationTime); + bindingSigSubpackets.setSignatureCreationTime(configuration.keyCreationTime); bindingSigSubpackets = SIGNING_SUBKEY_SUBPACKETS.apply(bindingSigSubpackets); PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(signingSubkey.getPublicKey()), + configuration.contentSignerBuilderProvider.get(signingSubkey.getPublicKey()), signingSubkey.getPublicKey()); backSigGen.init(PGPSignature.PRIMARYKEY_BINDING, signingSubkey.getPrivateKey()); backSigGen.setHashedSubpackets(backSigSubpackets.generate()); @@ -1079,13 +1093,13 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing build() + public OpenPGPKey build() throws PGPException { PGPSecretKey primarySecretKey = new PGPSecretKey( primaryKey.pair.getPrivateKey(), primaryKey.pair.getPublicKey(), - impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), true, primaryKey.encryptor); List keys = new ArrayList(); @@ -1097,13 +1111,14 @@ public PGPSecretKeyRing build() PGPSecretKey subkey = new PGPSecretKey( key.pair.getPrivateKey(), key.pair.getPublicKey(), - impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), false, key.encryptor); keys.add(subkey); } - return new PGPSecretKeyRing(keys); + PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(keys); + return new OpenPGPKey(secretKeys, implementation); } /** @@ -1114,16 +1129,16 @@ public PGPSecretKeyRing build() * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing build(char[] passphrase) + public OpenPGPKey build(char[] passphrase) throws PGPException { - PBESecretKeyEncryptor primaryKeyEncryptor = impl.keyEncryptorBuilderProvider + PBESecretKeyEncryptor primaryKeyEncryptor = configuration.keyEncryptorBuilderProvider .build(passphrase, primaryKey.pair.getPublicKey().getPublicKeyPacket()); sanitizeKeyEncryptor(primaryKeyEncryptor); PGPSecretKey primarySecretKey = new PGPSecretKey( primaryKey.pair.getPrivateKey(), primaryKey.pair.getPublicKey(), - impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), true, primaryKeyEncryptor); List keys = new ArrayList(); @@ -1132,13 +1147,13 @@ public PGPSecretKeyRing build(char[] passphrase) for (Iterator it = subkeys.iterator(); it.hasNext();) { Key key = (Key)it.next(); - PBESecretKeyEncryptor subkeyEncryptor = impl.keyEncryptorBuilderProvider + PBESecretKeyEncryptor subkeyEncryptor = configuration.keyEncryptorBuilderProvider .build(passphrase, key.pair.getPublicKey().getPublicKeyPacket()); sanitizeKeyEncryptor(subkeyEncryptor); PGPSecretKey subkey = new PGPSecretKey( key.pair.getPrivateKey(), key.pair.getPublicKey(), - impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), false, subkeyEncryptor); keys.add(subkey); @@ -1149,7 +1164,8 @@ public PGPSecretKeyRing build(char[] passphrase) Arrays.fill(passphrase, (char)0); } - return new PGPSecretKeyRing(keys); + PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(keys); + return new OpenPGPKey(secretKeys, implementation); } protected void sanitizeKeyEncryptor(PBESecretKeyEncryptor keyEncryptor) @@ -1183,7 +1199,7 @@ private PGPPublicKey getPublicSubKey(PGPKeyPair encryptionSubkey, SignatureSubpa } PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + configuration.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), primaryKey.pair.getPublicKey()); bindingSigGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.pair.getPrivateKey()); bindingSigGen.setHashedSubpackets(subpackets.generate()); @@ -1196,20 +1212,23 @@ private PGPPublicKey getPublicSubKey(PGPKeyPair encryptionSubkey, SignatureSubpa /** * Bundle implementation-specific provider classes. */ - private static class Implementation + private static class Configuration { + final Date keyCreationTime; final PGPKeyPairGeneratorProvider kpGenProvider; final PGPContentSignerBuilderProvider contentSignerBuilderProvider; final PGPDigestCalculatorProvider digestCalculatorProvider; final PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider; final KeyFingerPrintCalculator keyFingerprintCalculator; - public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, + public Configuration(Date keyCreationTime, + PGPKeyPairGeneratorProvider keyPairGeneratorProvider, PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider, KeyFingerPrintCalculator keyFingerPrintCalculator) { + this.keyCreationTime = new Date((keyCreationTime.getTime() / 1000) * 1000); this.kpGenProvider = keyPairGeneratorProvider; this.contentSignerBuilderProvider = contentSignerBuilderProvider; this.digestCalculatorProvider = digestCalculatorProvider; @@ -1218,19 +1237,6 @@ public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, } } - /** - * Bundle configuration-specific data. - */ - private static class Configuration - { - final Date keyCreationTime; - - public Configuration(Date keyCreationTime) - { - this.keyCreationTime = keyCreationTime; - } - } - /** * Tuple of a {@link PGPKeyPair} and (nullable) {@link PBESecretKeyEncryptor}. */ diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java index 8953dc4ce9..52c6cb7b76 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java @@ -1,13 +1,8 @@ package org.bouncycastle.openpgp.api.bc; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.BcOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcCFBSecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; import java.util.Date; @@ -22,6 +17,7 @@ public class BcOpenPGPV6KeyGenerator * Create a new key generator for OpenPGP v6 keys. */ public BcOpenPGPV6KeyGenerator() + throws PGPException { this(new Date()); } @@ -33,6 +29,7 @@ public BcOpenPGPV6KeyGenerator() * @param creationTime creation time of the generated OpenPGP key */ public BcOpenPGPV6KeyGenerator(Date creationTime) + throws PGPException { this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); } @@ -44,6 +41,7 @@ public BcOpenPGPV6KeyGenerator(Date creationTime) * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) + throws PGPException { this(signatureHashAlgorithm, new Date(), true); } @@ -55,25 +53,8 @@ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) * @param creationTime creation time of the key and signatures */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + throws PGPException { - super( - new BcPGPKeyPairGeneratorProvider(), - new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), - new BcPGPDigestCalculatorProvider(), - keyEncryptorFactory(aeadProtection), - new BcKeyFingerprintCalculator(), - creationTime); - } - - private static PBESecretKeyEncryptorFactory keyEncryptorFactory(boolean aeadProtection) - { - if (aeadProtection) - { - return new BcAEADSecretKeyEncryptorFactory(); - } - else - { - return new BcCFBSecretKeyEncryptorFactory(); - } + super(new BcOpenPGPImplementation(), signatureHashAlgorithm, aeadProtection, creationTime); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java index d0890f321e..311a23a5fa 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -1,16 +1,11 @@ package org.bouncycastle.openpgp.api.jcajce; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.JcaOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaCFBSecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilderProvider; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPairGeneratorProvider; import java.security.Provider; +import java.security.SecureRandom; import java.util.Date; public class JcaOpenPGPV6KeyGenerator @@ -45,29 +40,9 @@ public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, b throws PGPException { super( - new JcaPGPKeyPairGeneratorProvider() - .setProvider(provider), - new JcaPGPContentSignerBuilderProvider(signatureHashAlgorithm) - .setSecurityProvider(provider), - new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(provider) - .build(), - keyEncryptorFactory(provider, aeadProtection), - new JcaKeyFingerprintCalculator(), - creationTime); - } - - private static PBESecretKeyEncryptorFactory keyEncryptorFactory(Provider provider, boolean aeadProtection) - throws PGPException - { - if (aeadProtection) - { - return new JcaAEADSecretKeyEncryptorFactory().setProvider(provider); - } - else - { - return new JcaCFBSecretKeyEncryptorFactory().setProvider(provider); - - } + new JcaOpenPGPImplementation(provider, new SecureRandom()), + signatureHashAlgorithm, + aeadProtection, + creationTime); } } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index e69954ee6c..5e04693d1f 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -22,6 +22,7 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; @@ -52,8 +53,7 @@ public void performTest() @Override public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, Date creationTime, - boolean aeadProtection) - { + boolean aeadProtection) throws PGPException { return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); } }); @@ -95,7 +95,8 @@ private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) throws PGPException { OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(); - PGPSecretKeyRing secretKeys = generator.signOnlyKey(null); + OpenPGPKey key = generator.signOnlyKey(null); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)it.next(); @@ -122,7 +123,8 @@ private void testGenerateAEADProtectedSignOnlyKey(APIProvider apiProvider) throws PGPException { OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(true); - PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); + OpenPGPKey key = generator.signOnlyKey("passphrase".toCharArray()); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)it.next(); @@ -139,7 +141,8 @@ private void testGenerateCFBProtectedSignOnlyKey(APIProvider apiProvider) throws PGPException { OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(false); - PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); + OpenPGPKey key = generator.signOnlyKey("passphrase".toCharArray()); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)it.next(); @@ -157,8 +160,9 @@ private void testGenerateClassicKeyBaseCase(APIProvider apiProvider) { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); - PGPSecretKeyRing secretKeys = generator + OpenPGPKey key = generator .classicKey("Alice ", null); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator keys = secretKeys.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)keys.next(); @@ -207,8 +211,8 @@ private void testGenerateClassicKeyBaseCase(APIProvider apiProvider) // Test all keys are unprotected for (Iterator it = secretKeys.getSecretKeys(); it.hasNext();) { - PGPSecretKey key = (PGPSecretKey)it.next(); - isEquals("(Sub-)keys MUST be unprotected", SecretKeyPacket.USAGE_NONE, key.getS2KUsage()); + PGPSecretKey k = (PGPSecretKey)it.next(); + isEquals("(Sub-)keys MUST be unprotected", SecretKeyPacket.USAGE_NONE, k.getS2KUsage()); } } @@ -217,15 +221,16 @@ private void testGenerateProtectedTypicalKey(APIProvider apiProvider) { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); - PGPSecretKeyRing secretKeys = generator + OpenPGPKey key = generator .classicKey("Alice ", "passphrase".toCharArray()); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); // Test creation time for (Iterator it = secretKeys.toCertificate().iterator(); it.hasNext();) { - PGPPublicKey key = (PGPPublicKey)it.next(); - isEquals(creationTime, key.getCreationTime()); - for (Iterator its = key.getSignatures(); its.hasNext(); ) + PGPPublicKey k = (PGPPublicKey)it.next(); + isEquals(creationTime, k.getCreationTime()); + for (Iterator its = k.getSignatures(); its.hasNext(); ) { PGPSignature sig = (PGPSignature)its.next(); isEquals(creationTime, sig.getCreationTime()); @@ -240,8 +245,9 @@ private void testGenerateProtectedTypicalKey(APIProvider apiProvider) for (Iterator it = secretKeys.getSecretKeys(); it.hasNext();) { - PGPSecretKey key = (PGPSecretKey)it.next(); - isEquals("(Sub-)keys MUST be protected", SecretKeyPacket.USAGE_AEAD, key.getS2KUsage()); + PGPSecretKey k = (PGPSecretKey)it.next(); + isEquals("(Sub-)keys MUST be protected", SecretKeyPacket.USAGE_AEAD, k.getS2KUsage()); + } } @@ -252,7 +258,8 @@ private void testGenerateEd25519x25519Key(APIProvider apiProvider) String userId = "Foo "; OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); - PGPSecretKeyRing secretKey = generator.ed25519x25519Key(userId, null); + OpenPGPKey key = generator.ed25519x25519Key(userId, null); + PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator iterator = secretKey.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)iterator.next(); @@ -299,7 +306,8 @@ private void testGenerateEd448x448Key(APIProvider apiProvider) String userId = "Foo "; OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); - PGPSecretKeyRing secretKey = generator.ed448x448Key(userId, null); + OpenPGPKey key = generator.ed448x448Key(userId, null); + PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator iterator = secretKey.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)iterator.next(); @@ -345,7 +353,7 @@ private void testGenerateCustomKey(APIProvider apiProvider) Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); - PGPSecretKeyRing secretKey = generator + OpenPGPKey key = generator .withPrimaryKey( new KeyPairGeneratorCallback() { @@ -406,6 +414,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) "encryption-key-passphrase".toCharArray()) .build(); + PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator keyIt = secretKey.getSecretKeys(); PGPSecretKey primaryKey = (PGPSecretKey)keyIt.next(); isEquals("Primary key MUST be RSA_GENERAL", From 4c5841b893b3c2dad0fa076968ae2921854714e6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Dec 2024 17:07:58 +0100 Subject: [PATCH 014/154] Add OpenPGPKey.getPrimarySecretKey --- .../main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index be4ac05457..342faf188d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -152,6 +152,11 @@ public static OpenPGPKey fromBytes( return new OpenPGPKey(keyRing, implementation); } + public OpenPGPSecretKey getPrimarySecretKey() + { + return getSecretKey(getPrimaryKey()); + } + /** * Return a {@link Map} containing all {@link OpenPGPSecretKey} components (secret subkeys) of the key. * From ca2dd71b5dfd580c41c90e7b52cb8125bb1284ea Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Dec 2024 17:08:37 +0100 Subject: [PATCH 015/154] Introduce AbstractOpenPGPKeySignatureGenerator --- .../AbstractOpenPGPKeySignatureGenerator.java | 180 ++++++++++++++++++ .../openpgp/api/OpenPGPV6KeyGenerator.java | 139 +------------- 2 files changed, 185 insertions(+), 134 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java new file mode 100644 index 0000000000..d895d45f9c --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java @@ -0,0 +1,180 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; + +public abstract class AbstractOpenPGPKeySignatureGenerator +{ + + /** + * Standard AEAD encryption preferences (SEIPDv2). + * By default, only announce support for OCB + AES. + */ + protected SignatureSubpacketsFunction defaultAeadAlgorithmPreferences = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + subpackets.setPreferredAEADCiphersuites(PreferredAEADCiphersuites.builder(false) + .addCombination(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB) + .addCombination(SymmetricKeyAlgorithmTags.AES_192, AEADAlgorithmTags.OCB) + .addCombination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); + return subpackets; + } + }; + + /** + * Standard symmetric-key encryption preferences (SEIPDv1). + * By default, announce support for AES. + */ + protected SignatureSubpacketsFunction defaultSymmetricKeyPreferences = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); + subpackets.setPreferredSymmetricAlgorithms(false, new int[]{ + SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 + }); + return subpackets; + } + }; + + /** + * Standard signature hash algorithm preferences. + * By default, only announce SHA3 and SHA2 algorithms. + */ + protected SignatureSubpacketsFunction defaultHashAlgorithmPreferences = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_HASH_ALGS); + subpackets.setPreferredHashAlgorithms(false, new int[]{ + HashAlgorithmTags.SHA3_512, HashAlgorithmTags.SHA3_256, + HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256 + }); + return subpackets; + } + }; + + /** + * Standard compression algorithm preferences. + * By default, announce support for all known algorithms. + */ + protected SignatureSubpacketsFunction defaultCompressionAlgorithmPreferences = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); + subpackets.setPreferredCompressionAlgorithms(false, new int[]{ + CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, + CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2 + }); + return subpackets; + } + }; + + /** + * Standard features to announce. + * By default, announce SEIPDv1 (modification detection) and SEIPDv2. + */ + protected SignatureSubpacketsFunction defaultFeatures = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); + subpackets.setFeature(false, (byte)(Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); + return subpackets; + } + }; + + /** + * Standard signature subpackets for signing subkey's binding signatures. + * Sets the keyflag subpacket to SIGN_DATA. + */ + protected SignatureSubpacketsFunction signingSubkeySubpackets = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); + return subpackets; + } + }; + + /** + * Standard signature subpackets for encryption subkey's binding signatures. + * Sets the keyflag subpacket to ENCRYPT_STORAGE|ENCRYPT_COMMS. + */ + protected SignatureSubpacketsFunction encryptionSubkeySubpackets = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(true, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); + return subpackets; + } + }; + + /** + * Standard signature subpackets for the direct-key signature. + * Sets default features, hash-, compression-, symmetric-key-, and AEAD algorithm preferences. + */ + protected SignatureSubpacketsFunction directKeySignatureSubpackets = new SignatureSubpacketsFunction() + { + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets = defaultFeatures.apply(subpackets); + subpackets = defaultHashAlgorithmPreferences.apply(subpackets); + subpackets = defaultCompressionAlgorithmPreferences.apply(subpackets); + subpackets = defaultSymmetricKeyPreferences.apply(subpackets); + subpackets = defaultAeadAlgorithmPreferences.apply(subpackets); + return subpackets; + } + }; + + public void setDefaultAeadAlgorithmPreferences(SignatureSubpacketsFunction aeadAlgorithmPreferences) + { + this.defaultAeadAlgorithmPreferences = aeadAlgorithmPreferences; + } + + public void setDefaultSymmetricKeyPreferences(SignatureSubpacketsFunction symmetricKeyPreferences) + { + this.defaultSymmetricKeyPreferences = symmetricKeyPreferences; + } + + public void setDefaultHashAlgorithmPreferences(SignatureSubpacketsFunction hashAlgorithmPreferences) + { + this.defaultHashAlgorithmPreferences = hashAlgorithmPreferences; + } + + public void setDefaultCompressionAlgorithmPreferences(SignatureSubpacketsFunction compressionAlgorithmPreferences) + { + this.defaultCompressionAlgorithmPreferences = compressionAlgorithmPreferences; + } + + public void setDirectKeySignatureSubpackets(SignatureSubpacketsFunction directKeySignatureSubpackets) { + this.directKeySignatureSubpackets = directKeySignatureSubpackets; + } + + public void setDefaultFeatures(SignatureSubpacketsFunction features) + { + this.defaultFeatures = features; + } + + public void setSigningSubkeySubpackets(SignatureSubpacketsFunction signingSubkeySubpackets) + { + this.signingSubkeySubpackets = signingSubkeySubpackets; + } + + public void setEncryptionSubkeySubpackets(SignatureSubpacketsFunction encryptionSubkeySubpackets) + { + this.encryptionSubkeySubpackets = encryptionSubkeySubpackets; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index d625f5b505..a81f4060ff 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -6,18 +6,13 @@ import java.util.Iterator; import java.util.List; -import org.bouncycastle.bcpg.AEADAlgorithmTags; -import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.PublicSubkeyPacket; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SignatureSubpacketTags; -import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; @@ -39,6 +34,7 @@ * High-level generator class for OpenPGP v6 keys. */ public class OpenPGPV6KeyGenerator + extends AbstractOpenPGPKeySignatureGenerator { /** * Hash algorithm for key signatures if no other one is provided during construction. @@ -51,131 +47,6 @@ public class OpenPGPV6KeyGenerator private static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; private static final long SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY; - /** - * Standard AEAD encryption preferences (SEIPDv2). - * By default, only announce support for OCB + AES. - */ - public static SignatureSubpacketsFunction DEFAULT_AEAD_ALGORITHM_PREFERENCES = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); - subpackets.setPreferredAEADCiphersuites(PreferredAEADCiphersuites.builder(false) - .addCombination(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB) - .addCombination(SymmetricKeyAlgorithmTags.AES_192, AEADAlgorithmTags.OCB) - .addCombination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); - return subpackets; - } - }; - - /** - * Standard symmetric-key encryption preferences (SEIPDv1). - * By default, announce support for AES. - */ - public static SignatureSubpacketsFunction DEFAULT_SYMMETRIC_KEY_PREFERENCES = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); - subpackets.setPreferredSymmetricAlgorithms(false, new int[]{ - SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 - }); - return subpackets; - } - }; - - /** - * Standard signature hash algorithm preferences. - * By default, only announce SHA3 and SHA2 algorithms. - */ - public static SignatureSubpacketsFunction DEFAULT_HASH_ALGORITHM_PREFERENCES = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_HASH_ALGS); - subpackets.setPreferredHashAlgorithms(false, new int[]{ - HashAlgorithmTags.SHA3_512, HashAlgorithmTags.SHA3_256, - HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256 - }); - return subpackets; - } - }; - - /** - * Standard compression algorithm preferences. - * By default, announce support for all known algorithms. - */ - public static SignatureSubpacketsFunction DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); - subpackets.setPreferredCompressionAlgorithms(false, new int[]{ - CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, - CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2 - }); - return subpackets; - } - }; - - /** - * Standard features to announce. - * By default, announce SEIPDv1 (modification detection) and SEIPDv2. - */ - public static SignatureSubpacketsFunction DEFAULT_FEATURES = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); - subpackets.setFeature(false, (byte)(Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); - return subpackets; - } - }; - - /** - * Standard signature subpackets for signing subkey's binding signatures. - * Sets the keyflag subpacket to SIGN_DATA. - */ - public static SignatureSubpacketsFunction SIGNING_SUBKEY_SUBPACKETS = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); - subpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); - return subpackets; - } - }; - - /** - * Standard signature subpackets for encryption subkey's binding signatures. - * Sets the keyflag subpacket to ENCRYPT_STORAGE|ENCRYPT_COMMS. - */ - public static SignatureSubpacketsFunction ENCRYPTION_SUBKEY_SUBPACKETS = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); - subpackets.setKeyFlags(true, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); - return subpackets; - } - }; - - /** - * Standard signature subpackets for the direct-key signature. - * Sets default features, hash-, compression-, symmetric-key-, and AEAD algorithm preferences. - */ - public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets = DEFAULT_FEATURES.apply(subpackets); - subpackets = DEFAULT_HASH_ALGORITHM_PREFERENCES.apply(subpackets); - subpackets = DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES.apply(subpackets); - subpackets = DEFAULT_SYMMETRIC_KEY_PREFERENCES.apply(subpackets); - subpackets = DEFAULT_AEAD_ALGORITHM_PREFERENCES.apply(subpackets); - return subpackets; - } - }; private final OpenPGPImplementation implementationProvider; private final Configuration configuration; // contains BC or JCA/JCE implementations @@ -562,7 +433,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa subpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); subpackets.setSignatureCreationTime(configuration.keyCreationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); - subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); + subpackets = directKeySignatureSubpackets.apply(subpackets); subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); return subpackets; } @@ -629,7 +500,7 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( * Intermediate builder class. * Constructs an OpenPGP key from a specified primary key. */ - public static class WithPrimaryKey + public class WithPrimaryKey { private final OpenPGPImplementation implementation; private final Configuration configuration; @@ -893,7 +764,7 @@ public WithPrimaryKey addEncryptionSubkey( PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); subpackets.setSignatureCreationTime(configuration.keyCreationTime); - subpackets = ENCRYPTION_SUBKEY_SUBPACKETS.apply(subpackets); + subpackets = encryptionSubkeySubpackets.apply(subpackets); // allow subpacket customization PGPPublicKey publicSubkey = getPublicSubKey(encryptionSubkey, bindingSubpacketsCallback, subpackets); @@ -1061,7 +932,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); bindingSigSubpackets.setSignatureCreationTime(configuration.keyCreationTime); - bindingSigSubpackets = SIGNING_SUBKEY_SUBPACKETS.apply(bindingSigSubpackets); + bindingSigSubpackets = signingSubkeySubpackets.apply(bindingSigSubpackets); PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( configuration.contentSignerBuilderProvider.get(signingSubkey.getPublicKey()), From da8931505e74c2ae4e58b822d261e312c564749b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Dec 2024 17:08:52 +0100 Subject: [PATCH 016/154] WIP: Start working on an OpenPGPKeyEditor --- .../openpgp/api/OpenPGPKeyEditor.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java new file mode 100644 index 0000000000..a0536b5d15 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -0,0 +1,88 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; + +import java.util.Date; + +public class OpenPGPKeyEditor + extends AbstractOpenPGPKeySignatureGenerator +{ + + private final OpenPGPImplementation implementation; + private OpenPGPKey key; + + public OpenPGPKeyEditor(OpenPGPKey key) + { + this(key, key.implementation); + } + + public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation) + { + this.key = key; + this.implementation = implementation; + } + + public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) + throws PGPException + { + return addUserId(userId, PGPSignature.POSITIVE_CERTIFICATION, null, HashAlgorithmTags.SHA3_512, new Date(), primaryKeyPassphrase); + } + + public OpenPGPKeyEditor addUserId(String userId, + int certificationType, + SignatureSubpacketsFunction userIdSubpackets, + int hashAlgorithmId, + Date bindingTime, + char[] primaryKeyPassphrase) + throws PGPException + { + if (userId == null || userId.trim().isEmpty()) + { + throw new IllegalArgumentException("User-ID cannot be null or empty."); + } + + if (!PGPSignature.isCertification(certificationType)) + { + throw new IllegalArgumentException("Signature type MUST be a certification type (0x10 - 0x13)"); + } + + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder(publicPrimaryKey.getAlgorithm(), hashAlgorithmId), + publicPrimaryKey); + uidSigGen.init(certificationType, privatePrimaryKey); + + PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); + subpackets.setIssuerFingerprint(true, publicPrimaryKey); + subpackets.setSignatureCreationTime(bindingTime); + + if (userIdSubpackets != null) + { + subpackets = userIdSubpackets.apply(subpackets); + } + uidSigGen.setHashedSubpackets(subpackets.generate()); + + PGPSignature uidSig = uidSigGen.generateCertification(userId, publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId, uidSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation); + return this; + } + + public OpenPGPKey done() + { + return key; + } +} From 43008eb9d7906273c49b141eec9ccfd3b439591a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Dec 2024 12:52:00 +0100 Subject: [PATCH 017/154] Fix checkstyle issues --- .../openpgp/api/AbstractOpenPGPKeySignatureGenerator.java | 3 ++- .../org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java index d895d45f9c..927131bce9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java @@ -159,7 +159,8 @@ public void setDefaultCompressionAlgorithmPreferences(SignatureSubpacketsFunctio this.defaultCompressionAlgorithmPreferences = compressionAlgorithmPreferences; } - public void setDirectKeySignatureSubpackets(SignatureSubpacketsFunction directKeySignatureSubpackets) { + public void setDirectKeySignatureSubpackets(SignatureSubpacketsFunction directKeySignatureSubpackets) + { this.directKeySignatureSubpackets = directKeySignatureSubpackets; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index a81f4060ff..4ae6f8a4f1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -54,7 +54,9 @@ public class OpenPGPV6KeyGenerator public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, int signatureHashAlgorithmId, boolean aead, - Date creationTime) throws PGPException { + Date creationTime) + throws PGPException + { this( implementationProvider, implementationProvider.pgpKeyPairGeneratorProvider(), From 7847bd48ab668928654f21df33f40b8cb517c207 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Dec 2024 12:52:19 +0100 Subject: [PATCH 018/154] Fix signature verification --- .../bouncycastle/openpgp/api/OpenPGPMessageInputStream.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 80c6d7b9ea..3c8d38a86d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -219,8 +219,8 @@ public int read(byte[] b, int off, int len) int i = in.read(b, off, len); if (i >= 0) { - layer.onePassSignatures.update(b, off, len); - layer.prefixedSignatures.update(b, off, len); + layer.onePassSignatures.update(b, off, i); + layer.prefixedSignatures.update(b, off, i); } return i; } From d0d469839251e53b6568974a3a46629db459c0e8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Dec 2024 12:53:44 +0100 Subject: [PATCH 019/154] Signature validity: Take into consideration validity of signing subkey and primary key --- .../java/org/bouncycastle/openpgp/api/OpenPGPSignature.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index f705c01759..380e63a0d1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -529,7 +529,9 @@ public boolean isValidAt(Date date) { return false; } - return issuer.isSigningKey(date); + return issuer.getCertificate().getPrimaryKey().isBoundAt(date) && + issuer.isBoundAt(date) && + issuer.isSigningKey(date); } } } From ca92a683577f93f9e5645d1c311199bbc5511589 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 2 Jan 2025 13:08:42 +0100 Subject: [PATCH 020/154] Remove duplicate method --- .../org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java index 05cdbb6047..046e5c1c1e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java @@ -141,12 +141,6 @@ public void addMethod(PGPKeyEncryptionMethodGenerator method) methods.add(method); } - - public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) - { - this.sessionKeyExtractionCallback = callback; - } - public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) { this.sessionKeyExtractionCallback = callback; From f36b6381a3f4c83c90b6306e6dc834f41caa3d7c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:05:26 +0100 Subject: [PATCH 021/154] PGPEncryptedDataGenerator: Allow extraction of session-key --- .../org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java index 046e5c1c1e..44b9d55cb5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java @@ -146,6 +146,11 @@ public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callbac this.sessionKeyExtractionCallback = callback; } + public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) + { + this.sessionKeyExtractionCallback = callback; + } + /** * Create an OutputStream based on the configured methods. *

From 94c20b497a1a8f5edee69c3ef870b39470320403 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Dec 2024 12:07:03 +0100 Subject: [PATCH 022/154] API --- .../org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java index 44b9d55cb5..046e5c1c1e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java @@ -146,11 +146,6 @@ public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callbac this.sessionKeyExtractionCallback = callback; } - public void setSessionKeyExtractionCallback(SessionKeyExtractionCallback callback) - { - this.sessionKeyExtractionCallback = callback; - } - /** * Create an OutputStream based on the configured methods. *

From 77e8a0dbd90ac6dfec216cebef1b63e293909051 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 14:50:53 +0200 Subject: [PATCH 023/154] Add PGPKeyPairGenerator class + tests --- .../openpgp/PGPV6KeyRingGenerator.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java new file mode 100644 index 0000000000..fce6a106a9 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java @@ -0,0 +1,31 @@ +package org.bouncycastle.openpgp; + +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; + +public class PGPV6KeyRingGenerator +{ + public PGPV6KeyRingGenerator( + PGPKeyPair primaryKey, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder keySignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + + } + + public PGPV6KeyRingGenerator( + PGPSecretKeyRing originalSecretRing, + PBESecretKeyDecryptor secretKeyDecryptor, + PGPDigestCalculator checksumCalculator, + PGPContentSignerBuilder keySignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + + } +} From 99326085e8acbc93f2ca0b5fafd38633af2d2aaf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Oct 2024 11:53:18 +0200 Subject: [PATCH 024/154] Implement high-level OpenPGPV6KeyGenerator class --- .../openpgp/PGPV6KeyRingGenerator.java | 31 -------- .../test/PGPV6KeyRingGeneratorTest.java | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 31 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java deleted file mode 100644 index fce6a106a9..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.bouncycastle.openpgp; - -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; - -public class PGPV6KeyRingGenerator -{ - public PGPV6KeyRingGenerator( - PGPKeyPair primaryKey, - PGPSignatureSubpacketVector hashedPcks, - PGPSignatureSubpacketVector unhashedPcks, - PGPContentSignerBuilder keySignerBuilder, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { - - } - - public PGPV6KeyRingGenerator( - PGPSecretKeyRing originalSecretRing, - PBESecretKeyDecryptor secretKeyDecryptor, - PGPDigestCalculator checksumCalculator, - PGPContentSignerBuilder keySignerBuilder, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { - - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java new file mode 100644 index 0000000000..b4e5b3213d --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java @@ -0,0 +1,71 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; + +public class PGPV6KeyRingGeneratorTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "PGPV6KeyRingGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + testGenerateMinimalKey(); + } + + private void testGenerateMinimalKey() + throws PGPException, IOException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( + new BcPGPKeyPairGeneratorProvider(), + new BcPGPContentSignerBuilderProvider(HashAlgorithmTags.SHA3_512), + new BcPGPDigestCalculatorProvider(), + creationTime + ); + PGPSecretKeyRing secretKeys = gen.withPrimaryKey( + PGPKeyPairGenerator::generateEd25519KeyPair, + subpackets -> + { + subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); + return subpackets; + }, + null) + .addUserId("Alice ") + .addEncryptionSubkey(null) + .addSigningSubkey(null) + .build(); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKeys.encode(pOut); + pOut.close(); + aOut.close(); + System.out.println(bOut); + } + + public static void main(String[] args) + { + runTest(new PGPV6KeyRingGeneratorTest()); + } +} From 25e6cd63cff6efdcccfa1ac4130295ae0716101a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:26:04 +0200 Subject: [PATCH 025/154] Remove methods for adding deprecated UserAttributes --- .../test/PGPV6KeyRingGeneratorTest.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java index b4e5b3213d..4310cca635 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java @@ -6,11 +6,10 @@ import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.BcOpenPGPImplementation; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -37,12 +36,8 @@ private void testGenerateMinimalKey() { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( - new BcPGPKeyPairGeneratorProvider(), - new BcPGPContentSignerBuilderProvider(HashAlgorithmTags.SHA3_512), - new BcPGPDigestCalculatorProvider(), - creationTime - ); - PGPSecretKeyRing secretKeys = gen.withPrimaryKey( + new BcOpenPGPImplementation(), HashAlgorithmTags.SHA3_512, false, creationTime); + OpenPGPKey key = gen.withPrimaryKey( PGPKeyPairGenerator::generateEd25519KeyPair, subpackets -> { @@ -51,9 +46,10 @@ private void testGenerateMinimalKey() }, null) .addUserId("Alice ") - .addEncryptionSubkey(null) - .addSigningSubkey(null) + .addEncryptionSubkey((char[]) null) + .addSigningSubkey((char[]) null) .build(); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); From a4e3c179258a70a4e26b064e25f46162635268ef Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:26:41 +0200 Subject: [PATCH 026/154] Introduce BC implementation of OpenPGPV6KeyGenerator --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 48 +++++++++++++++++++ .../test/BcOpenPGPV6KeyGeneratorTest.java} | 45 ++++++++++------- 2 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java rename pg/src/test/java/org/bouncycastle/openpgp/{test/PGPV6KeyRingGeneratorTest.java => api/test/BcOpenPGPV6KeyGeneratorTest.java} (58%) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java new file mode 100644 index 0000000000..3ac4107d67 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -0,0 +1,48 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; + +import java.util.Date; + +/** + * Bouncy Castle implementation of {@link OpenPGPV6KeyGenerator}. + */ +public class BcOpenPGPV6KeyGenerator + extends OpenPGPV6KeyGenerator +{ + + public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; + + public BcOpenPGPV6KeyGenerator() + { + this(DEFAULT_SIGNATURE_HASH_ALGORITHM); + } + + public BcOpenPGPV6KeyGenerator(Date creationTime) + { + this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); + } + + public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) + { + this(signatureHashAlgorithm, new Date()); + } + + /** + * Generate a new OpenPGP key generator for v6 keys. + * + * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key + * @param creationTime creation time of the key and signatures + */ + public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) + { + super( + new BcPGPKeyPairGeneratorProvider(), + new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), + new BcPGPDigestCalculatorProvider(), + creationTime); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java similarity index 58% rename from pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java rename to pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 4310cca635..5a56bebeed 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -1,27 +1,26 @@ -package org.bouncycastle.openpgp.test; +package org.bouncycastle.openpgp.api.test; -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.api.BcOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.Date; +import java.util.Iterator; -public class PGPV6KeyRingGeneratorTest +public class BcOpenPGPV6KeyGeneratorTest extends AbstractPgpKeyPairTest { @Override public String getName() { - return "PGPV6KeyRingGeneratorTest"; + return "OpenPGPV6KeyGeneratorTest"; } @Override @@ -32,13 +31,13 @@ public void performTest() } private void testGenerateMinimalKey() - throws PGPException, IOException + throws PGPException { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( new BcOpenPGPImplementation(), HashAlgorithmTags.SHA3_512, false, creationTime); OpenPGPKey key = gen.withPrimaryKey( - PGPKeyPairGenerator::generateEd25519KeyPair, + PGPKeyPairGenerator::generateEd25519KeyPair, subpackets -> { subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); @@ -51,17 +50,27 @@ private void testGenerateMinimalKey() .build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); - BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); - secretKeys.encode(pOut); - pOut.close(); - aOut.close(); - System.out.println(bOut); + // Test creation time + for (PGPPublicKey k : secretKeys.toCertificate()) + { + isEquals(creationTime, k.getCreationTime()); + for (Iterator it = k.getSignatures(); it.hasNext(); ) { + PGPSignature sig = it.next(); + isEquals(creationTime, sig.getCreationTime()); + } + } + + PGPPublicKey primaryKey = secretKeys.getPublicKey(); + // Test UIDs + Iterator uids = primaryKey.getUserIDs(); + isEquals("Alice ", uids.next()); + isFalse(uids.hasNext()); + + } public static void main(String[] args) { - runTest(new PGPV6KeyRingGeneratorTest()); + runTest(new BcOpenPGPV6KeyGeneratorTest()); } } From 06a515ee5e7f2762acd40d3ec27b095e654bbb6d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:39:54 +0200 Subject: [PATCH 027/154] Add BcOpenPGPV6KeyGeneratorTest to RegressionTests --- .../java/org/bouncycastle/openpgp/test/RegressionTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index e7db0b0388..2a89a600ae 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,10 +3,14 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; +<<<<<<< HEAD import org.bouncycastle.openpgp.api.test.OpenPGPMessageGeneratorTest; import org.bouncycastle.openpgp.api.test.OpenPGPMessageProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; import org.bouncycastle.openpgp.api.test.StaticV6OpenPGPMessageGeneratorTest; +======= +import org.bouncycastle.openpgp.api.test.BcOpenPGPV6KeyGeneratorTest; +>>>>>>> 8473d7b16 (Add BcOpenPGPV6KeyGeneratorTest to RegressionTests) import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; @@ -92,6 +96,7 @@ public class RegressionTest new PGPKeyPairGeneratorTest(), new OpenPGPV6KeyGeneratorTest(), new PGPKeyRingGeneratorTest(), + new BcOpenPGPV6KeyGeneratorTest(), new OpenPGPMessageGeneratorTest(), new OpenPGPMessageProcessorTest(), From 77a2c1b45dafe48938c61aa8b735f2c1736706df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:40:03 +0200 Subject: [PATCH 028/154] Javadoc, Checkstyle --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java index 3ac4107d67..f0b9e7e611 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -1,6 +1,5 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; @@ -14,25 +13,38 @@ public class BcOpenPGPV6KeyGenerator extends OpenPGPV6KeyGenerator { - public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; - + /** + * Create a new key generator for OpenPGP v6 keys. + */ public BcOpenPGPV6KeyGenerator() { this(DEFAULT_SIGNATURE_HASH_ALGORITHM); } + /** + * Create a new key generator for OpenPGP v6 keys. + * The key creation time will be set to {@code creationTime} + * + * @param creationTime creation time of the generated OpenPGP key + */ public BcOpenPGPV6KeyGenerator(Date creationTime) { this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); } + /** + * Create a new key generator for OpenPGP v6 keys. + * Signatures on the key will be generated using the specified {@code signatureHashAlgorithm}. + * + * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation + */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) { this(signatureHashAlgorithm, new Date()); } /** - * Generate a new OpenPGP key generator for v6 keys. + * Create a new OpenPGP key generator for v6 keys. * * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures From 6f76b48c5896ecdc418aaba801c9247f8489fa74 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Oct 2024 23:15:31 +0200 Subject: [PATCH 029/154] AEAD secret key encryption --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 14 ++++++++------ .../AEADSecretKeyEncryptorBuilderProvider.java | 12 ++++++++++++ .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 18 ++++++++++++++---- .../openpgp/test/RegressionTest.java | 3 --- 4 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java index f0b9e7e611..31abc88f80 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -1,8 +1,6 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; +import org.bouncycastle.openpgp.PGPException; import java.util.Date; @@ -17,6 +15,7 @@ public class BcOpenPGPV6KeyGenerator * Create a new key generator for OpenPGP v6 keys. */ public BcOpenPGPV6KeyGenerator() + throws PGPException { this(DEFAULT_SIGNATURE_HASH_ALGORITHM); } @@ -28,6 +27,7 @@ public BcOpenPGPV6KeyGenerator() * @param creationTime creation time of the generated OpenPGP key */ public BcOpenPGPV6KeyGenerator(Date creationTime) + throws PGPException { this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); } @@ -39,6 +39,7 @@ public BcOpenPGPV6KeyGenerator(Date creationTime) * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) + throws PGPException { this(signatureHashAlgorithm, new Date()); } @@ -50,11 +51,12 @@ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) * @param creationTime creation time of the key and signatures */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) + throws PGPException { super( - new BcPGPKeyPairGeneratorProvider(), - new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), - new BcPGPDigestCalculatorProvider(), + new BcOpenPGPImplementation(), + signatureHashAlgorithm, + false, creationTime); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java new file mode 100644 index 0000000000..5f6056b391 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java @@ -0,0 +1,12 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.bcpg.S2K; + +public abstract class AEADSecretKeyEncryptorBuilderProvider +{ + + public abstract AEADSecretKeyEncryptorBuilder get( + int aeadAlgorithm, + int symmetricAlgorithm, + S2K.Argon2Params argon2Params); +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 5a56bebeed..42e7067d17 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -1,6 +1,9 @@ package org.bouncycastle.openpgp.api.test; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -11,6 +14,8 @@ import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Date; import java.util.Iterator; @@ -31,8 +36,7 @@ public void performTest() } private void testGenerateMinimalKey() - throws PGPException - { + throws PGPException, IOException { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( new BcOpenPGPImplementation(), HashAlgorithmTags.SHA3_512, false, creationTime); @@ -43,7 +47,7 @@ private void testGenerateMinimalKey() subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); return subpackets; }, - null) + "hello".toCharArray()) .addUserId("Alice ") .addEncryptionSubkey((char[]) null) .addSigningSubkey((char[]) null) @@ -66,7 +70,13 @@ private void testGenerateMinimalKey() isEquals("Alice ", uids.next()); isFalse(uids.hasNext()); - + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKeys.encode(pOut); + pOut.close(); + aOut.close(); + System.out.println(bOut); } public static void main(String[] args) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index 2a89a600ae..b9c8b866ea 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,14 +3,11 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; -<<<<<<< HEAD import org.bouncycastle.openpgp.api.test.OpenPGPMessageGeneratorTest; import org.bouncycastle.openpgp.api.test.OpenPGPMessageProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; import org.bouncycastle.openpgp.api.test.StaticV6OpenPGPMessageGeneratorTest; -======= import org.bouncycastle.openpgp.api.test.BcOpenPGPV6KeyGeneratorTest; ->>>>>>> 8473d7b16 (Add BcOpenPGPV6KeyGeneratorTest to RegressionTests) import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; From ccbc35bb00fac6a541358dff8ff2d27353a46d22 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 15:37:53 +0200 Subject: [PATCH 030/154] Further progress with the new KeyGenerator API --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 62 ------------------- .../api/test/OpenPGPV6KeyGeneratorTest.java | 4 +- 2 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java deleted file mode 100644 index 31abc88f80..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.bouncycastle.openpgp.api; - -import org.bouncycastle.openpgp.PGPException; - -import java.util.Date; - -/** - * Bouncy Castle implementation of {@link OpenPGPV6KeyGenerator}. - */ -public class BcOpenPGPV6KeyGenerator - extends OpenPGPV6KeyGenerator -{ - - /** - * Create a new key generator for OpenPGP v6 keys. - */ - public BcOpenPGPV6KeyGenerator() - throws PGPException - { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM); - } - - /** - * Create a new key generator for OpenPGP v6 keys. - * The key creation time will be set to {@code creationTime} - * - * @param creationTime creation time of the generated OpenPGP key - */ - public BcOpenPGPV6KeyGenerator(Date creationTime) - throws PGPException - { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); - } - - /** - * Create a new key generator for OpenPGP v6 keys. - * Signatures on the key will be generated using the specified {@code signatureHashAlgorithm}. - * - * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation - */ - public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) - throws PGPException - { - this(signatureHashAlgorithm, new Date()); - } - - /** - * Create a new OpenPGP key generator for v6 keys. - * - * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key - * @param creationTime creation time of the key and signatures - */ - public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) - throws PGPException - { - super( - new BcOpenPGPImplementation(), - signatureHashAlgorithm, - false, - creationTime); - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 5e04693d1f..7ed7efe239 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -53,7 +53,9 @@ public void performTest() @Override public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, Date creationTime, - boolean aeadProtection) throws PGPException { + boolean aeadProtection) + throws PGPException + { return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); } }); From bd9fca42045e79b49a234dbc49e41ba893e7bd93 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 19:23:33 +0200 Subject: [PATCH 031/154] Add more javadoc to OpenPGPV6KeyGenerator --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 4ae6f8a4f1..c0c11e581d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -292,6 +292,16 @@ public WithPrimaryKey withPrimaryKey() return withPrimaryKey((SignatureSubpacketsFunction)null); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * + * @param keyGenCallback nullable callback to modify the direct-key signatures subpackets + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback) throws PGPException From 872b85e80e6894b710eb88b9c7225905f75c25e7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 16:10:15 +0200 Subject: [PATCH 032/154] Remove unused AEADSecretKeyEncryptorBuilderProvider class --- .../AEADSecretKeyEncryptorBuilderProvider.java | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java deleted file mode 100644 index 5f6056b391..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.bouncycastle.openpgp.operator; - -import org.bouncycastle.bcpg.S2K; - -public abstract class AEADSecretKeyEncryptorBuilderProvider -{ - - public abstract AEADSecretKeyEncryptorBuilder get( - int aeadAlgorithm, - int symmetricAlgorithm, - S2K.Argon2Params argon2Params); -} From 5716328b5c3bdbaa81fb1fe87628209ac7df85c0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 11:37:13 +0100 Subject: [PATCH 033/154] Implement AEADProtectedPGPSecretKeyTest.reencryptKeyJca() --- .../test/AEADProtectedPGPSecretKeyTest.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java index c9eb712af7..df48813c75 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java @@ -20,6 +20,7 @@ import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator; import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -45,6 +46,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.util.Strings; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.util.encoders.Hex; public class AEADProtectedPGPSecretKeyTest @@ -365,14 +367,57 @@ private void lockUnlockKeyJca( keyPair.getPrivateKey().getPrivateKeyDataPacket().getEncoded(), dec.getPrivateKeyDataPacket().getEncoded()); } - private void reencryptKey() throws PGPException { + private void reencryptKey() + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException + { reencryptKeyBc(); reencryptKeyJca(); } private void reencryptKeyJca() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, PGPException { + BouncyCastleProvider prov = new BouncyCastleProvider(); + KeyPairGenerator eddsaGen = KeyPairGenerator.getInstance("EdDSA", prov); + eddsaGen.initialize(new ECNamedCurveGenParameterSpec("ed25519")); + KeyPair kp = eddsaGen.generateKeyPair(); + Date creationTime = currentTimeRounded(); + String passphrase = "recycle"; + + PGPKeyPair keyPair = new JcaPGPKeyPair(PublicKeyPacket.VERSION_6, PublicKeyAlgorithmTags.Ed25519, kp, creationTime); + PBESecretKeyEncryptor cfbEncBuilder = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128) + .setProvider(prov) + .setSecureRandom(CryptoServicesRegistrar.getSecureRandom()) + .build(passphrase.toCharArray()); + PGPDigestCalculatorProvider digestProv = new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(prov) + .build(); + + // Encrypt key using CFB mode + PGPSecretKey cfbEncKey = new PGPSecretKey( + keyPair.getPrivateKey(), + keyPair.getPublicKey(), + digestProv.get(HashAlgorithmTags.SHA1), + true, + cfbEncBuilder); + + PBESecretKeyDecryptor cfbDecryptor = new JcePBESecretKeyDecryptorBuilder(digestProv) + .setProvider(prov) + .build(passphrase.toCharArray()); + + JcaAEADSecretKeyEncryptorBuilder aeadEncBuilder = new JcaAEADSecretKeyEncryptorBuilder( + AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_128, S2K.Argon2Params.memoryConstrainedParameters()) + .setProvider(prov); + + PGPSecretKey aeadEncKey = PGPSecretKey.copyWithNewPassword( + cfbEncKey, + cfbDecryptor, + aeadEncBuilder.build(passphrase.toCharArray(), cfbEncKey.getPublicKey().getPublicKeyPacket())); + PBESecretKeyDecryptor aeadDecryptor = new JcePBESecretKeyDecryptorBuilder(digestProv) + .setProvider(prov) + .build(passphrase.toCharArray()); + isNotNull(aeadEncKey.extractPrivateKey(aeadDecryptor)); } private void reencryptKeyBc() From 8447e50522178b7402dc61bc34c72a3574186940 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 11 Dec 2024 15:52:53 +0100 Subject: [PATCH 034/154] Add OpenPGPDetachedSignature generator and processor --- .../OpenPGPDetachedSignatureGenerator.java | 103 +++++++++++++ .../OpenPGPDetachedSignatureProcessor.java | 139 ++++++++++++++++++ .../openpgp/api/OpenPGPMessageGenerator.java | 5 +- .../exception/InvalidSigningKeyException.java | 12 ++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java new file mode 100644 index 0000000000..45793ce60a --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -0,0 +1,103 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class OpenPGPDetachedSignatureGenerator +{ + private final OpenPGPImplementation implementation; + private int signatureType = PGPSignature.BINARY_DOCUMENT; + + private final List signatureGenerators = new ArrayList<>(); + private final List signingKeys = new ArrayList<>(); + + public OpenPGPDetachedSignatureGenerator() + { + this(OpenPGPImplementation.getInstance()); + } + + public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation) + { + this.implementation = implementation; + } + + public OpenPGPDetachedSignatureGenerator setBinarySignature() + { + this.signatureType = PGPSignature.BINARY_DOCUMENT; + return this; + } + + public OpenPGPDetachedSignatureGenerator setCanonicalTextDocument() + { + this.signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; + return this; + } + + public OpenPGPDetachedSignatureGenerator addSigningKey(OpenPGPKey key, char[] passphrase) + throws PGPException + { + List signingSubkeys = key.getSigningKeys(); + if (signingSubkeys.isEmpty()) + { + throw new InvalidSigningKeyException("Key " + key.getPrettyFingerprint() + " cannot sign."); + } + OpenPGPKey.OpenPGPSecretKey signingKey = key.getSecretKey(signingSubkeys.get(0)); + + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + signingKey.getPublicKey().getPGPPublicKey().getAlgorithm(), + getPreferredHashAlgorithm(signingKey)), + signingKey.getPGPPublicKey()); + sigGen.init(signatureType, signingKey.unlock(passphrase)); + + signatureGenerators.add(sigGen); + signingKeys.add(signingKey); + + return this; + } + + private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) + { + PreferredAlgorithms hashPreferences = key.getHashAlgorithmPreferences(); + if (hashPreferences == null || hashPreferences.getPreferences().length == 0) + { + return HashAlgorithmTags.SHA512; + } + return hashPreferences.getPreferences()[0]; + } + + public List sign(InputStream inputStream) + throws IOException, PGPException + { + byte[] buf = new byte[2048]; + int r; + while ((r = inputStream.read(buf)) != -1) + { + for (PGPSignatureGenerator sigGen : signatureGenerators) + { + sigGen.update(buf, 0, r); + } + } + + List documentSignatures = new ArrayList<>(); + for (int i = 0; i < signatureGenerators.size(); i++) + { + PGPSignatureGenerator sigGen = signatureGenerators.get(i); + PGPSignature signature = sigGen.generate(); + OpenPGPSignature.OpenPGPDocumentSignature docSig = new OpenPGPSignature.OpenPGPDocumentSignature( + signature, signingKeys.get(i)); + documentSignatures.add(docSig); + } + + return documentSignatures; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java new file mode 100644 index 0000000000..ce731a763f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java @@ -0,0 +1,139 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.KeyIdentifier; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class OpenPGPDetachedSignatureProcessor +{ + + private final OpenPGPImplementation implementation; + private final OpenPGPKeyMaterialPool.OpenPGPCertificatePool certificatePool = new OpenPGPKeyMaterialPool.OpenPGPCertificatePool(); + private final List pgpSignatures = new ArrayList<>(); + + private OpenPGPMessageProcessor.PGPExceptionCallback exceptionCallback = null; + + public OpenPGPDetachedSignatureProcessor() + { + this(OpenPGPImplementation.getInstance()); + } + + public OpenPGPDetachedSignatureProcessor(OpenPGPImplementation implementation) + { + this.implementation = implementation; + } + + public OpenPGPDetachedSignatureProcessor addSignatures(InputStream inputStream) + throws IOException + { + InputStream decoderStream = PGPUtil.getDecoderStream(inputStream); + PGPObjectFactory objFac = implementation.pgpObjectFactory(decoderStream); + Object next; + while ((next = objFac.nextObject()) != null) + { + if (next instanceof PGPSignatureList) + { + PGPSignatureList signatureList = (PGPSignatureList) next; + for (PGPSignature signature : signatureList) + { + pgpSignatures.add(signature); + } + } + } + return this; + } + + public OpenPGPDetachedSignatureProcessor addVerificationCertificate(OpenPGPCertificate certificate) + { + this.certificatePool.addItem(certificate); + return this; + } + + public List verify(InputStream inputStream) + throws IOException + { + List documentSignatures = new ArrayList<>(); + for (PGPSignature signature : pgpSignatures) + { + // Match up signatures with certificates + + KeyIdentifier identifier = OpenPGPSignature.getMostExpressiveIdentifier(signature.getKeyIdentifiers()); + if (identifier == null) + { + continue; + } + + OpenPGPCertificate certificate = certificatePool.provide(identifier); + if (certificate == null) + { + continue; + } + + OpenPGPCertificate.OpenPGPComponentKey signingKey = certificate.getKey(identifier); + if (signingKey == null) + { + continue; + } + + // Initialize signatures with verification key + try + { + signature.init(implementation.pgpContentVerifierBuilderProvider(), signingKey.getPGPPublicKey()); + } + catch (PGPException e) + { + if (exceptionCallback != null) + { + exceptionCallback.onException(e); + } + } + + OpenPGPSignature.OpenPGPDocumentSignature sig = + new OpenPGPSignature.OpenPGPDocumentSignature(signature, signingKey); + documentSignatures.add(sig); + } + + // Process plaintext + byte[] buf = new byte[2048]; + int r; + while ((r = inputStream.read(buf)) != -1) + { + for (OpenPGPSignature.OpenPGPDocumentSignature sig : documentSignatures) + { + sig.getSignature().update(buf, 0, r); + } + } + + // Verify signatures + for (OpenPGPSignature.OpenPGPDocumentSignature sig : documentSignatures) + { + try + { + sig.verify(); + } + catch (PGPException e) + { + if (exceptionCallback != null) + { + exceptionCallback.onException(e); + } + } + } + + return documentSignatures; + } + + public OpenPGPDetachedSignatureProcessor setExceptionCallback(OpenPGPMessageProcessor.PGPExceptionCallback callback) + { + this.exceptionCallback = callback; + return this; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 205afcd974..bb26c9891d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -504,7 +504,9 @@ public static class Configuration { List nextPreferences = Arrays.asList(next.getAlgorithms()); return new PreferredAEADCiphersuites(false, Arrays.stream(current.getAlgorithms()) - .filter(nextPreferences::contains).toArray(PreferredAEADCiphersuites.Combination[]::new)); + .filter(nextPreferences::contains) + .filter(it -> it.getSymmetricAlgorithm() != SymmetricKeyAlgorithmTags.NULL) + .toArray(PreferredAEADCiphersuites.Combination[]::new)); }) // If no common combination was found, fall back to implicitly supported algorithms .orElse(PreferredAEADCiphersuites.builder(false) @@ -535,6 +537,7 @@ public static class Configuration new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, Arrays.stream(current.getPreferences()) .filter(alg -> Arrays.stream(next.getPreferences()).anyMatch(it -> alg == it)) + .filter(it -> it != SymmetricKeyAlgorithmTags.NULL) .toArray())) // If no common combination was found, fall back to implicitly supported algorithms .orElse(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java new file mode 100644 index 0000000000..773b66a5c2 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java @@ -0,0 +1,12 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPException; + +public class InvalidSigningKeyException + extends PGPException +{ + public InvalidSigningKeyException(String message) + { + super(message); + } +} From a9ec564ee41d264c66bb66dc19220a1b7af3efcd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 12:56:14 +0100 Subject: [PATCH 035/154] WIP: Try to make stream verifying --- .../openpgp/PGPEncryptedData.java | 5 ++ .../openpgp/VerifyingInputStream.java | 48 +++++++++++++++++++ .../openpgp/api/OpenPGPCertificate.java | 11 ++++- .../OpenPGPDetachedSignatureProcessor.java | 9 +++- .../api/OpenPGPMessageInputStream.java | 10 ++-- .../openpgp/api/OpenPGPMessageProcessor.java | 31 ++++++------ .../api/test/OpenPGPMessageProcessorTest.java | 25 ++++++++++ 7 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java index fba7395286..c5083b851f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java @@ -175,6 +175,11 @@ public boolean isIntegrityProtected() return (encData instanceof SymmetricEncIntegrityPacket); } + public InputStreamPacket getEncData() + { + return encData; + } + /** * Checks whether the packet is protected using an AEAD algorithm. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java new file mode 100644 index 0000000000..6f2b4630fb --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java @@ -0,0 +1,48 @@ +package org.bouncycastle.openpgp; + +import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream} that performs verification of integrity protection upon {@link #close()}. + */ +public class VerifyingInputStream + extends FilterInputStream +{ + + private final PGPEncryptedData esk; + + public VerifyingInputStream(InputStream in, PGPEncryptedData dataPacket) + { + super(in); + this.esk = dataPacket; + } + + @Override + public void close() + throws IOException + { + super.close(); + if (esk.getEncData() instanceof SymmetricEncIntegrityPacket) + { + SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) esk.getEncData(); + if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1) + { + try + { + if (!esk.verify()) + { + throw new PGPException("Malformed integrity protected data."); + } + } + catch (PGPException e) + { + throw new IOException(e); + } + } + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index f32df16fea..34b1b62550 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -781,7 +781,16 @@ public boolean isBoundAt(Date evaluationTime) */ public OpenPGPSignatureChains getSignatureChains() { - return getCertificate().getAllSignatureChainsFor(this); + OpenPGPSignatureChains chains = getCertificate().getAllSignatureChainsFor(this); + if (getPublicComponent() instanceof OpenPGPPrimaryKey) + { + OpenPGPPrimaryKey pk = (OpenPGPPrimaryKey) getPublicComponent(); + if (!pk.getUserIDs().isEmpty()) + { + chains.addAll(getCertificate().getAllSignatureChainsFor(pk.getUserIDs().get(0))); + } + } + return chains; } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java index ce731a763f..0ee0843090 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.api; +import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; @@ -35,7 +36,8 @@ public OpenPGPDetachedSignatureProcessor addSignatures(InputStream inputStream) throws IOException { InputStream decoderStream = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory objFac = implementation.pgpObjectFactory(decoderStream); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objFac = implementation.pgpObjectFactory(pIn); Object next; while ((next = objFac.nextObject()) != null) { @@ -47,6 +49,11 @@ public OpenPGPDetachedSignatureProcessor addSignatures(InputStream inputStream) pgpSignatures.add(signature); } } + else if (next instanceof PGPSignature) + { + PGPSignature signature = (PGPSignature) next; + pgpSignatures.add(signature); + } } return this; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 3c8d38a86d..4912e44811 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -16,6 +16,7 @@ import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.VerifyingInputStream; import java.io.IOException; import java.io.InputStream; @@ -134,6 +135,7 @@ else if (next instanceof PGPEncryptedDataList) PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next; OpenPGPMessageProcessor.Decrypted decrypted = processor.decrypt(encryptedDataList); InputStream decryptedIn = decrypted.inputStream; + VerifyingInputStream verifyIn = new VerifyingInputStream(decryptedIn, decrypted.esk); resultBuilder.encrypted(decrypted); InputStream decodeIn = BCPGInputStream.wrap(decryptedIn); PGPObjectFactory decFac = implementation.pgpObjectFactory(decodeIn); @@ -533,9 +535,9 @@ static class EncryptedData this.decryptionKey = decrypted.decryptionKey; this.decryptionPassphrase = decrypted.decryptionPassphrase; this.sessionKey = decrypted.sessionKey; - if (decrypted.dataPacket instanceof SymmetricEncIntegrityPacket) + if (decrypted.esk.getEncData() instanceof SymmetricEncIntegrityPacket) { - SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) decrypted.dataPacket; + SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) decrypted.esk.getEncData(); if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_2) { encryption = MessageEncryptionMechanism.aead( @@ -546,13 +548,13 @@ static class EncryptedData encryption = MessageEncryptionMechanism.integrityProtected(sessionKey.getAlgorithm()); } } - else if (decrypted.dataPacket instanceof AEADEncDataPacket) + else if (decrypted.esk.getEncData() instanceof AEADEncDataPacket) { encryption = MessageEncryptionMechanism.librePgp(sessionKey.getAlgorithm()); } else { - throw new RuntimeException("Unexpected encrypted data packet type: " + decrypted.dataPacket.getClass().getName()); + throw new RuntimeException("Unexpected encrypted data packet type: " + decrypted.esk.getClass().getName()); } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index f62899db8c..50959ef719 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -1,6 +1,5 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.InputStreamPacket; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; @@ -10,6 +9,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; @@ -225,15 +225,15 @@ static class Decrypted { final InputStream inputStream; final PGPSessionKey sessionKey; - final InputStreamPacket dataPacket; + final PGPEncryptedData esk; OpenPGPCertificate.OpenPGPComponentKey decryptionKey; char[] decryptionPassphrase; - public Decrypted(InputStreamPacket encryptedData, + public Decrypted(PGPEncryptedData encryptedData, PGPSessionKey decryptedSessionKey, InputStream decryptedIn) { - this.dataPacket = encryptedData; + this.esk = encryptedData; this.sessionKey = decryptedSessionKey; this.inputStream = decryptedIn; } @@ -255,10 +255,10 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) // decrypt with provided session key SessionKeyDataDecryptorFactory decryptorFactory = implementation.sessionKeyDataDecryptorFactory(configuration.sessionKey); - InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() - .getDataStream(decryptorFactory); + PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); + InputStream decryptedIn = encData.getDataStream(decryptorFactory); - return new Decrypted(encDataList.getEncryptedData(), configuration.sessionKey, decryptedIn); + return new Decrypted(encData, configuration.sessionKey, decryptedIn); } List skesks = skesks(encDataList); @@ -283,10 +283,10 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) // Decrypt the message with the decrypted session key SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); - InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() - .getDataStream(skDecryptorFactory); + PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); + InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); decrypted.decryptionPassphrase = passphrase; return decrypted; @@ -335,9 +335,9 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) // Decrypt the message using the decrypted session key SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); - InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData() - .getDataStream(skDecryptorFactory); - Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); + InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); decrypted.decryptionKey = decryptionKey; return decrypted; } @@ -363,8 +363,9 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) // Decrypt the data using the decrypted session key SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); - InputStream decryptedIn = encDataList.extractSessionKeyEncryptedData().getDataStream(skDecryptorFactory); - Decrypted decrypted = new Decrypted(encDataList.getEncryptedData(), decryptedSessionKey, decryptedIn); + PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); + InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); decrypted.decryptionPassphrase = passphrase; return decrypted; } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 57234132d8..1fdeb17711 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -43,6 +43,8 @@ public String getName() public void performTest() throws Exception { + testVerificationOfSEIPD1MessageWithTamperedCiphertext(); + roundtripUnarmoredPlaintextMessage(); roundtripArmoredPlaintextMessage(); roundTripCompressedMessage(); @@ -657,6 +659,29 @@ private void incompleteMessageProcessing() isFalse(sig.isValid()); } + private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() throws IOException, PGPException { + String MSG = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQwAsQZU7mGUrxAmETLOel7uIyFM4LARZh9GcR/9V6gzEE+x\n" + + "4nVIHd16L77TN8w0WobUqdxHTAbh6iSGY3nkd+s8lo8f+bxzptpSSUCE3YFeQcnb\n" + + "fNhB2y4xeCLDBL7eEzNZXi8ovz6Hyx/VnZ0/GPmKk+1ilze8Q6S5E6tCYFSEXd9D\n" + + "IKNyYV8OtjFV6qBJHBY0TgWALKK7Xan0PoB0ZM5Azb3nE6TGvkicLd3gKcKuo+jq\n" + + "xx8Rrq3D8DvaR8ieQQQzcRB1WxDDzUS1LBeqShdCzY5F4fUcnXyRb8e2dPc/1uYy\n" + + "EYCODPBOEvseZIrToScb3VHWArQRRZXVsYE5x85rWusEy9YfXyUXvVCZtCSgQVmp\n" + + "MDEM3QPWsUgF9ijDKgBAIwvjTxSF+sdlYWof2lSGq4FcdML7hmqp74JoJKLqbNxE\n" + + "O2eqbw8CxNyMjEK7DXzeGt5cVbYvIWjPvKY83OmMZdyP3DFzbquqiWOAb0x/T+mV\n" + + "irzkUPg2nmKHdAyMqpKY0kwBxWqYCIWLUYLAjQt63FVg7zxjMMY8vhlZd7+o4dsb\n" + + "OXlBenyffXJbGIih8SWviOIO7yDz0VuJP5dYLC2FeGlrYilt7wfUcNjhMH6w\n" + + "=T9pP\n" + + "-----END PGP MESSAGE-----\n"; + OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + processor.addDecryptionKey(key); + OpenPGPMessageInputStream oIn = processor.process(new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8))); + Streams.drain(oIn); + oIn.close(); + } + public static void main(String[] args) { runTest(new OpenPGPMessageProcessorTest()); From 5341eb75d5ab141fa7f61601b439027f12cea62c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 20:48:09 +0100 Subject: [PATCH 036/154] Throw exception on unknown critical packets --- .../org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java | 3 ++- .../org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java index 10b99c822e..1aeed80b40 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java @@ -45,7 +45,8 @@ public class BcOpenPGPImplementation @Override public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream) { - return new BcPGPObjectFactory(packetInputStream); + return new BcPGPObjectFactory(packetInputStream) + .setThrowForUnknownCriticalPackets(true); } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java index 273c06dd93..3546b8bc9b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java @@ -63,7 +63,8 @@ public JcaOpenPGPImplementation(Provider provider, SecureRandom secureRandom) @Override public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream) { - return new JcaPGPObjectFactory(packetInputStream); + return new JcaPGPObjectFactory(packetInputStream) + .setThrowForUnknownCriticalPackets(true); } @Override From d9bcc6ca78a24968f5eb864c248452ee13dc9ea8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 20:48:33 +0100 Subject: [PATCH 037/154] Verify integrity protected encrypted data --- .../openpgp/VerifyingInputStream.java | 30 +++++++++++++++++++ .../api/OpenPGPMessageInputStream.java | 1 - .../openpgp/api/OpenPGPMessageProcessor.java | 13 +++++--- .../api/test/OpenPGPMessageProcessorTest.java | 28 +++++++++-------- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java index 6f2b4630fb..189466d78d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java @@ -21,6 +21,36 @@ public VerifyingInputStream(InputStream in, PGPEncryptedData dataPacket) this.esk = dataPacket; } + @Override + public int read() throws IOException { + int i = in.read(); + if (i == -1) + { + close(); + } + return i; + } + + @Override + public int read(byte[] b) throws IOException { + int r = in.read(b); + if (r == -1) + { + close(); + } + return r; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int r = in.read(b, off, len); + if (r == -1) + { + close(); + } + return r; + } + @Override public void close() throws IOException diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 4912e44811..eb9fe593ff 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -135,7 +135,6 @@ else if (next instanceof PGPEncryptedDataList) PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next; OpenPGPMessageProcessor.Decrypted decrypted = processor.decrypt(encryptedDataList); InputStream decryptedIn = decrypted.inputStream; - VerifyingInputStream verifyIn = new VerifyingInputStream(decryptedIn, decrypted.esk); resultBuilder.encrypted(decrypted); InputStream decodeIn = BCPGInputStream.wrap(decryptedIn); PGPObjectFactory decFac = implementation.pgpObjectFactory(decodeIn); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index 50959ef719..6cbf28dfa1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -11,6 +11,7 @@ import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.VerifyingInputStream; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; @@ -257,8 +258,9 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(configuration.sessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(decryptorFactory); + VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); - return new Decrypted(encData, configuration.sessionKey, decryptedIn); + return new Decrypted(encData, configuration.sessionKey, verifyingIn); } List skesks = skesks(encDataList); @@ -285,8 +287,9 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); + VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); - Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionPassphrase = passphrase; return decrypted; @@ -337,7 +340,8 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); + VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionKey = decryptionKey; return decrypted; } @@ -365,7 +369,8 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, decryptedIn); + VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionPassphrase = passphrase; return decrypted; } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 1fdeb17711..589de45765 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -659,21 +659,23 @@ private void incompleteMessageProcessing() isFalse(sig.isValid()); } - private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() throws IOException, PGPException { + private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() + throws IOException, PGPException + { String MSG = "-----BEGIN PGP MESSAGE-----\n" + "\n" + - "wcDMA3wvqk35PDeyAQwAsQZU7mGUrxAmETLOel7uIyFM4LARZh9GcR/9V6gzEE+x\n" + - "4nVIHd16L77TN8w0WobUqdxHTAbh6iSGY3nkd+s8lo8f+bxzptpSSUCE3YFeQcnb\n" + - "fNhB2y4xeCLDBL7eEzNZXi8ovz6Hyx/VnZ0/GPmKk+1ilze8Q6S5E6tCYFSEXd9D\n" + - "IKNyYV8OtjFV6qBJHBY0TgWALKK7Xan0PoB0ZM5Azb3nE6TGvkicLd3gKcKuo+jq\n" + - "xx8Rrq3D8DvaR8ieQQQzcRB1WxDDzUS1LBeqShdCzY5F4fUcnXyRb8e2dPc/1uYy\n" + - "EYCODPBOEvseZIrToScb3VHWArQRRZXVsYE5x85rWusEy9YfXyUXvVCZtCSgQVmp\n" + - "MDEM3QPWsUgF9ijDKgBAIwvjTxSF+sdlYWof2lSGq4FcdML7hmqp74JoJKLqbNxE\n" + - "O2eqbw8CxNyMjEK7DXzeGt5cVbYvIWjPvKY83OmMZdyP3DFzbquqiWOAb0x/T+mV\n" + - "irzkUPg2nmKHdAyMqpKY0kwBxWqYCIWLUYLAjQt63FVg7zxjMMY8vhlZd7+o4dsb\n" + - "OXlBenyffXJbGIih8SWviOIO7yDz0VuJP5dYLC2FeGlrYilt7wfUcNjhMH6w\n" + - "=T9pP\n" + - "-----END PGP MESSAGE-----\n"; + "wcDMA3wvqk35PDeyAQv/c0eFDZud8YCzKu0qzq7xOUeF0KiFFv58RSAookfyce9B\n" + + "LSXH7g/F/3Pdp9EHcrtBsxYRXUdWmZHvwFRvAiwCl9unjUgRendopmuNJ5zNgB2w\n" + + "DkuMA2J2J5HGTicvCwGrWALDG6Dc56UEFTwCsip8uKNG+Q3X5IwpU7Vztqywkt4/\n" + + "RNp8+neu+oJELWn3mC3oZrMzYIaD2SlyVaW5Vpksjz32VGKXCm4/hGC/03tGuE1i\n" + + "5sOZicHpeN24BD2tr3MMOdHKPXKxVPPx5T1MIJYUoYjMp7Tnml6F4Obhf+VllAli\n" + + "mkQHj6vevbEkLcJX67pvD04PJiQqm5ea1GwOZDW/nPLih80AJWHpXME36WBzk4X2\n" + + "bHaK3qQxyxqfpvMvWcargI3neWNLaSzqY/2eCrY/OEbAcj18W+9u7phkEoVRmrC7\n" + + "mqIeEUXtGjWSywtJXF8tIcxOU3+IqekXLW9yFIzRrHWEzRVKzP2P5q7mwOp2ddjg\n" + + "8vqe/DOz1r8VxN6orUue0kwBJVHfkYpW8cwX2AtIPYk90ct2qCTbCtNQul+txpRY\n" + + "IwBVELjaaSGpdOuIHkETYssCNfqPSv0rNmaTDq78xItvhjuc4lRaKkpF9DdE\n" + + "=I5BA\n" + + "-----END PGP MESSAGE-----"; OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); processor.addDecryptionKey(key); From ada3886efe586b94aadde856d2197fe12a51417b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Dec 2024 13:47:05 +0100 Subject: [PATCH 038/154] Fix NPE in OPS verification --- .../api/OpenPGPMessageInputStream.java | 6 + .../openpgp/api/test/HardRevocationTest.java | 116 ++++++++++++++++++ .../api/test/OpenPGPMessageProcessorTest.java | 9 +- 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index eb9fe593ff..e3d069d788 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -615,17 +615,23 @@ void update(int i) { for (PGPOnePassSignature onePassSignature : onePassSignatures) { + if (issuers.containsKey(onePassSignature)) + { onePassSignature.update((byte) i); } } + } void update(byte[] b, int off, int len) { for (PGPOnePassSignature onePassSignature : onePassSignatures) { + if (issuers.containsKey(onePassSignature)) + { onePassSignature.update(b, off, len); } } + } List verify( OpenPGPMessageProcessor processor) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java new file mode 100644 index 0000000000..1c810867c1 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java @@ -0,0 +1,116 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; + +public class HardRevocationTest extends AbstractPacketTest +{ + + @Override + public String getName() + { + return "HardRevocationTest"; + } + + @Override + public void performTest() throws Exception + { + String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "6sXYWB8=\n" + + "=13Sf\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(CERT); + String msg = "Hello, World"; + String SIG1 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsC7BAABCgBvBYJa564ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfM0EN4Ei0bQv6UO9BRq2wtUfV948cRynRMBb8TSGCG\n" + + "tBYhBOMsttqCApG3522xqAitUcrkcPAGAAAlNwf+L0KQK9i/xmYKOMV2EX13QUoZ\n" + + "vvb/pHGZaCQ9JtvEF2l2DT0DqByZ+tOv5Y4isU+un7CraoyvyajAwR0Yqk937B6C\n" + + "HQHKMkmIl+5R4/xqSoWYmOidbrgilojPMBEhB3INQ8/THjjFijtLzitVhnWBd7+u\n" + + "s0kcqnWnOdx2By4aDe+UEiyCfSE02e/0tIsM71RqiU91zH6dl6+q8nml7PsYuTFV\n" + + "V09oQTbBuuvUe+YgN/uvyKVIsA64lQ+YhqEeIA8Quek7fHhW+du9OIhSPsbYodyx\n" + + "VWMTXwSWKGNvZNAkpmgUYqFjS2Cx5ZUWblZLjrNKBwnnmt50qvUN7+o2pjlnfA==\n" + + "=UuXb\n" + + "-----END PGP SIGNATURE-----\n"; + + + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 589de45765..53b3f64b31 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -681,7 +681,14 @@ private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() processor.addDecryptionKey(key); OpenPGPMessageInputStream oIn = processor.process(new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8))); Streams.drain(oIn); - oIn.close(); + try + { + oIn.close(); + } + catch (IOException e) + { + // expected + } } public static void main(String[] args) From ad70e6856330def1709144bb4ceae45e9a186e30 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Dec 2024 12:53:44 +0100 Subject: [PATCH 039/154] Rename VerifyingInputStream to IntegrityProtectedInputStream --- ...tStream.java => IntegrityProtectedInputStream.java} | 4 ++-- .../openpgp/api/OpenPGPMessageInputStream.java | 9 ++++----- .../openpgp/api/OpenPGPMessageProcessor.java | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) rename pg/src/main/java/org/bouncycastle/openpgp/{VerifyingInputStream.java => IntegrityProtectedInputStream.java} (93%) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java similarity index 93% rename from pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java rename to pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java index 189466d78d..39d8c37c46 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/VerifyingInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java @@ -9,13 +9,13 @@ /** * {@link InputStream} that performs verification of integrity protection upon {@link #close()}. */ -public class VerifyingInputStream +public class IntegrityProtectedInputStream extends FilterInputStream { private final PGPEncryptedData esk; - public VerifyingInputStream(InputStream in, PGPEncryptedData dataPacket) + public IntegrityProtectedInputStream(InputStream in, PGPEncryptedData dataPacket) { super(in); this.esk = dataPacket; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index e3d069d788..626088867e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -16,7 +16,6 @@ import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.VerifyingInputStream; import java.io.IOException; import java.io.InputStream; @@ -617,10 +616,10 @@ void update(int i) { if (issuers.containsKey(onePassSignature)) { - onePassSignature.update((byte) i); + onePassSignature.update((byte) i); + } } } - } void update(byte[] b, int off, int len) { @@ -628,10 +627,10 @@ void update(byte[] b, int off, int len) { if (issuers.containsKey(onePassSignature)) { - onePassSignature.update(b, off, len); + onePassSignature.update(b, off, len); + } } } - } List verify( OpenPGPMessageProcessor processor) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index 6cbf28dfa1..c2b866a88b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -11,7 +11,7 @@ import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.VerifyingInputStream; +import org.bouncycastle.openpgp.IntegrityProtectedInputStream; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; @@ -258,7 +258,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(configuration.sessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(decryptorFactory); - VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + IntegrityProtectedInputStream verifyingIn = new IntegrityProtectedInputStream(decryptedIn, encData); return new Decrypted(encData, configuration.sessionKey, verifyingIn); } @@ -287,7 +287,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + IntegrityProtectedInputStream verifyingIn = new IntegrityProtectedInputStream(decryptedIn, encData); Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionPassphrase = passphrase; @@ -340,7 +340,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + IntegrityProtectedInputStream verifyingIn = new IntegrityProtectedInputStream(decryptedIn, encData); Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionKey = decryptionKey; return decrypted; @@ -369,7 +369,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) SessionKeyDataDecryptorFactory skDecryptorFactory = implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey); PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData(); InputStream decryptedIn = encData.getDataStream(skDecryptorFactory); - VerifyingInputStream verifyingIn = new VerifyingInputStream(decryptedIn, encData); + IntegrityProtectedInputStream verifyingIn = new IntegrityProtectedInputStream(decryptedIn, encData); Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn); decrypted.decryptionPassphrase = passphrase; return decrypted; From 9c2c47e837eaa407b6b54ec9c609e8ff70138dc0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Dec 2024 14:27:29 +0100 Subject: [PATCH 040/154] Add support for signature creation time intervals --- .../OpenPGPDetachedSignatureProcessor.java | 19 ++++++++++++++ .../api/OpenPGPMessageInputStream.java | 5 ++++ .../openpgp/api/OpenPGPMessageProcessor.java | 25 +++++++++++++++++++ .../openpgp/api/OpenPGPSignature.java | 8 ++++++ 4 files changed, 57 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java index 0ee0843090..b5ae37dbdd 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Date; import java.util.List; public class OpenPGPDetachedSignatureProcessor @@ -19,6 +20,8 @@ public class OpenPGPDetachedSignatureProcessor private final OpenPGPImplementation implementation; private final OpenPGPKeyMaterialPool.OpenPGPCertificatePool certificatePool = new OpenPGPKeyMaterialPool.OpenPGPCertificatePool(); private final List pgpSignatures = new ArrayList<>(); + private Date verifyNotAfter = new Date(); // now + private Date verifyNotBefore = new Date(0L); // beginning of time private OpenPGPMessageProcessor.PGPExceptionCallback exceptionCallback = null; @@ -64,6 +67,18 @@ public OpenPGPDetachedSignatureProcessor addVerificationCertificate(OpenPGPCerti return this; } + public OpenPGPDetachedSignatureProcessor verifyNotBefore(Date date) + { + this.verifyNotBefore = date; + return this; + } + + public OpenPGPDetachedSignatureProcessor verifyNotAfter(Date date) + { + this.verifyNotAfter = date; + return this; + } + public List verify(InputStream inputStream) throws IOException { @@ -105,6 +120,10 @@ public List verify(InputStream inputS OpenPGPSignature.OpenPGPDocumentSignature sig = new OpenPGPSignature.OpenPGPDocumentSignature(signature, signingKey); + if (!sig.createdInBounds(verifyNotBefore, verifyNotAfter)) + { + continue; + } documentSignatures.add(sig); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 626088867e..ef79204586 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -649,6 +649,11 @@ List verify( OpenPGPSignature.OpenPGPDocumentSignature dataSignature = new OpenPGPSignature.OpenPGPDocumentSignature(signature, key); + if (!dataSignature.createdInBounds(processor.getVerifyNotBefore(), processor.getVerifyNotAfter())) + { + // sig is not in bounds + continue; + } try { dataSignature.verify(ops); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index c2b866a88b..c6be1c4398 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Date; import java.util.List; public class OpenPGPMessageProcessor @@ -60,6 +61,18 @@ public OpenPGPMessageProcessor addVerificationCertificate(OpenPGPCertificate iss return this; } + public OpenPGPMessageProcessor verifyNotAfter(Date date) + { + configuration.verifyNotAfter = date; + return this; + } + + public OpenPGPMessageProcessor verifyNotBefore(Date date) + { + configuration.verifyNotBefore = date; + return this; + } + /** * Add an {@link OpenPGPKey} as potential decryption key. * If the message is encrypted for an {@link OpenPGPKey}, this key can be tried to decrypt the message. @@ -217,6 +230,16 @@ public OpenPGPMessageInputStream process(InputStream messageIn) return in; } + Date getVerifyNotBefore() + { + return configuration.verifyNotBefore; + } + + Date getVerifyNotAfter() + { + return configuration.verifyNotAfter; + } + /** * Bundle together metadata about the decryption result. * That includes the encrypted data packet itself, the passphrase or (sub-)key that was used to decrypt the @@ -465,6 +488,8 @@ public static class Configuration private MissingPassphraseCallback missingMessagePassphraseCallback; private PGPExceptionCallback exceptionCallback = null; private PGPSessionKey sessionKey; + private Date verifyNotAfter = new Date(); // now + private Date verifyNotBefore = new Date(0L); // beginning of time public Configuration() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 380e63a0d1..4884133d91 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -507,6 +507,7 @@ public boolean verify() * @return true if the signature is valid now. */ public boolean isValid() + throws MalformedPGPSignatureException { return isValidAt(getCreationTime()); } @@ -520,6 +521,7 @@ public boolean isValid() * @throws IllegalStateException if the signature has not yet been tested using a

verify()
method. */ public boolean isValidAt(Date date) + throws MalformedPGPSignatureException { if (!isTested) { @@ -529,9 +531,15 @@ public boolean isValidAt(Date date) { return false; } + sanitize(issuer); return issuer.getCertificate().getPrimaryKey().isBoundAt(date) && issuer.isBoundAt(date) && issuer.isSigningKey(date); } + + public boolean createdInBounds(Date notBefore, Date notAfter) + { + return !getCreationTime().before(notBefore) && !getCreationTime().after(notAfter); + } } } From 8b77a2d293ca38e450db3b2ed418a2a1a5244c78 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 28 Dec 2024 12:36:32 +0100 Subject: [PATCH 041/154] Fix NPE when negotiating SEIPD2 algorithm suite if key has no PreferredAEADCipherSuites subpacket --- core/src/main/java/org/bouncycastle/util/Objects.java | 11 +++++++++++ .../bcpg/sig/PreferredAEADCiphersuites.java | 5 +++++ .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 1 + .../openpgp/api/OpenPGPMessageGenerator.java | 4 +++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bouncycastle/util/Objects.java b/core/src/main/java/org/bouncycastle/util/Objects.java index 9ea2ff36cc..3b0c44fe80 100644 --- a/core/src/main/java/org/bouncycastle/util/Objects.java +++ b/core/src/main/java/org/bouncycastle/util/Objects.java @@ -1,5 +1,7 @@ package org.bouncycastle.util; +import java.util.function.Supplier; + public class Objects { public static boolean areEqual(Object a, Object b) @@ -11,4 +13,13 @@ public static int hashCode(Object obj) { return null == obj ? 0 : obj.hashCode(); } + + public static T or(T nullable, Supplier supplier) + { + if (nullable == null) + { + return supplier.get(); + } + return nullable; + } } diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAEADCiphersuites.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAEADCiphersuites.java index f58b25bb76..d99a01e9fa 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAEADCiphersuites.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAEADCiphersuites.java @@ -30,6 +30,11 @@ public class PreferredAEADCiphersuites */ private static final Combination AES_128_OCB = new Combination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB); + public static PreferredAEADCiphersuites DEFAULT() + { + return new PreferredAEADCiphersuites(false, new Combination[]{AES_128_OCB}); + } + /** * Create a new PreferredAEADAlgorithms signature subpacket from raw data. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 34b1b62550..207f474d0f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -33,6 +33,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index bb26c9891d..8cd865b531 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -498,7 +498,9 @@ public static class Configuration // go from List> to List .flatMap(it -> it) // Extract AEAD preferences per key - .map(OpenPGPCertificate.OpenPGPComponentKey::getAEADCipherSuitePreferences) + .map(it -> org.bouncycastle.util.Objects.or( + it.getAEADCipherSuitePreferences(), + PreferredAEADCiphersuites::DEFAULT)) // Take the intersection of combinations to find commonly preferred combination .reduce((current, next) -> { From b25617503f813ee4209d9ef3a55762a17ffa1a2a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 28 Dec 2024 12:38:56 +0100 Subject: [PATCH 042/154] Fix checkstyle --- .../openpgp/IntegrityProtectedInputStream.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java index 39d8c37c46..d553201d09 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/IntegrityProtectedInputStream.java @@ -22,7 +22,9 @@ public IntegrityProtectedInputStream(InputStream in, PGPEncryptedData dataPacket } @Override - public int read() throws IOException { + public int read() + throws IOException + { int i = in.read(); if (i == -1) { @@ -32,7 +34,9 @@ public int read() throws IOException { } @Override - public int read(byte[] b) throws IOException { + public int read(byte[] b) + throws IOException + { int r = in.read(b); if (r == -1) { @@ -42,7 +46,9 @@ public int read(byte[] b) throws IOException { } @Override - public int read(byte[] b, int off, int len) throws IOException { + public int read(byte[] b, int off, int len) + throws IOException + { int r = in.read(b, off, len); if (r == -1) { From 42c88e7e201d31b065db13761d6234060d775275 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 28 Dec 2024 17:10:22 +0100 Subject: [PATCH 043/154] WIP: Work on OpenPGPPolicy --- .../api/MessageEncryptionMechanism.java | 6 + .../api/OpenPGPEncryptionNegotiator.java | 309 ++++++++++++++++++ .../openpgp/api/OpenPGPImplementation.java | 12 + .../openpgp/api/OpenPGPMessageGenerator.java | 137 +++----- .../openpgp/api/OpenPGPNotationRegistry.java | 19 -- .../openpgp/api/OpenPGPPolicy.java | 181 ++++++++++ .../api/exception/PolicyException.java | 10 + 7 files changed, 555 insertions(+), 119 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/PolicyException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java b/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java index 0fb1d36673..9c3138baf2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/MessageEncryptionMechanism.java @@ -2,6 +2,7 @@ import org.bouncycastle.bcpg.AEADAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; /** * Encryption mode (SEIPDv1 / SEIPDv2 / OED) and algorithms. @@ -93,6 +94,11 @@ public static MessageEncryptionMechanism aead(int symmetricKeyAlgorithm, int aea return new MessageEncryptionMechanism(EncryptedDataPacketType.SEIPDv2, symmetricKeyAlgorithm, aeadAlgorithm); } + public static MessageEncryptionMechanism aead(PreferredAEADCiphersuites.Combination combination) + { + return aead(combination.getSymmetricAlgorithm(), combination.getAeadAlgorithm()); + } + /** * Return true, if the message will be encrypted. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java new file mode 100644 index 0000000000..614935cf07 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java @@ -0,0 +1,309 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface OpenPGPEncryptionNegotiator +{ + /** + * Negotiate encryption mode and algorithms. + * + * @param configuration message generator configuration + * @return negotiated encryption mode and algorithms + */ + MessageEncryptionMechanism negotiateEncryption(OpenPGPMessageGenerator.Configuration configuration); + + static PreferredAEADCiphersuites negotiateAEADCiphersuite(List certificates) + { + return new PreferredAEADCiphersuites(false, new PreferredAEADCiphersuites.Combination[]{ + bestAEADCiphersuiteByWeight(certificates) + }); + } + + /** + * Return true, if all recipient {@link OpenPGPCertificate certificates} contain at least one subkey that supports + * {@link Features#FEATURE_SEIPD_V2}. + * @param certificates certificates + * @return true if all certificates support the feature, false otherwise + */ + static boolean allRecipientsSupportSeipd2(List certificates) + { + return allRecipientsSupportEncryptionFeature(certificates, Features.FEATURE_SEIPD_V2); + } + + static boolean allRecipientsSupportLibrePGPOED(List certificates) + { + return allRecipientsSupportEncryptionFeature(certificates, Features.FEATURE_AEAD_ENCRYPTED_DATA); + } + + static boolean allRecipientsSupportEncryptionFeature(List certificates, byte feature) + { + for (OpenPGPCertificate recipient : certificates) + { + List encryptionKeys = recipient.getEncryptionKeys(); + if (encryptionKeys.isEmpty()) + { + continue; + } + + boolean recipientHasSupport = false; + for (OpenPGPCertificate.OpenPGPComponentKey subkey : encryptionKeys) + { + Features features = subkey.getFeatures(); + if (features != null && features.supportsFeature(feature)) + { + recipientHasSupport = true; + break; + } + } + + if (!recipientHasSupport) + { + return false; + } + } + return true; + } + + static PreferredAEADCiphersuites.Combination bestAEADCiphersuiteByWeight(Collection certificates) + { + // Keep track of combinations, assigning a weight + Map weights = new HashMap<>(); + + // Go through all certificate's capable subkeys + for (OpenPGPCertificate certificate : certificates) + { + List encryptionKeys = certificate.getEncryptionKeys(); + if (encryptionKeys.isEmpty()) + { + continue; + } + + // Only consider encryption keys capable of AEAD + Map capableKeys = new HashMap<>(); + for (OpenPGPCertificate.OpenPGPComponentKey subkey : encryptionKeys) + { + Features features = subkey.getFeatures(); + if (features == null || !features.supportsSEIPDv2()) + { + continue; + } + + PreferredAEADCiphersuites preferences = subkey.getAEADCipherSuitePreferences(); + if (preferences == null) + { + continue; + } + capableKeys.put(subkey, preferences); + } + + // Count the keys AEAD preferences and update the weight map + for (OpenPGPCertificate.OpenPGPComponentKey subkey : capableKeys.keySet()) + { + PreferredAEADCiphersuites preferences = capableKeys.get(subkey); + + // Weigh the preferences descending by index: w(p_i) = 1/(i+1) + // This way, combinations with a smaller index have a higher weight than combinations with larger index. + // Additionally, we divide this weight by the number of capable subkeys per cert in order to + // prevent a certificate with many capable subkeys from outvoting other certificates + List algorithms = + Arrays.stream(preferences.getAlgorithms()) + .filter(it -> it.getSymmetricAlgorithm() != SymmetricKeyAlgorithmTags.NULL) + .collect(Collectors.toList()); + for (int i = 0; i < algorithms.size(); i++) + { + PreferredAEADCiphersuites.Combination c = algorithms.get(i); + float currentWeight = weights.getOrDefault(c, 0f); + float addedWeight = (1f / (i + 1)) / capableKeys.size(); + weights.put(c, currentWeight + addedWeight); + } + } + } + + // Select the entry with the highest weight + Map.Entry maxEntry = null; + for (Map.Entry entry : weights.entrySet()) + { + if (maxEntry == null || entry.getValue() > maxEntry.getValue()) + { + maxEntry = entry; + } + } + + if (maxEntry != null) + { + return maxEntry.getKey(); + } + + // else, return default combination + return PreferredAEADCiphersuites.DEFAULT().getAlgorithms()[0]; + } + + static int bestSymmetricKeyAlgorithmByWeight(Collection certificates) + { + Map weights = new HashMap<>(); + + // Go through all certificate's capable subkeys + for (OpenPGPCertificate certificate : certificates) + { + List encryptionKeys = certificate.getEncryptionKeys(); + if (encryptionKeys.isEmpty()) + { + continue; + } + + // Only consider encryption keys capable of SEIPDv1 + Map capableKeys = new HashMap<>(); + for (OpenPGPCertificate.OpenPGPComponentKey subkey : encryptionKeys) + { + Features features = subkey.getFeatures(); + if (features == null || !features.supportsModificationDetection()) + { + continue; + } + + PreferredAlgorithms preferences = subkey.getSymmetricCipherPreferences(); + if (preferences == null) + { + continue; + } + capableKeys.put(subkey, preferences); + } + + // Count the keys AEAD preferences and update the weight map + for (OpenPGPCertificate.OpenPGPComponentKey subkey : capableKeys.keySet()) + { + PreferredAlgorithms preferences = capableKeys.get(subkey); + + // Weigh the preferences descending by index: w(p_i) = 1/(i+1) + // This way, combinations with a smaller index have a higher weight than combinations with larger index. + // Additionally, we divide this weight by the number of capable subkeys per cert in order to + // prevent a certificate with many capable subkeys from outvoting other certificates + int[] algorithms = Arrays.stream(preferences.getPreferences()) + .filter(it -> it != SymmetricKeyAlgorithmTags.NULL) + .toArray(); + + for (int i = 0; i < algorithms.length; i++) + { + int a = algorithms[i]; + float currentWeight = weights.getOrDefault(a, 0f); + float addedWeight = (1f / (i + 1)) / capableKeys.size(); + weights.put(a, currentWeight + addedWeight); + } + } + } + + // Select the entry with the highest weight + Map.Entry maxEntry = null; + for (Map.Entry entry : weights.entrySet()) + { + if (maxEntry == null || entry.getValue() > maxEntry.getValue()) + { + maxEntry = entry; + } + } + + if (maxEntry != null) + { + return maxEntry.getKey(); + } + + // else, return default combination + return SymmetricKeyAlgorithmTags.AES_128; + } + + static int bestOEDEncryptionModeByWeight(Collection certificates) + { + Map weights = new HashMap<>(); + + // Go through all certificate's capable subkeys + for (OpenPGPCertificate certificate : certificates) + { + List encryptionKeys = certificate.getEncryptionKeys(); + if (encryptionKeys.isEmpty()) + { + continue; + } + + // Only consider encryption keys capable of OED + Map capableKeys = new HashMap<>(); + for (OpenPGPCertificate.OpenPGPComponentKey subkey : encryptionKeys) + { + Features features = subkey.getFeatures(); + if (features == null || !features.supportsFeature(Features.FEATURE_AEAD_ENCRYPTED_DATA)) + { + continue; + } + + PreferredAlgorithms preferences = subkey.getSymmetricCipherPreferences(); + if (preferences == null) + { + continue; + } + capableKeys.put(subkey, preferences); + } + + // Count the keys symmetric key preferences (that can be used with OED) and update the weight map + for (OpenPGPCertificate.OpenPGPComponentKey subkey : capableKeys.keySet()) + { + PreferredAlgorithms preferences = capableKeys.get(subkey); + int[] algorithms = Arrays.stream(preferences.getPreferences()) + .filter(alg -> + { + switch (alg) { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + // case SymmetricKeyAlgorithmTags.TWOFISH: + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + return true; + default: + return false; + } + }) + .toArray(); + + // Weigh the preferences descending by index: w(p_i) = 1/(i+1) + // This way, combinations with a smaller index have a higher weight than combinations with larger index. + // Additionally, we divide this weight by the number of capable subkeys per cert in order to + // prevent a certificate with many capable subkeys from outvoting other certificates + for (int i = 0; i < algorithms.length; i++) + { + int a = algorithms[i]; + float currentWeight = weights.getOrDefault(a, 0f); + float addedWeight = (1f / (i + 1)) / capableKeys.size(); + weights.put(a, currentWeight + addedWeight); + } + } + } + + // Select the entry with the highest weight + Map.Entry maxEntry = null; + for (Map.Entry entry : weights.entrySet()) + { + if (maxEntry == null || entry.getValue() > maxEntry.getValue()) + { + maxEntry = entry; + } + } + + if (maxEntry != null) + { + return maxEntry.getKey(); + } + + // else, return default combination + return SymmetricKeyAlgorithmTags.AES_128; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java index b91f9fbbda..978b499a87 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -37,6 +37,7 @@ public abstract class OpenPGPImplementation { private static OpenPGPImplementation INSTANCE; + private OpenPGPPolicy policy = OpenPGPPolicy.rfc9580(); /** * Replace the {@link OpenPGPImplementation} instance that is returned by {@link #getInstance()}. @@ -62,6 +63,17 @@ public static OpenPGPImplementation getInstance() return INSTANCE; } + public OpenPGPPolicy policy() + { + return policy; + } + + public OpenPGPImplementation setPolicy(OpenPGPPolicy policy) + { + this.policy = policy; + return this; + } + /** * Return an instance of {@link PGPObjectFactory} based on the given {@link InputStream}. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 8cd865b531..7e5b003fb2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -6,7 +6,6 @@ import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; @@ -33,7 +32,6 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -46,7 +44,7 @@ public class OpenPGPMessageGenerator public static final int BUFFER_SIZE = 1024; private final OpenPGPImplementation implementation; - private final Configuration config = new Configuration(); + private final Configuration config; // Literal Data metadata private Date fileModificationDate = null; @@ -60,8 +58,14 @@ public OpenPGPMessageGenerator() } public OpenPGPMessageGenerator(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + public OpenPGPMessageGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = Objects.requireNonNull(implementation); + this.config = new Configuration(policy); } /** @@ -425,17 +429,6 @@ public interface CompressionNegotiator int negotiateCompression(Configuration configuration); } - public interface EncryptionNegotiator - { - /** - * Negotiate encryption mode and algorithms. - * - * @param configuration message generator configuration - * @return negotiated encryption mode and algorithms - */ - MessageEncryptionMechanism negotiateEncryption(Configuration configuration); - } - public interface HashAlgorithmNegotiator { int negotiateHashAlgorithm(OpenPGPKey key, OpenPGPCertificate.OpenPGPComponentKey subkey); @@ -448,6 +441,12 @@ public static class Configuration private final List recipients = new ArrayList<>(); private final List signingKeys = new ArrayList<>(); private final List passphrases = new ArrayList<>(); + private final OpenPGPPolicy policy; + + public Configuration(OpenPGPPolicy policy) + { + this.policy = policy; + } // Factory for creating ASCII armor private ArmoredOutputStreamFactory armorStreamFactory = @@ -461,101 +460,39 @@ public static class Configuration private SubkeySelector signingKeySelector = OpenPGPCertificate::getSigningKeys; // Encryption method negotiator for when only password-based encryption is requested - private EncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> + private OpenPGPEncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB); // Encryption method negotiator for when public-key encryption is requested - private EncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> + private OpenPGPEncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> { + List certificates = recipients.stream() + .map(it -> it.certificate) + .collect(Collectors.toList()); + // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. - boolean seipd2Supported = configuration.recipients - .stream() - // ignore keys that can't encrypt at all - .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) - // Make sure all recipients have at least one key that can do SEIPD2 - .allMatch(recipient -> recipient.certificate.getEncryptionKeys() - .stream() - // if some recipient only has keys which DO NOT support SEIPD2 -> downgrade to SEIPD1 - .anyMatch(subkey -> - { - Features features = subkey.getFeatures(); - return features != null && features.supportsFeature(Features.FEATURE_SEIPD_V2); - }) - ); - - if (seipd2Supported) + if (policy.isProduceFeature(Features.FEATURE_SEIPD_V2) && + OpenPGPEncryptionNegotiator.allRecipientsSupportSeipd2(certificates)) + { + PreferredAEADCiphersuites commonDenominator = + OpenPGPEncryptionNegotiator.negotiateAEADCiphersuite(certificates); + return MessageEncryptionMechanism.aead(commonDenominator.getAlgorithms()[0]); + } + else if (policy.isProduceFeature(Features.FEATURE_AEAD_ENCRYPTED_DATA) && + OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificates)) { - PreferredAEADCiphersuites commonDenominator = configuration.recipients - .stream() - // Ignore certificates that cannot encrypt - .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) - // Ignore subkeys on recipients certificates that do not support SEIPDv2 - .map(recipient -> - { - List encKeys = recipient.encryptionSubkeys(); - return encKeys.stream().filter(it -> it.getFeatures().supportsSEIPDv2()); - }) - // go from List> to List - .flatMap(it -> it) - // Extract AEAD preferences per key - .map(it -> org.bouncycastle.util.Objects.or( - it.getAEADCipherSuitePreferences(), - PreferredAEADCiphersuites::DEFAULT)) - // Take the intersection of combinations to find commonly preferred combination - .reduce((current, next) -> - { - List nextPreferences = Arrays.asList(next.getAlgorithms()); - return new PreferredAEADCiphersuites(false, Arrays.stream(current.getAlgorithms()) - .filter(nextPreferences::contains) - .filter(it -> it.getSymmetricAlgorithm() != SymmetricKeyAlgorithmTags.NULL) - .toArray(PreferredAEADCiphersuites.Combination[]::new)); - }) - // If no common combination was found, fall back to implicitly supported algorithms - .orElse(PreferredAEADCiphersuites.builder(false) - // Default combination - .addCombination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB) - .build() - ); - PreferredAEADCiphersuites.Combination[] combinations = commonDenominator.getAlgorithms(); - // Select best combo from common combinations - // TODO: Make sure this is actually the best - PreferredAEADCiphersuites.Combination best = combinations[0]; - return MessageEncryptionMechanism.aead(best.getSymmetricAlgorithm(), best.getAeadAlgorithm()); + return MessageEncryptionMechanism.librePgp( + OpenPGPEncryptionNegotiator.bestOEDEncryptionModeByWeight(certificates)); } else { - PreferredAlgorithms commonDenominator = configuration.recipients - .stream() - // Ignore certificates that cannot encrypt - .filter(recipient -> !recipient.certificate.getEncryptionKeys().isEmpty()) - .map(Recipient::encryptionSubkeys) - .map(List::stream) - // go from List> to List - .flatMap(it -> it) - // Extract sym. cipher preferences per key - .map(OpenPGPCertificate.OpenPGPComponentKey::getSymmetricCipherPreferences) - // Take the intersection of combinations to find commonly preferred combination - .reduce((current, next) -> - new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, - Arrays.stream(current.getPreferences()) - .filter(alg -> Arrays.stream(next.getPreferences()).anyMatch(it -> alg == it)) - .filter(it -> it != SymmetricKeyAlgorithmTags.NULL) - .toArray())) - // If no common combination was found, fall back to implicitly supported algorithms - .orElse(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, - new int[] { - SymmetricKeyAlgorithmTags.AES_128 - } // AES128 is "MUST implement" - )); - // TODO: Algorithm selection - int bestCipherPreference = commonDenominator.getPreferences()[0]; - - return MessageEncryptionMechanism.integrityProtected(bestCipherPreference); + return MessageEncryptionMechanism.integrityProtected( + OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight(certificates)); } }; // Primary encryption method negotiator - private final EncryptionNegotiator encryptionNegotiator = + private final OpenPGPEncryptionNegotiator encryptionNegotiator = configuration -> { // No encryption methods provided -> Unencrypted message @@ -594,26 +531,26 @@ else if (configuration.recipients.isEmpty()) }; /** - * Replace the default {@link EncryptionNegotiator} that gets to decide, which {@link MessageEncryptionMechanism} mode + * Replace the default {@link OpenPGPEncryptionNegotiator} that gets to decide, which {@link MessageEncryptionMechanism} mode * to use if only password-based encryption is used. * * @param pbeNegotiator custom PBE negotiator. * @return this */ - public Configuration setPasswordBasedEncryptionNegotiator(EncryptionNegotiator pbeNegotiator) + public Configuration setPasswordBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pbeNegotiator) { this.passwordBasedEncryptionNegotiator = Objects.requireNonNull(pbeNegotiator); return this; } /** - * Replace the default {@link EncryptionNegotiator} that decides, which {@link MessageEncryptionMechanism} + * Replace the default {@link OpenPGPEncryptionNegotiator} that decides, which {@link MessageEncryptionMechanism} * mode to use if public-key encryption is used. * * @param pkbeNegotiator custom encryption negotiator that gets to decide if PK-based encryption is used * @return this */ - public Configuration setPublicKeyBasedEncryptionNegotiator(EncryptionNegotiator pkbeNegotiator) + public Configuration setPublicKeyBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pkbeNegotiator) { this.publicKeyBasedEncryptionNegotiator = Objects.requireNonNull(pkbeNegotiator); return this; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java deleted file mode 100644 index fa9f0406af..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPNotationRegistry.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.bouncycastle.openpgp.api; - -import java.util.HashSet; -import java.util.Set; - -public class OpenPGPNotationRegistry -{ - private final Set knownNotations = new HashSet<>(); - - public boolean isNotationKnown(String notationName) - { - return knownNotations.contains(notationName); - } - - public void addKnownNotation(String notationName) - { - this.knownNotations.add(notationName); - } -} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java new file mode 100644 index 0000000000..2a7492a7ba --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java @@ -0,0 +1,181 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.SignatureSubpacket; +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +public interface OpenPGPPolicy +{ + default boolean isAcceptableSigningKey(PGPPublicKey key) + { + return isAcceptablePublicKey(key); + } + + default boolean isAcceptableVerificationKey(PGPPublicKey key) + { + return isAcceptablePublicKey(key); + } + + default boolean isAcceptableEncryptionKey(PGPPublicKey key) + { + return isAcceptablePublicKey(key); + } + + default boolean isAcceptableDecryptionKey(PGPPublicKey key) + { + return isAcceptablePublicKey(key); + } + + default boolean isAcceptablePublicKey(PGPPublicKey key) + { + switch (key.getVersion()) + { + case PublicKeyPacket.VERSION_4: + case PublicKeyPacket.LIBREPGP_5: + case PublicKeyPacket.VERSION_6: + switch (key.getAlgorithm()) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + return key.getBitStrength() >= 2048; + case PublicKeyAlgorithmTags.Ed25519: + case PublicKeyAlgorithmTags.Ed448: + case PublicKeyAlgorithmTags.X25519: + case PublicKeyAlgorithmTags.X448: + return true; + + default: + return false; + } + + default: + return false; + } + } + + default boolean isAcceptableSignature(PGPSignature signature) + { + return hasAcceptableSignatureHashAlgorithm(signature) && + hasNoCriticalUnknownNotations(signature) && + hasNoCriticalUnknownSubpackets(signature); + } + + default boolean hasAcceptableSignatureHashAlgorithm(PGPSignature signature) + { + switch (signature.getSignatureType()) + { + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + case PGPSignature.DIRECT_KEY: + case PGPSignature.SUBKEY_BINDING: + case PGPSignature.PRIMARYKEY_BINDING: + return hasAcceptableCertificationSignatureHashAlgorithm(signature); + + case PGPSignature.CERTIFICATION_REVOCATION: + case PGPSignature.KEY_REVOCATION: + case PGPSignature.SUBKEY_REVOCATION: + return hasAcceptableRevocationSignatureHashAlgorithm(signature); + + case PGPSignature.BINARY_DOCUMENT: + case PGPSignature.CANONICAL_TEXT_DOCUMENT: + default: + return hasAcceptableDocumentSignatureHashAlgorithm(signature); + } + } + + default boolean hasAcceptableDocumentSignatureHashAlgorithm(PGPSignature signature) + { + return isAcceptableDocumentSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); + } + + default boolean hasAcceptableRevocationSignatureHashAlgorithm(PGPSignature signature) + { + return isAcceptableRevocationSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); + } + + default boolean hasAcceptableCertificationSignatureHashAlgorithm(PGPSignature signature) + { + return isAcceptableCertificationSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); + } + + default boolean hasNoCriticalUnknownNotations(PGPSignature signature) + { + PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); + if (hashedSubpackets == null) + { + return true; + } + + OpenPGPNotationRegistry registry = getNotationRegistry(); + + NotationData[] notations = hashedSubpackets.getNotationDataOccurrences(); + for (NotationData notation : notations) + { + if (notation.isCritical() && !registry.isNotationKnown(notation.getNotationName())) + { + return false; + } + } + return true; + } + + default boolean hasNoCriticalUnknownSubpackets(PGPSignature signature) + { + PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); + if (hashedSubpackets == null) + { + return true; + } + + for (SignatureSubpacket subpacket : hashedSubpackets.toArray()) + { + if (subpacket.isCritical() && + // only consider subpackets which are not recognized by SignatureSubpacketInputStream + subpacket.getClass().equals(SignatureSubpacket.class)) + { + if (!isKnownSignatureSubpacket(subpacket.getType())) + { + return false; + } + } + } + return true; + } + + default boolean isKnownSignatureSubpacket(int signatureSubpacketTag) + { + return false; + } + + boolean isAcceptableDocumentSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + + boolean isAcceptableRevocationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + + boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + + OpenPGPNotationRegistry getNotationRegistry(); + + class OpenPGPNotationRegistry + { + private final Set knownNotations = new HashSet<>(); + + public boolean isNotationKnown(String notationName) + { + return knownNotations.contains(notationName); + } + + public void addKnownNotation(String notationName) + { + this.knownNotations.add(notationName); + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/PolicyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/PolicyException.java new file mode 100644 index 0000000000..5bdd3a225e --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/PolicyException.java @@ -0,0 +1,10 @@ +package org.bouncycastle.openpgp.api.exception; + +public class PolicyException + extends Exception +{ + public PolicyException(String reason) + { + super(reason); + } +} From f1a056c6b18b3dce6a182743863b88649ffb8591 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Dec 2024 14:13:37 +0100 Subject: [PATCH 044/154] More work on the policy class --- .../openpgp/api/OpenPGPCertificate.java | 24 ++- .../openpgp/api/OpenPGPDefaultPolicy.java | 199 ++++++++++++++++++ .../api/OpenPGPEncryptionNegotiator.java | 18 +- .../openpgp/api/OpenPGPImplementation.java | 2 +- .../openpgp/api/OpenPGPMessageGenerator.java | 79 ++++--- .../openpgp/api/OpenPGPMessageProcessor.java | 6 +- .../openpgp/api/OpenPGPPolicy.java | 7 +- .../openpgp/api/util/DebugPrinter.java | 3 +- 8 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 207f474d0f..b335300294 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -4,6 +4,8 @@ import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.HashUtils; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; @@ -593,7 +595,7 @@ private boolean isBoundBy(OpenPGPCertificateComponent component, } // Chain needs to be valid (signatures correct) - if (chain.isValid(implementation.pgpContentVerifierBuilderProvider())) + if (chain.isValid(implementation.pgpContentVerifierBuilderProvider(), implementation.policy())) { // Chain needs to not contain a revocation signature, otherwise the component is considered revoked return !chain.isRevocation(); @@ -884,7 +886,8 @@ public OpenPGPComponentKey getTargetKeyComponent() * @param contentVerifierBuilderProvider provider for verifiers * @throws PGPSignatureException if the signature cannot be verified successfully */ - public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, + OpenPGPPolicy policy) throws PGPSignatureException { if (issuer == null) @@ -895,6 +898,11 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi sanitize(issuer); + if (!policy.hasAcceptableSignatureHashAlgorithm(signature)) + { + throw new PGPSignatureException("Unacceptable hash algorithm: " + signature.getHashAlgorithm()); + } + // Direct-Key signature if (target == issuer) { @@ -1760,10 +1768,11 @@ public boolean isEffectiveAt(Date evaluationDate) public boolean isValid() throws PGPSignatureException { - return isValid(getRootKey().getCertificate().implementation.pgpContentVerifierBuilderProvider()); + return isValid(getRootKey().getCertificate().implementation.pgpContentVerifierBuilderProvider(), + getRootKey().getCertificate().implementation.policy()); } - public boolean isValid(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + public boolean isValid(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, OpenPGPPolicy policy) throws PGPSignatureException { boolean correct = true; @@ -1771,7 +1780,7 @@ public boolean isValid(PGPContentVerifierBuilderProvider contentVerifierBuilderP { if (!link.signature.isTested) { - link.verify(contentVerifierBuilderProvider); + link.verify(contentVerifierBuilderProvider, policy); } if (!link.signature.isCorrect) @@ -1845,10 +1854,11 @@ public Date until() return signature.getExpirationTime(); } - public boolean verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + public boolean verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, + OpenPGPPolicy policy) throws PGPSignatureException { - signature.verify(contentVerifierBuilderProvider); + signature.verify(contentVerifierBuilderProvider, policy); return true; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java new file mode 100644 index 0000000000..5471fcd21a --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java @@ -0,0 +1,199 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.api.util.UTCUtil; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class OpenPGPDefaultPolicy + implements OpenPGPPolicy +{ + private final Map hashAlgorithmCutoffDates = new HashMap<>(); + private final Map symmetricKeyAlgorithmCutoffDates = new HashMap<>(); + private final Map publicKeyMinimalBitStrengths = new HashMap<>(); + + public OpenPGPDefaultPolicy() + { + /* + * Hash Algorithms + */ + // SHA-3 + acceptHashAlgorithm(HashAlgorithmTags.SHA3_512); + acceptHashAlgorithm(HashAlgorithmTags.SHA3_256); + // SHA-2 + acceptHashAlgorithm(HashAlgorithmTags.SHA512); + acceptHashAlgorithm(HashAlgorithmTags.SHA384); + acceptHashAlgorithm(HashAlgorithmTags.SHA256); + acceptHashAlgorithm(HashAlgorithmTags.SHA224); + // SHA-1 + acceptHashAlgorithmUntil(HashAlgorithmTags.SHA1, UTCUtil.parse("2023-02-01 00:00:00 UTC")); + + acceptHashAlgorithmUntil(HashAlgorithmTags.RIPEMD160, UTCUtil.parse("2023-02-01 00:00:00 UTC")); + acceptHashAlgorithmUntil(HashAlgorithmTags.MD5, UTCUtil.parse("1997-02-01 00:00:00 UTC")); + + /* + * Symmetric Key Algorithms + */ + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_256); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_192); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_128); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.TWOFISH); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.CAMELLIA_256); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.CAMELLIA_192); + acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.CAMELLIA_128); + + /* + * Public Key Algorithms and key strengths + */ + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.RSA_GENERAL, 2000); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.RSA_ENCRYPT, 2000); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.RSA_SIGN, 2000); + + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, 2000); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ELGAMAL_GENERAL, 2000); + + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.DSA, 2000); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ECDSA, 250); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.EDDSA_LEGACY, 250); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.DIFFIE_HELLMAN, 2000); + acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ECDH, 250); + + acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.X25519); + acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.X448); + acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.Ed25519); + acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.Ed448); + } + + public OpenPGPDefaultPolicy rejectHashAlgorithm(int hashAlgorithmId) + { + hashAlgorithmCutoffDates.remove(hashAlgorithmId); + return this; + } + + public OpenPGPDefaultPolicy acceptHashAlgorithm(int hashAlgorithmId) + { + return acceptHashAlgorithmUntil(hashAlgorithmId, null); + } + + public OpenPGPDefaultPolicy acceptHashAlgorithmUntil(int hashAlgorithmId, Date until) + { + hashAlgorithmCutoffDates.put(hashAlgorithmId, until); + return this; + } + + public OpenPGPDefaultPolicy rejectSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId) + { + symmetricKeyAlgorithmCutoffDates.remove(symmetricKeyAlgorithmId); + return this; + } + + public OpenPGPDefaultPolicy acceptSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId) + { + return acceptSymmetricKeyAlgorithmUntil(symmetricKeyAlgorithmId, null); + } + + public OpenPGPDefaultPolicy acceptSymmetricKeyAlgorithmUntil(int symmetricKeyAlgorithmId, Date until) + { + symmetricKeyAlgorithmCutoffDates.put(symmetricKeyAlgorithmId, until); + return this; + } + + public OpenPGPDefaultPolicy rejectPublicKeyAlgorithm(int publicKeyAlgorithmId) + { + publicKeyMinimalBitStrengths.remove(publicKeyAlgorithmId); + return this; + } + + public OpenPGPDefaultPolicy acceptPublicKeyAlgorithm(int publicKeyAlgorithmId) + { + publicKeyMinimalBitStrengths.put(publicKeyAlgorithmId, null); + return this; + } + + public OpenPGPDefaultPolicy acceptPublicKeyAlgorithmWithMinimalStrength(int publicKeyAlgorithmId, int minBitStrength) + { + publicKeyMinimalBitStrengths.put(publicKeyAlgorithmId, minBitStrength); + return this; + } + + @Override + public boolean isAcceptableDocumentSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) + { + return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + } + + @Override + public boolean isAcceptableRevocationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) + { + return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + } + + @Override + public boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) + { + return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + } + + @Override + public boolean isAcceptableSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId) + { + return isAcceptable(symmetricKeyAlgorithmId, symmetricKeyAlgorithmCutoffDates); + } + + @Override + public boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitStrength) + { + return isAcceptable(publicKeyAlgorithmId, bitStrength, publicKeyMinimalBitStrengths); + } + + @Override + public OpenPGPNotationRegistry getNotationRegistry() + { + return null; + } + + private boolean isAcceptable(int algorithmId, Date usageDate, Map cutoffTable) + { + if (!cutoffTable.containsKey(algorithmId)) + { + // algorithm is not listed in the map at all + return false; + } + + Date cutoffDate = cutoffTable.get(algorithmId); + if (cutoffDate == null) + { + // no cutoff date given -> algorithm is acceptable indefinitely + return true; + } + + return usageDate.before(cutoffDate); + } + + private boolean isAcceptable(int algorithmId, Map cutoffTable) + { + return cutoffTable.containsKey(algorithmId); + } + + private boolean isAcceptable(int algorithmId, int bitStrength, Map minBitStrengths) + { + if (!minBitStrengths.containsKey(algorithmId)) + { + // algorithm is not listed in the map at all + return false; + } + + Integer minBitStrength = minBitStrengths.get(algorithmId); + if (minBitStrength == null) + { + // no minimal bit strength defined -> accept all strengths + return true; + } + + return bitStrength >= minBitStrength; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java index 614935cf07..0b9c780013 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java @@ -22,10 +22,10 @@ public interface OpenPGPEncryptionNegotiator */ MessageEncryptionMechanism negotiateEncryption(OpenPGPMessageGenerator.Configuration configuration); - static PreferredAEADCiphersuites negotiateAEADCiphersuite(List certificates) + static PreferredAEADCiphersuites negotiateAEADCiphersuite(List certificates, OpenPGPPolicy policy) { return new PreferredAEADCiphersuites(false, new PreferredAEADCiphersuites.Combination[]{ - bestAEADCiphersuiteByWeight(certificates) + bestAEADCiphersuiteByWeight(certificates, policy) }); } @@ -74,7 +74,9 @@ static boolean allRecipientsSupportEncryptionFeature(List ce return true; } - static PreferredAEADCiphersuites.Combination bestAEADCiphersuiteByWeight(Collection certificates) + static PreferredAEADCiphersuites.Combination bestAEADCiphersuiteByWeight( + Collection certificates, + OpenPGPPolicy policy) { // Keep track of combinations, assigning a weight Map weights = new HashMap<>(); @@ -118,6 +120,7 @@ static PreferredAEADCiphersuites.Combination bestAEADCiphersuiteByWeight(Collect List algorithms = Arrays.stream(preferences.getAlgorithms()) .filter(it -> it.getSymmetricAlgorithm() != SymmetricKeyAlgorithmTags.NULL) + .filter(it -> policy.isAcceptableSymmetricKeyAlgorithm(it.getSymmetricAlgorithm())) .collect(Collectors.toList()); for (int i = 0; i < algorithms.size(); i++) { @@ -148,7 +151,9 @@ static PreferredAEADCiphersuites.Combination bestAEADCiphersuiteByWeight(Collect return PreferredAEADCiphersuites.DEFAULT().getAlgorithms()[0]; } - static int bestSymmetricKeyAlgorithmByWeight(Collection certificates) + static int bestSymmetricKeyAlgorithmByWeight( + Collection certificates, + OpenPGPPolicy policy) { Map weights = new HashMap<>(); @@ -190,6 +195,7 @@ static int bestSymmetricKeyAlgorithmByWeight(Collection cert // prevent a certificate with many capable subkeys from outvoting other certificates int[] algorithms = Arrays.stream(preferences.getPreferences()) .filter(it -> it != SymmetricKeyAlgorithmTags.NULL) + .filter(it -> policy.isAcceptableSymmetricKeyAlgorithm(it)) .toArray(); for (int i = 0; i < algorithms.length; i++) @@ -221,7 +227,8 @@ static int bestSymmetricKeyAlgorithmByWeight(Collection cert return SymmetricKeyAlgorithmTags.AES_128; } - static int bestOEDEncryptionModeByWeight(Collection certificates) + static int bestOEDEncryptionModeByWeight(Collection certificates, + OpenPGPPolicy policy) { Map weights = new HashMap<>(); @@ -272,6 +279,7 @@ static int bestOEDEncryptionModeByWeight(Collection certific return false; } }) + .filter(it -> policy.isAcceptableSymmetricKeyAlgorithm(it)) .toArray(); // Weigh the preferences descending by index: w(p_i) = 1/(i+1) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java index 978b499a87..dfcab08de7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -37,7 +37,7 @@ public abstract class OpenPGPImplementation { private static OpenPGPImplementation INSTANCE; - private OpenPGPPolicy policy = OpenPGPPolicy.rfc9580(); + private OpenPGPPolicy policy = new OpenPGPDefaultPolicy(); /** * Replace the {@link OpenPGPImplementation} instance that is returned by {@link #getInstance()}. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 7e5b003fb2..c1dd825b7a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -7,7 +7,6 @@ import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.crypto.CryptoServicesRegistrar; @@ -32,6 +31,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -93,7 +93,7 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip */ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate, SubkeySelector subkeySelector) { - config.recipients.add(new Recipient(recipientCertificate, subkeySelector)); + config.recipients.add(new Recipient(recipientCertificate, implementation.policy(), subkeySelector)); return this; } @@ -144,7 +144,7 @@ public OpenPGPMessageGenerator addSigningKey( SecretKeyPassphraseProvider signingKeyDecryptorProvider, SubkeySelector subkeySelector) { - config.signingKeys.add(new Signer(signingKey, signingKeyDecryptorProvider, subkeySelector)); + config.signingKeys.add(new Signer(signingKey, implementation.policy(), signingKeyDecryptorProvider, subkeySelector)); return this; } @@ -426,12 +426,12 @@ public interface CompressionNegotiator * @param configuration message generator configuration * @return negotiated compression algorithm ID */ - int negotiateCompression(Configuration configuration); + int negotiateCompression(Configuration configuration, OpenPGPPolicy policy); } public interface HashAlgorithmNegotiator { - int negotiateHashAlgorithm(OpenPGPKey key, OpenPGPCertificate.OpenPGPComponentKey subkey); + int negotiateHashAlgorithm(OpenPGPKey key, OpenPGPCertificate.OpenPGPComponentKey subkey, OpenPGPPolicy policy); } public static class Configuration @@ -455,9 +455,25 @@ public Configuration(OpenPGPPolicy policy) .enableCRC(false) // Disable CRC sum .build(outputStream); - private SubkeySelector encryptionKeySelector = OpenPGPCertificate::getEncryptionKeys; + private SubkeySelector encryptionKeySelector = new SubkeySelector() { + @Override + public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + return certificate.getEncryptionKeys() + .stream() + .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) + .collect(Collectors.toList()); + } + }; - private SubkeySelector signingKeySelector = OpenPGPCertificate::getSigningKeys; + private SubkeySelector signingKeySelector = new SubkeySelector() { + @Override + public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + return certificate.getSigningKeys() + .stream() + .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) + .collect(Collectors.toList()); + } + }; // Encryption method negotiator for when only password-based encryption is requested private OpenPGPEncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> @@ -471,23 +487,21 @@ public Configuration(OpenPGPPolicy policy) .collect(Collectors.toList()); // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. - if (policy.isProduceFeature(Features.FEATURE_SEIPD_V2) && - OpenPGPEncryptionNegotiator.allRecipientsSupportSeipd2(certificates)) + if (OpenPGPEncryptionNegotiator.allRecipientsSupportSeipd2(certificates)) { PreferredAEADCiphersuites commonDenominator = - OpenPGPEncryptionNegotiator.negotiateAEADCiphersuite(certificates); + OpenPGPEncryptionNegotiator.negotiateAEADCiphersuite(certificates, configuration.policy); return MessageEncryptionMechanism.aead(commonDenominator.getAlgorithms()[0]); } - else if (policy.isProduceFeature(Features.FEATURE_AEAD_ENCRYPTED_DATA) && - OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificates)) + else if (OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificates)) { return MessageEncryptionMechanism.librePgp( - OpenPGPEncryptionNegotiator.bestOEDEncryptionModeByWeight(certificates)); + OpenPGPEncryptionNegotiator.bestOEDEncryptionModeByWeight(certificates, configuration.policy)); } else { return MessageEncryptionMechanism.integrityProtected( - OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight(certificates)); + OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight(certificates, configuration.policy)); } }; @@ -516,18 +530,25 @@ else if (configuration.recipients.isEmpty()) // TODO: Implement properly, taking encryption into account (sign-only should not compress) private CompressionNegotiator compressionNegotiator = - configuration -> CompressionAlgorithmTags.UNCOMPRESSED; + (configuration, policy) -> CompressionAlgorithmTags.UNCOMPRESSED; private HashAlgorithmNegotiator hashAlgorithmNegotiator = - (key, subkey) -> + (key, subkey, policy) -> { // TODO: Take into consideration hash preferences of recipients, not the sender PreferredAlgorithms hashPreferences = subkey.getHashAlgorithmPreferences(); - if (hashPreferences == null) + if (hashPreferences != null) { - return HashAlgorithmTags.SHA512; + int[] pref = Arrays.stream(hashPreferences.getPreferences()) + .filter(it -> policy.isAcceptableDocumentSignatureHashAlgorithm(it, new Date())) + .toArray(); + if (pref.length != 0) + { + return pref[0]; + } } - return hashPreferences.getPreferences()[0]; + + return HashAlgorithmTags.SHA512; }; /** @@ -634,12 +655,12 @@ public Configuration setPadded(boolean isPadded) public int negotiateCompression() { - return compressionNegotiator.negotiateCompression(this); + return compressionNegotiator.negotiateCompression(this, policy); } public int negotiateHashAlgorithm(OpenPGPKey signingKey, OpenPGPKey.OpenPGPSecretKey signingSubkey) { - return hashAlgorithmNegotiator.negotiateHashAlgorithm(signingKey, signingSubkey); + return hashAlgorithmNegotiator.negotiateHashAlgorithm(signingKey, signingSubkey, policy); } public MessageEncryptionMechanism negotiateEncryption() @@ -654,6 +675,7 @@ public MessageEncryptionMechanism negotiateEncryption() static class Recipient { private final OpenPGPCertificate certificate; + private final OpenPGPPolicy policy; private final SubkeySelector subkeySelector; /** @@ -664,12 +686,13 @@ static class Recipient */ public Recipient(PGPPublicKeyRing certificate, SubkeySelector subkeySelector, OpenPGPImplementation implementation) { - this(new OpenPGPCertificate(certificate, implementation), subkeySelector); + this(new OpenPGPCertificate(certificate, implementation), implementation.policy(), subkeySelector); } - public Recipient(OpenPGPCertificate certificate, SubkeySelector subkeySelector) + public Recipient(OpenPGPCertificate certificate, OpenPGPPolicy policy, SubkeySelector subkeySelector) { this.certificate = certificate; + this.policy = policy; this.subkeySelector = subkeySelector; } @@ -680,7 +703,7 @@ public Recipient(OpenPGPCertificate certificate, SubkeySelector subkeySelector) */ public List encryptionSubkeys() { - return subkeySelector.select(certificate) + return subkeySelector.select(certificate, policy) .stream() .distinct() .collect(Collectors.toList()); @@ -693,21 +716,24 @@ public List encryptionSubkeys() static class Signer { private final OpenPGPKey signingKey; + private final OpenPGPPolicy policy; private final SecretKeyPassphraseProvider passphraseProvider; private final SubkeySelector subkeySelector; public Signer(OpenPGPKey signingKey, + OpenPGPPolicy policy, SecretKeyPassphraseProvider passphraseProvider, SubkeySelector subkeySelector) { this.signingKey = signingKey; + this.policy = policy; this.passphraseProvider = passphraseProvider; this.subkeySelector = subkeySelector; } public List signingSubkeys() { - return subkeySelector.select(signingKey) + return subkeySelector.select(signingKey, policy) .stream() .map(signingKey::getSecretKey) .distinct() @@ -727,9 +753,10 @@ public interface SubkeySelector * {@link KeyIdentifier KeyIdentifiers}. * * @param certificate OpenPGP key or certificate + * @param policy OpenPGP algorithm policy * @return non-null list of identifiers */ - List select(OpenPGPCertificate certificate); + List select(OpenPGPCertificate certificate, OpenPGPPolicy policy); } public interface SecretKeyPassphraseProvider diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index c6be1c4398..0c6f3f6c7b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -44,7 +44,7 @@ public OpenPGPMessageProcessor() public OpenPGPMessageProcessor(OpenPGPImplementation implementation) { this.implementation = implementation; - this.configuration = new Configuration(); + this.configuration = new Configuration(implementation.policy()); } /** @@ -481,6 +481,7 @@ void onException(PGPException e) public static class Configuration { + private final OpenPGPPolicy policy; private final OpenPGPKeyMaterialPool.OpenPGPCertificatePool certificatePool; private final OpenPGPKeyMaterialPool.OpenPGPKeyPool keyPool; private final KeyPassphraseProvider.DefaultKeyPassphraseProvider keyPassphraseProvider; @@ -491,8 +492,9 @@ public static class Configuration private Date verifyNotAfter = new Date(); // now private Date verifyNotBefore = new Date(0L); // beginning of time - public Configuration() + public Configuration(OpenPGPPolicy policy) { + this.policy = policy; this.certificatePool = new OpenPGPKeyMaterialPool.OpenPGPCertificatePool(); this.keyPool = new OpenPGPKeyMaterialPool.OpenPGPKeyPool(); this.keyPassphraseProvider = new KeyPassphraseProvider.DefaultKeyPassphraseProvider(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java index 2a7492a7ba..aa1c2556d0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java @@ -44,12 +44,11 @@ default boolean isAcceptablePublicKey(PGPPublicKey key) switch (key.getAlgorithm()) { case PublicKeyAlgorithmTags.RSA_GENERAL: - return key.getBitStrength() >= 2048; case PublicKeyAlgorithmTags.Ed25519: case PublicKeyAlgorithmTags.Ed448: case PublicKeyAlgorithmTags.X25519: case PublicKeyAlgorithmTags.X448: - return true; + return isAcceptablePublicKeyStrength(key.getAlgorithm(), key.getBitStrength()); default: return false; @@ -162,6 +161,10 @@ default boolean isKnownSignatureSubpacket(int signatureSubpacketTag) boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + boolean isAcceptableSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId); + + boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitStrength); + OpenPGPNotationRegistry getNotationRegistry(); class OpenPGPNotationRegistry diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java index 7ef3b9fc94..2bf7acac82 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java @@ -2,6 +2,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPDefaultPolicy; import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; import java.io.IOException; @@ -142,7 +143,7 @@ public static String toString(OpenPGPCertificate certificate, Date evaluationTim sb.append(indent); try { - link.verify(new BcPGPContentVerifierBuilderProvider()); + link.verify(new BcPGPContentVerifierBuilderProvider(), new OpenPGPDefaultPolicy()); if (revocation) { if (isHardRevocation) From fe3e7b8767de58bf0ca0042ba0cb285060928014 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Dec 2024 14:44:35 +0100 Subject: [PATCH 045/154] More policy progress --- .../openpgp/api/OpenPGPCertificate.java | 4 ++++ .../api/OpenPGPMessageInputStream.java | 20 ++++++++++++++++ .../openpgp/api/OpenPGPPolicy.java | 24 +------------------ 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index b335300294..382a0702a3 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -898,6 +898,10 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi sanitize(issuer); + if (!policy.isAcceptablePublicKey(issuer.getPGPPublicKey())) + { + throw new PGPSignatureException("Unacceptable issuer key."); + } if (!policy.hasAcceptableSignatureHashAlgorithm(signature)) { throw new PGPSignatureException("Unacceptable hash algorithm: " + signature.getHashAlgorithm()); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index ef79204586..7053b6b66f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -635,6 +635,7 @@ void update(byte[] b, int off, int len) List verify( OpenPGPMessageProcessor processor) { + OpenPGPPolicy policy = processor.getImplementation().policy(); List dataSignatures = new ArrayList<>(); int num = onePassSignatures.size(); for (int i = 0; i < signatures.size(); i++) @@ -647,6 +648,15 @@ List verify( continue; } + if (!policy.isAcceptablePublicKey(key.getPGPPublicKey())) + { + continue; + } + if (!policy.isAcceptableSignature(signature)) + { + continue; + } + OpenPGPSignature.OpenPGPDocumentSignature dataSignature = new OpenPGPSignature.OpenPGPDocumentSignature(signature, key); if (!dataSignature.createdInBounds(processor.getVerifyNotBefore(), processor.getVerifyNotAfter())) @@ -737,8 +747,18 @@ void update(byte[] buf, int off, int len) List verify(OpenPGPMessageProcessor processor) { + OpenPGPPolicy policy = processor.getImplementation().policy(); for (OpenPGPSignature.OpenPGPDocumentSignature sig : dataSignatures) { + if (!policy.isAcceptablePublicKey(sig.getIssuer().getPGPPublicKey())) + { + continue; + } + if (!policy.isAcceptableSignature(sig.signature)) + { + continue; + } + try { sig.verify(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java index aa1c2556d0..3b3a740736 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java @@ -1,7 +1,5 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.sig.NotationData; import org.bouncycastle.openpgp.PGPPublicKey; @@ -36,27 +34,7 @@ default boolean isAcceptableDecryptionKey(PGPPublicKey key) default boolean isAcceptablePublicKey(PGPPublicKey key) { - switch (key.getVersion()) - { - case PublicKeyPacket.VERSION_4: - case PublicKeyPacket.LIBREPGP_5: - case PublicKeyPacket.VERSION_6: - switch (key.getAlgorithm()) - { - case PublicKeyAlgorithmTags.RSA_GENERAL: - case PublicKeyAlgorithmTags.Ed25519: - case PublicKeyAlgorithmTags.Ed448: - case PublicKeyAlgorithmTags.X25519: - case PublicKeyAlgorithmTags.X448: - return isAcceptablePublicKeyStrength(key.getAlgorithm(), key.getBitStrength()); - - default: - return false; - } - - default: - return false; - } + return isAcceptablePublicKeyStrength(key.getAlgorithm(), key.getBitStrength()); } default boolean isAcceptableSignature(PGPSignature signature) From 8be9ac44d80a86f627ed0233313e5c7073c0de01 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 2 Jan 2025 15:14:49 +0100 Subject: [PATCH 046/154] Move OpenPGPImplementation subclasses into packages --- .../org/bouncycastle/openpgp/api/OpenPGPImplementation.java | 1 + .../openpgp/api/{ => bc}/BcOpenPGPImplementation.java | 3 ++- .../bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java | 1 - .../openpgp/api/{ => jcajce}/JcaOpenPGPImplementation.java | 3 ++- .../openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java | 1 - .../openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) rename pg/src/main/java/org/bouncycastle/openpgp/api/{ => bc}/BcOpenPGPImplementation.java (98%) rename pg/src/main/java/org/bouncycastle/openpgp/api/{ => jcajce}/JcaOpenPGPImplementation.java (98%) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java index dfcab08de7..4153b53c7b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -6,6 +6,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java similarity index 98% rename from pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java index 1aeed80b40..8ef91c9f5d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java @@ -1,4 +1,4 @@ -package org.bouncycastle.openpgp.api; +package org.bouncycastle.openpgp.api.bc; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.openpgp.PGPException; @@ -6,6 +6,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.api.OpenPGPImplementation; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java index 52c6cb7b76..3c85e7ef08 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java @@ -1,7 +1,6 @@ package org.bouncycastle.openpgp.api.bc; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.api.BcOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import java.util.Date; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java similarity index 98% rename from pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java index 3546b8bc9b..bf475123a2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/JcaOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java @@ -1,4 +1,4 @@ -package org.bouncycastle.openpgp.api; +package org.bouncycastle.openpgp.api.jcajce; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.crypto.CryptoServicesRegistrar; @@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.api.OpenPGPImplementation; import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java index 311a23a5fa..e11f6b462e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -1,7 +1,6 @@ package org.bouncycastle.openpgp.api.jcajce; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.api.JcaOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import java.security.Provider; diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 42e7067d17..684e82d777 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -8,7 +8,7 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.api.BcOpenPGPImplementation; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; From 95af45d47a1fab398ef8e60381392ba1b6fc5868 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 2 Jan 2025 15:36:54 +0100 Subject: [PATCH 047/154] Move armor comment methods to ArmoredOutputStream.Builder --- .../bcpg/ArmoredOutputStream.java | 35 +++++++++++++++++++ .../openpgp/api/OpenPGPCertificate.java | 35 ++----------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java index aa957720ad..76947cfb8d 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java @@ -612,6 +612,41 @@ public Builder addComment(String comment) return addHeader(COMMENT_HDR, comment); } + public Builder addEllipsizedComment(String comment) + { + int availableCommentCharsPerLine = 64 - (COMMENT_HDR.length() + 2); // ASCII armor width - header len + comment = comment.trim(); + + if (comment.length() > availableCommentCharsPerLine) + { + comment = comment.substring(0, availableCommentCharsPerLine - 1) + '…'; + } + addComment(comment); + return this; + } + + public Builder addSplitMultilineComment(String comment) + { + int availableCommentCharsPerLine = 64 - (COMMENT_HDR.length() + 2); // ASCII armor width - header len + + comment = comment.trim(); + for (String line : comment.split("\n")) + { + while (line.length() > availableCommentCharsPerLine) + { + // split comment into multiple lines + addComment(comment.substring(0, availableCommentCharsPerLine)); + line = line.substring(availableCommentCharsPerLine).trim(); + } + + if (!line.isEmpty()) + { + addComment(line); + } + } + return this; + } + /** * Set and replace the given header value with a single-line header. * If the value is
null
, this method will remove the header entirely. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 382a0702a3..ac51959c40 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -387,12 +387,12 @@ public String toAsciiArmoredString() ArmoredOutputStream.Builder armorBuilder = ArmoredOutputStream.builder() .clearHeaders(); // Add fingerprint comment - splitMultilineComment(armorBuilder, getPrettyFingerprint()); + armorBuilder.addSplitMultilineComment(getPrettyFingerprint()); // Add user-id comments for (OpenPGPUserId userId : getPrimaryKey().getUserIDs()) { - ellipsizedComment(armorBuilder, userId.getUserId()); + armorBuilder.addEllipsizedComment(userId.getUserId()); } ArmoredOutputStream aOut = armorBuilder.build(bOut); @@ -412,37 +412,6 @@ public String toAsciiArmoredString() return bOut.toString(); } - private void splitMultilineComment(ArmoredOutputStream.Builder armorBuilder, String comment) - { - int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len - - comment = comment.trim(); - - while (comment.length() > availableCommentCharsPerLine) - { - // split comment into multiple lines - armorBuilder.addComment(comment.substring(0, availableCommentCharsPerLine)); - comment = comment.substring(availableCommentCharsPerLine).trim(); - } - - if (!comment.isEmpty()) - { - armorBuilder.addComment(comment); - } - } - - private void ellipsizedComment(ArmoredOutputStream.Builder armorBuilder, String comment) - { - int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len - comment = comment.trim(); - - if (comment.length() > availableCommentCharsPerLine) - { - comment = comment.substring(0, availableCommentCharsPerLine - 1) + '…'; - } - armorBuilder.addComment(comment); - } - protected List fingerprintComments() { // TODO: Implement slicing in ArmoredOutputStream.Builder instead? From cfa5e63337968272294b5ecddd57f8a48018e4d5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 2 Jan 2025 15:37:10 +0100 Subject: [PATCH 048/154] Fix lambdas --- .../api/OpenPGPEncryptionNegotiator.java | 3 ++- .../openpgp/api/OpenPGPMessageGenerator.java | 14 ++++++++++---- .../api/test/OpenPGPMessageGeneratorTest.java | 4 ++-- .../api/test/OpenPGPMessageProcessorTest.java | 12 ++++++++---- .../StaticV6OpenPGPMessageGeneratorTest.java | 19 +++++++++++++++++-- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java index 0b9c780013..66422c4043 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java @@ -266,7 +266,8 @@ static int bestOEDEncryptionModeByWeight(Collection certific int[] algorithms = Arrays.stream(preferences.getPreferences()) .filter(alg -> { - switch (alg) { + switch (alg) + { case SymmetricKeyAlgorithmTags.AES_128: case SymmetricKeyAlgorithmTags.AES_192: case SymmetricKeyAlgorithmTags.AES_256: diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index c1dd825b7a..730a8eaee2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -455,9 +455,12 @@ public Configuration(OpenPGPPolicy policy) .enableCRC(false) // Disable CRC sum .build(outputStream); - private SubkeySelector encryptionKeySelector = new SubkeySelector() { + private SubkeySelector encryptionKeySelector = new SubkeySelector() + { @Override - public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + public List select(OpenPGPCertificate certificate, + OpenPGPPolicy policy) + { return certificate.getEncryptionKeys() .stream() .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) @@ -465,9 +468,12 @@ public List select(OpenPGPCertificate ce } }; - private SubkeySelector signingKeySelector = new SubkeySelector() { + private SubkeySelector signingKeySelector = new SubkeySelector() + { @Override - public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + public List select(OpenPGPCertificate certificate, + OpenPGPPolicy policy) + { return certificate.getSigningKeys() .stream() .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java index 826788a7f3..0f512e4e15 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -85,7 +85,7 @@ private void armoredCompressedLiteralDataPacket() OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); gen.setIsPadded(false); OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); - configuration.setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + configuration.setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -109,7 +109,7 @@ private void unarmoredCompressedLiteralDataPacket() gen.setArmored(false); // no armor gen.setIsPadded(false); OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); - configuration.setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + configuration.setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 53b3f64b31..0c47bdcdc7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -79,7 +79,8 @@ private void roundtripUnarmoredPlaintextMessage() .setArmored(false) .setIsPadded(false); - gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.UNCOMPRESSED); + gen.getConfiguration().setCompressionNegotiator( + (conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -103,7 +104,8 @@ private void roundtripArmoredPlaintextMessage() OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() .setArmored(true) .setIsPadded(false); - gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.UNCOMPRESSED); + gen.getConfiguration().setCompressionNegotiator( + (conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -128,7 +130,8 @@ private void roundTripCompressedMessage() OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() .setArmored(true) .setIsPadded(false); - gen.getConfiguration().setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + gen.getConfiguration().setCompressionNegotiator( + (conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -158,7 +161,8 @@ private void roundTripCompressedSymEncMessageMessage() gen.getConfiguration() .setPasswordBasedEncryptionNegotiator(conf -> MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256)) - .setCompressionNegotiator(conf -> CompressionAlgorithmTags.ZIP); + .setCompressionNegotiator( + (conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java index 39570c6402..be7c540e5e 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java @@ -4,15 +4,18 @@ import org.bouncycastle.bcpg.test.AbstractPacketTest; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.openpgp.api.OpenPGPPolicy; import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.List; public class StaticV6OpenPGPMessageGeneratorTest extends AbstractPacketTest @@ -79,8 +82,20 @@ public OpenPGPMessageGenerator getStaticGenerator() OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); gen.getConfiguration() - .setEncryptionKeySelector(keyRing -> Collections.singletonList(keyRing.getKey(encryptionKeyIdentifier))) - .setSigningKeySelector(keyRing -> Collections.singletonList(keyRing.getKey(signingKeyIdentifier))); + .setEncryptionKeySelector( + new OpenPGPMessageGenerator.SubkeySelector() { + @Override + public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + return Collections.singletonList(certificate.getKey(encryptionKeyIdentifier)); + } + }) + .setSigningKeySelector( + new OpenPGPMessageGenerator.SubkeySelector() { + @Override + public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + return Collections.singletonList(certificate.getKey(signingKeyIdentifier)); + } + }); return gen; } From 60f65c8d6d914459d8ecc602e9b2b63f7aae19ce Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 11:13:17 +0100 Subject: [PATCH 049/154] Remove fingerprintSlices() method --- .../openpgp/api/OpenPGPCertificate.java | 33 +++++++------------ .../bouncycastle/openpgp/api/OpenPGPKey.java | 5 +-- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index ac51959c40..80ec4b8791 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -4,8 +4,6 @@ import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.FingerprintUtil; -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.bcpg.HashUtils; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; @@ -15,7 +13,18 @@ import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.openpgp.*; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureException; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.api.exception.IncorrectPGPSignatureException; import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; @@ -35,7 +44,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -412,23 +420,6 @@ public String toAsciiArmoredString() return bOut.toString(); } - protected List fingerprintComments() - { - // TODO: Implement slicing in ArmoredOutputStream.Builder instead? - String prettyPrinted = FingerprintUtil.prettifyFingerprint(getFingerprint()); - - int availableCommentCharsPerLine = 64 - "Comment: ".length(); // ASCII armor width - header len - List slices = new ArrayList<>(); - - while (prettyPrinted.length() > availableCommentCharsPerLine) - { - slices.add(prettyPrinted.substring(0, availableCommentCharsPerLine)); - prettyPrinted = prettyPrinted.substring(availableCommentCharsPerLine).trim(); - } - slices.add(prettyPrinted); - return slices; - } - private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent component, OpenPGPComponentKey origin, Date evaluationDate) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 342faf188d..861e1aca56 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -203,10 +203,7 @@ public String toAsciiArmoredString() ArmoredOutputStream.Builder armorBuilder = ArmoredOutputStream.builder() .clearHeaders(); - for (String slice : fingerprintComments()) - { - armorBuilder.addComment(slice); - } + armorBuilder.addSplitMultilineComment(getPrettyFingerprint()); for (OpenPGPUserId userId : getPrimaryKey().getUserIDs()) { From 7ea0e2bd020a7e3cf150ae1eca231c50779347cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 13:15:46 +0100 Subject: [PATCH 050/154] Add getPrimaryUserId() methods --- .../openpgp/api/OpenPGPCertificate.java | 260 +++++++++++++----- .../openpgp/api/OpenPGPSignature.java | 71 +++++ .../api/test/OpenPGPCertificateTest.java | 39 +++ 3 files changed, 299 insertions(+), 71 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 80ec4b8791..958d4a100f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -13,6 +13,7 @@ import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.bcpg.sig.PrimaryUserID; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPObjectFactory; @@ -696,6 +697,28 @@ public List getIdentities() return new ArrayList<>(primaryKey.identityComponents); } + public OpenPGPUserId getPrimaryUserId() + { + return getPrimaryUserId(new Date()); + } + + public OpenPGPUserId getPrimaryUserId(Date evaluationTime) + { + return primaryKey.getExplicitOrImplicitPrimaryUserId(evaluationTime); + } + + public OpenPGPUserId getUserId(String userId) + { + for (OpenPGPUserId uid : primaryKey.getUserIDs()) + { + if (uid.getUserId().equals(userId)) + { + return uid; + } + } + return null; + } + /** * Component on an OpenPGP certificate. * Components can either be {@link OpenPGPComponentKey keys} or {@link OpenPGPIdentityComponent identities}. @@ -769,6 +792,73 @@ protected OpenPGPCertificateComponent getPublicComponent() { return this; } + + /** + * Return the {@link SignatureSubpacket} instance of the given subpacketType, which currently applies to + * the key. Since subpackets from the Direct-Key signature apply to all subkeys of a certificate, + * this method first inspects the signature that immediately applies to this key (e.g. a subkey-binding + * signature), and - if the queried subpacket is found in there, returns that instance. + * Otherwise, indirectly applying signatures (e.g. Direct-Key signatures) are queried. + * That way, preferences from the direct-key signature are considered, but per-key overwrites take precedence. + * + * @see + * OpenPGP for application developers - Attribute Shadowing + * + * @param evaluationTime evaluation time + * @param subpacketType subpacket type that is being searched for + * @return subpacket from directly or indirectly applying signature + */ + protected OpenPGPSignature.OpenPGPSignatureSubpacket getApplyingSubpacket(Date evaluationTime, int subpacketType) + { + OpenPGPSignatureChain binding = getSignatureChains().getCertificationAt(evaluationTime); + if (binding == null) + { + // is not bound + return null; + } + + // Check signatures + try + { + if (!binding.isValid()) + { + // Binding is incorrect + return null; + } + } + catch (PGPSignatureException e) + { + // Binding cannot be verified + return null; + } + + // find signature "closest to the key", e.g. subkey binding signature + OpenPGPComponentSignature keySignature = binding.getHeadLink().getSignature(); + + PGPSignatureSubpacketVector hashedSubpackets = keySignature.getSignature().getHashedSubPackets(); + if (hashedSubpackets == null || !hashedSubpackets.hasSubpacket(subpacketType)) + { + // If the subkey binding signature doesn't carry the desired subpacket, + // check direct-key or primary uid sig instead + OpenPGPSignatureChain preferenceBinding = getCertificate().getPreferenceSignature(evaluationTime); + if (preferenceBinding == null) + { + // No direct-key / primary uid sig found -> No subpacket + return null; + } + keySignature = preferenceBinding.getHeadLink().signature; + hashedSubpackets = keySignature.getSignature().getHashedSubPackets(); + } + // else -> attribute from DK sig is shadowed by SB sig + + // Extract subpacket from hashed area + SignatureSubpacket subpacket = hashedSubpackets.getSubpacket(subpacketType); + if (subpacket == null) + { + return null; + } + return OpenPGPSignature.OpenPGPSignatureSubpacket.hashed(subpacket, keySignature); + } } /** @@ -1175,11 +1265,11 @@ public KeyFlags getKeyFlags() */ public KeyFlags getKeyFlags(Date evaluationTime) { - SignatureSubpacket subpacket = getApplyingSubpacket( + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket( evaluationTime, SignatureSubpacketTags.KEY_FLAGS); if (subpacket != null) { - return (KeyFlags) subpacket; + return (KeyFlags) subpacket.getSubpacket(); } return null; } @@ -1200,75 +1290,14 @@ public Features getFeatures() */ public Features getFeatures(Date evaluationTime) { - SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.FEATURES); + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.FEATURES); if (subpacket != null) { - return (Features) subpacket; + return (Features) subpacket.getSubpacket(); } return null; } - /** - * Return the {@link SignatureSubpacket} instance of the given subpacketType, which currently applies to - * the key. Since subpackets from the Direct-Key signature apply to all subkeys of a certificate, - * this method first inspects the signature that immediately applies to this key (e.g. a subkey-binding - * signature), and - if the queried subpacket is found in there, returns that instance. - * Otherwise, indirectly applying signatures (e.g. Direct-Key signatures) are queried. - * That way, preferences from the direct-key signature are considered, but per-key overwrites take precedence. - * - * @see - * OpenPGP for application developers - Attribute Shadowing - * - * @param evaluationTime evaluation time - * @param subpacketType subpacket type that is being searched for - * @return subpacket from directly or indirectly applying signature - */ - protected SignatureSubpacket getApplyingSubpacket(Date evaluationTime, int subpacketType) - { - OpenPGPSignatureChain binding = getSignatureChains().getCertificationAt(evaluationTime); - if (binding == null) - { - // is not bound - return null; - } - - // Check signatures - try - { - if (!binding.isValid()) - { - // Binding is incorrect - return null; - } - } - catch (PGPSignatureException e) - { - // Binding cannot be verified - return null; - } - - // find signature "closest to the key", e.g. subkey binding signature - OpenPGPComponentSignature keySignature = binding.getHeadLink().getSignature(); - - PGPSignatureSubpacketVector hashedSubpackets = keySignature.getSignature().getHashedSubPackets(); - if (hashedSubpackets == null || !hashedSubpackets.hasSubpacket(subpacketType)) - { - // If the subkey binding signature doesn't carry the desired subpacket, - // check direct-key or primary uid sig instead - OpenPGPSignatureChain preferenceBinding = getCertificate().getPreferenceSignature(evaluationTime); - if (preferenceBinding == null) - { - // No direct-key / primary uid sig found -> No subpacket - return null; - } - hashedSubpackets = preferenceBinding.getHeadLink().getSignature().getSignature().getHashedSubPackets(); - } - // else -> attribute from DK sig is shadowed by SB sig - - // Extract subpacket from hashed area - return hashedSubpackets.getSubpacket(subpacketType); - } - public PreferredAEADCiphersuites getAEADCipherSuitePreferences() { return getAEADCipherSuitePreferences(new Date()); @@ -1276,11 +1305,11 @@ public PreferredAEADCiphersuites getAEADCipherSuitePreferences() public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTime) { - SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); if (subpacket != null) { - return (PreferredAEADCiphersuites) subpacket; + return (PreferredAEADCiphersuites) subpacket.getSubpacket(); } return null; } @@ -1292,10 +1321,10 @@ public PreferredAlgorithms getSymmetricCipherPreferences() public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) { - SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); if (subpacket != null) { - return (PreferredAlgorithms) subpacket; + return (PreferredAlgorithms) subpacket.getSubpacket(); } return null; } @@ -1307,10 +1336,10 @@ public PreferredAlgorithms getHashAlgorithmPreferences() public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) { - SignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); if (subpacket != null) { - return (PreferredAlgorithms) subpacket; + return (PreferredAlgorithms) subpacket.getSubpacket(); } return null; } @@ -1372,6 +1401,72 @@ public List getUserIDs() return userIds; } + public OpenPGPUserId getExplicitPrimaryUserId(Date evaluationTime) + { + // Return the latest, valid, explicitly marked as primary UserID + OpenPGPSignature latestBinding = null; + OpenPGPUserId latestUid = null; + + for (OpenPGPUserId userId : getUserIDs()) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = + userId.getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PRIMARY_USER_ID); + if (subpacket == null) + { + // Not bound at this time, or not explicit + continue; + } + + PrimaryUserID primaryUserId = (PrimaryUserID) subpacket.getSubpacket(); + if (!primaryUserId.isPrimaryUserID()) + { + // explicitly marked as not primary + continue; + } + + if (latestBinding == null || + subpacket.getSignature().getCreationTime().after(latestBinding.getCreationTime())) + { + latestBinding = subpacket.getSignature(); + latestUid = userId; + } + } + return latestUid; + } + + public OpenPGPUserId getExplicitOrImplicitPrimaryUserId(Date evaluationTime) + { + OpenPGPUserId explicitPrimaryUserId = getExplicitPrimaryUserId(evaluationTime); + if (explicitPrimaryUserId != null) + { + return explicitPrimaryUserId; + } + + // If no explicitly marked, valid primary UserID is found, return the oldest, valid UserId instead. + OpenPGPSignature oldestBinding = null; + OpenPGPUserId oldestUid = null; + + for (OpenPGPUserId userId : getUserIDs()) + { + OpenPGPSignatureChain chain = userId.getSignatureChains() + .getCertificationAt(evaluationTime); + if (chain == null) + { + // Not valid at this time + continue; + } + + OpenPGPSignature binding = chain.getHeadLink().getSignature(); + if (oldestBinding == null || + binding.getCreationTime().before(oldestBinding.getCreationTime())) + { + oldestBinding = binding; + oldestUid = userId; + } + } + return oldestUid; + } + /** * Return all {@link OpenPGPUserAttribute OpenPGPUserAttributes} on this key. * @@ -1540,6 +1635,29 @@ public String toString() { return "UserID[" + userId + "]"; } + + @Override + public boolean equals(Object obj) { + if (obj == null) + { + return false; + } + if (this == obj) + { + return true; + } + if (!(obj instanceof OpenPGPUserId)) + { + return false; + } + return getUserId().equals(((OpenPGPUserId) obj).getUserId()); + } + + @Override + public int hashCode() + { + return userId.hashCode(); + } } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 4884133d91..a40830fd79 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -397,6 +397,77 @@ protected String getType() } } + /** + * {@link SignatureSubpacket} and the {@link OpenPGPSignature} that contains it. + */ + public static final class OpenPGPSignatureSubpacket + { + private final SignatureSubpacket subpacket; + private final OpenPGPSignature signature; + private final boolean hashed; + + private OpenPGPSignatureSubpacket(SignatureSubpacket subpacket, + OpenPGPSignature signature, + boolean hashed) + { + this.signature = signature; + this.subpacket = subpacket; + this.hashed = hashed; + } + + /** + * Create a {@link OpenPGPSignatureSubpacket} contained in the hashed area of an {@link OpenPGPSignature}. + * + * @param subpacket subpacket + * @param signature the signature containing the subpacket + * @return OpenPGPSignatureSubpacket + */ + public static OpenPGPSignatureSubpacket hashed(SignatureSubpacket subpacket, OpenPGPSignature signature) + { + return new OpenPGPSignatureSubpacket(subpacket, signature, true); + } + + /** + * Create a {@link OpenPGPSignatureSubpacket} contained in the unhashed area of an {@link OpenPGPSignature}. + * + * @param subpacket subpacket + * @param signature the signature containing the subpacket + * @return OpenPGPSignatureSubpacket + */ + public static OpenPGPSignatureSubpacket unhashed(SignatureSubpacket subpacket, OpenPGPSignature signature) + { + return new OpenPGPSignatureSubpacket(subpacket, signature, false); + } + + /** + * Return the {@link OpenPGPSignature} that contains the {@link SignatureSubpacket}. + * @return signature + */ + public OpenPGPSignature getSignature() + { + return signature; + } + + /** + * Return the {@link SignatureSubpacket} itself. + * @return + */ + public SignatureSubpacket getSubpacket() + { + return subpacket; + } + + /** + * Return
true
if the subpacket is contained in the hashed area of the {@link OpenPGPSignature}, + * false otherwise. + * @return true if the subpacket is hashed, false if it is unhashed + */ + public boolean isHashed() + { + return hashed; + } + } + /** * An {@link OpenPGPSignature} made over a binary or textual document (e.g. a message). * Also known as a Data Signature. diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index b866a270e7..46ab89f611 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -3,21 +3,28 @@ import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.KeyIdentifier; +import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.test.AbstractPacketTest; import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; public class OpenPGPCertificateTest @@ -40,6 +47,7 @@ public void performTest() testPKSignsPKRevokedNoSubpacket(); testSKSignsPKRevokedNoSubpacket(); testPKSignsPKRevocationSuperseded(); + testGetPrimaryUserId(); } private void testOpenPGPv6Key() @@ -786,6 +794,37 @@ private void signatureValidityTest(String cert, TestSignature... testSignatures) } } + private void testGetPrimaryUserId() + throws PGPException + { + Date now = new Date((new Date().getTime() / 1000) * 1000); + Date oneHourAgo = new Date(now.getTime() - 1000 * 60 * 60); + + OpenPGPV6KeyGenerator gen = new BcOpenPGPV6KeyGenerator(oneHourAgo); + OpenPGPKey key = gen.withPrimaryKey() + .addUserId("Old non-primary ") + .addUserId("New primary ", + new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.CREATION_TIME); + subpackets.setSignatureCreationTime(now); + subpackets.setPrimaryUserID(false, true); + return subpackets; + } + }) + .build(null); + isEquals("Expect to find valid, explicit primary user ID", + key.getUserId("New primary "), + key.getPrimaryUserId()); + + isEquals("Explicit primary UserID is not yet valid, so return implicit UID", + key.getUserId("Old non-primary "), + key.getPrimaryUserId(oneHourAgo)); + } + public static class TestSignature { private final PGPSignature signature; From e445d9546ba5f0a68e5b36c87dd3276b9e1b1974 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 13:41:34 +0100 Subject: [PATCH 051/154] Add Locale to toUpperCase() calls --- pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java | 4 +++- pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java | 3 ++- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 5 +++-- .../java/org/bouncycastle/openpgp/api/OpenPGPSignature.java | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java b/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java index adf16a67c0..9e249b3983 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/FingerprintUtil.java @@ -3,6 +3,8 @@ import org.bouncycastle.util.Pack; import org.bouncycastle.util.encoders.Hex; +import java.util.Locale; + public class FingerprintUtil { @@ -141,7 +143,7 @@ public static void writeKeyID(long keyID, byte[] bytes) public static String prettifyFingerprint(byte[] fingerprint) { // -DM Hex.toHexString - char[] hex = Hex.toHexString(fingerprint).toUpperCase().toCharArray(); + char[] hex = Hex.toHexString(fingerprint).toUpperCase(Locale.getDefault()).toCharArray(); StringBuilder sb = new StringBuilder(); switch (hex.length) { diff --git a/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java b/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java index 16c58f15ef..8bbbe544fd 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java @@ -2,6 +2,7 @@ import java.util.Iterator; import java.util.List; +import java.util.Locale; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; @@ -247,6 +248,6 @@ public String toString() } // -DM Hex.toHexString - return Hex.toHexString(fingerprint).toUpperCase(); + return Hex.toHexString(fingerprint).toUpperCase(Locale.getDefault()); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 958d4a100f..eac607d56e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -44,6 +44,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -1354,7 +1355,7 @@ public static class OpenPGPPrimaryKey @Override public String toString() { - return "PrimaryKey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase() + "]"; + return "PrimaryKey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase(Locale.getDefault()) + "]"; } @Override @@ -1553,7 +1554,7 @@ public OpenPGPSubkey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) @Override public String toString() { - return "Subkey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase() + "]"; + return "Subkey[" + Long.toHexString(getKeyIdentifier().getKeyId()).toUpperCase(Locale.getDefault()) + "]"; } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index a40830fd79..d6eb1d5d6b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -15,6 +15,7 @@ import java.util.Date; import java.util.List; +import java.util.Locale; /** * An OpenPGP signature. @@ -353,7 +354,8 @@ protected String getIssuerDisplay() { return "Anonymous"; } - return "External[" + Long.toHexString(issuerIdentifier.getKeyId()).toUpperCase() + "]"; + return "External[" + Long.toHexString(issuerIdentifier.getKeyId()) + .toUpperCase(Locale.getDefault()) + "]"; } protected abstract String getTargetDisplay(); From 0e1aa4318f2ff4dcb934a53f56aede4dc4079616 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 13:42:32 +0100 Subject: [PATCH 052/154] Signature sanitization: Verify policy --- .../openpgp/api/OpenPGPCertificate.java | 14 ++--- .../openpgp/api/OpenPGPSignature.java | 53 ++++++++++++++++--- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index eac607d56e..141b954eeb 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -947,16 +947,7 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi throw new MissingIssuerCertException("Issuer certificate unavailable."); } - sanitize(issuer); - - if (!policy.isAcceptablePublicKey(issuer.getPGPPublicKey())) - { - throw new PGPSignatureException("Unacceptable issuer key."); - } - if (!policy.hasAcceptableSignatureHashAlgorithm(signature)) - { - throw new PGPSignatureException("Unacceptable hash algorithm: " + signature.getHashAlgorithm()); - } + sanitize(issuer, policy); // Direct-Key signature if (target == issuer) @@ -1638,7 +1629,8 @@ public String toString() } @Override - public boolean equals(Object obj) { + public boolean equals(Object obj) + { if (obj == null) { return false; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index d6eb1d5d6b..ef77299541 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureException; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; import org.bouncycastle.openpgp.api.util.UTCUtil; @@ -245,9 +246,19 @@ public boolean isCertification() * @param issuer signature issuer * @throws MalformedPGPSignatureException if the signature is malformed */ - void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer) - throws MalformedPGPSignatureException + void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, + OpenPGPPolicy policy) + throws PGPSignatureException { + if (!policy.isAcceptablePublicKey(issuer.getPGPPublicKey())) + { + throw new PGPSignatureException("Unacceptable issuer key."); + } + if (!policy.hasAcceptableSignatureHashAlgorithm(signature)) + { + throw new PGPSignatureException("Unacceptable hash algorithm: " + signature.getHashAlgorithm()); + } + PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); if (hashed == null) { @@ -580,9 +591,22 @@ public boolean verify() * @return true if the signature is valid now. */ public boolean isValid() - throws MalformedPGPSignatureException + throws PGPSignatureException + { + return isValid(OpenPGPImplementation.getInstance().policy()); + } + + /** + * Return true, if the signature is valid at this moment using the given policy. + * A valid signature is effective, correct and was issued by a valid signing key. + * + * @param policy policy + * @return true if the signature is valid now. + */ + public boolean isValid(OpenPGPPolicy policy) + throws PGPSignatureException { - return isValidAt(getCreationTime()); + return isValidAt(getCreationTime(), policy); } /** @@ -594,7 +618,22 @@ public boolean isValid() * @throws IllegalStateException if the signature has not yet been tested using a
verify()
method. */ public boolean isValidAt(Date date) - throws MalformedPGPSignatureException + throws PGPSignatureException + { + return isValidAt(date, OpenPGPImplementation.getInstance().policy()); + } + + /** + * Return true, if th signature is valid at the given date using the given policy. + * A valid signature is effective, correct and was issued by a valid signing key. + * + * @param date evaluation time + * @param policy policy + * @return true if the signature is valid at the given date + * @throws IllegalStateException if the signature has not yet been tested using a
verify()
method. + */ + public boolean isValidAt(Date date, OpenPGPPolicy policy) + throws PGPSignatureException { if (!isTested) { @@ -604,7 +643,9 @@ public boolean isValidAt(Date date) { return false; } - sanitize(issuer); + + sanitize(issuer, policy); + return issuer.getCertificate().getPrimaryKey().isBoundAt(date) && issuer.isBoundAt(date) && issuer.isSigningKey(date); From 741be57965c9bfab4320223ad29a815333b1647b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 14:24:13 +0100 Subject: [PATCH 053/154] Add getKeyExpirationTime() method --- .../openpgp/api/OpenPGPCertificate.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 141b954eeb..b0dcc4d716 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -10,6 +10,7 @@ import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyExpirationTime; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; @@ -188,7 +189,6 @@ else if (object instanceof PGPPublicKeyRing) } } - /** * Return the primary key of the certificate. * @@ -1335,6 +1335,34 @@ public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) } return null; } + + public Date getKeyExpirationDate() + { + return getKeyExpirationDateAt(new Date()); + } + + public Date getKeyExpirationDateAt(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = + getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.KEY_EXPIRE_TIME); + if (subpacket != null) + { + long expiresIn = ((KeyExpirationTime) subpacket.getSubpacket()).getTime(); + if (expiresIn == 0L) + { + // Explicit no expiry + return null; + } + + Date creationTime = getCreationTime(); + Date expirationTime = new Date(creationTime.getTime() + 1000 * expiresIn); + return expirationTime; + } + else + { + return null; // implicit no expiry + } + } } /** From 74352f06627f035663c3e50bd64cfa12b6781a99 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 14:24:33 +0100 Subject: [PATCH 054/154] Consider all non-soft revocation reasons as hard --- .../java/org/bouncycastle/openpgp/PGPSignature.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java index 996b4e2876..9617db1336 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java @@ -934,8 +934,17 @@ public boolean isHardRevocation() return true; // missing reason packet is hard } - return reason.getRevocationReason() == RevocationReasonTags.NO_REASON // No reason is hard - || reason.getRevocationReason() == RevocationReasonTags.KEY_COMPROMISED; // key compromise is hard + byte code = reason.getRevocationReason(); + if (code >= 100 && code <= 110) + { + // private / experimental reasons are considered hard + return true; + } + + // Reason is not from the set of known soft reasons + return code != RevocationReasonTags.KEY_SUPERSEDED && + code != RevocationReasonTags.KEY_RETIRED && + code != RevocationReasonTags.USER_NO_LONGER_VALID; } /** From 93fa4d8142c1081fbd9c0c15f08b288ced1c1b34 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 15:32:25 +0100 Subject: [PATCH 055/154] Reject weak document signature hash algorithms --- .../openpgp/api/OpenPGPDefaultPolicy.java | 63 +++++++++++++------ .../OpenPGPDetachedSignatureProcessor.java | 15 +++++ .../api/OpenPGPMessageInputStream.java | 25 +++++--- 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java index 5471fcd21a..4dd1a0f739 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java @@ -12,28 +12,41 @@ public class OpenPGPDefaultPolicy implements OpenPGPPolicy { - private final Map hashAlgorithmCutoffDates = new HashMap<>(); + private final Map documentHashAlgorithmCutoffDates = new HashMap<>(); + private final Map certificateHashAlgorithmCutoffDates = new HashMap<>(); private final Map symmetricKeyAlgorithmCutoffDates = new HashMap<>(); private final Map publicKeyMinimalBitStrengths = new HashMap<>(); public OpenPGPDefaultPolicy() { /* - * Hash Algorithms + * Certification Signature Hash Algorithms */ // SHA-3 - acceptHashAlgorithm(HashAlgorithmTags.SHA3_512); - acceptHashAlgorithm(HashAlgorithmTags.SHA3_256); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA3_512); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA3_256); // SHA-2 - acceptHashAlgorithm(HashAlgorithmTags.SHA512); - acceptHashAlgorithm(HashAlgorithmTags.SHA384); - acceptHashAlgorithm(HashAlgorithmTags.SHA256); - acceptHashAlgorithm(HashAlgorithmTags.SHA224); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA512); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA384); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA256); + acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA224); // SHA-1 - acceptHashAlgorithmUntil(HashAlgorithmTags.SHA1, UTCUtil.parse("2023-02-01 00:00:00 UTC")); + acceptCertificationSignatureHashAlgorithmUntil(HashAlgorithmTags.SHA1, UTCUtil.parse("2023-02-01 00:00:00 UTC")); - acceptHashAlgorithmUntil(HashAlgorithmTags.RIPEMD160, UTCUtil.parse("2023-02-01 00:00:00 UTC")); - acceptHashAlgorithmUntil(HashAlgorithmTags.MD5, UTCUtil.parse("1997-02-01 00:00:00 UTC")); + acceptCertificationSignatureHashAlgorithmUntil(HashAlgorithmTags.RIPEMD160, UTCUtil.parse("2023-02-01 00:00:00 UTC")); + acceptCertificationSignatureHashAlgorithmUntil(HashAlgorithmTags.MD5, UTCUtil.parse("1997-02-01 00:00:00 UTC")); + + /* + * Document Signature Hash Algorithms + */ + // SHA-3 + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA3_512); + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA3_256); + // SHA-2 + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA512); + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA384); + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA256); + acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA224); /* * Symmetric Key Algorithms @@ -70,18 +83,30 @@ public OpenPGPDefaultPolicy() public OpenPGPDefaultPolicy rejectHashAlgorithm(int hashAlgorithmId) { - hashAlgorithmCutoffDates.remove(hashAlgorithmId); + certificateHashAlgorithmCutoffDates.remove(hashAlgorithmId); + documentHashAlgorithmCutoffDates.remove(hashAlgorithmId); + return this; + } + + public OpenPGPDefaultPolicy acceptCertificationSignatureHashAlgorithm(int hashAlgorithmId) + { + return acceptCertificationSignatureHashAlgorithmUntil(hashAlgorithmId, null); + } + + public OpenPGPDefaultPolicy acceptCertificationSignatureHashAlgorithmUntil(int hashAlgorithmId, Date until) + { + certificateHashAlgorithmCutoffDates.put(hashAlgorithmId, until); return this; } - public OpenPGPDefaultPolicy acceptHashAlgorithm(int hashAlgorithmId) + public OpenPGPDefaultPolicy acceptDocumentSignatureHashAlgorithm(int hashAlgorithmId) { - return acceptHashAlgorithmUntil(hashAlgorithmId, null); + return acceptDocumentSignatureHashAlgorithmUntil(hashAlgorithmId, null); } - public OpenPGPDefaultPolicy acceptHashAlgorithmUntil(int hashAlgorithmId, Date until) + public OpenPGPDefaultPolicy acceptDocumentSignatureHashAlgorithmUntil(int hashAlgorithmId, Date until) { - hashAlgorithmCutoffDates.put(hashAlgorithmId, until); + documentHashAlgorithmCutoffDates.put(hashAlgorithmId, until); return this; } @@ -123,19 +148,19 @@ public OpenPGPDefaultPolicy acceptPublicKeyAlgorithmWithMinimalStrength(int publ @Override public boolean isAcceptableDocumentSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) { - return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + return isAcceptable(hashAlgorithmId, signatureCreationTime, documentHashAlgorithmCutoffDates); } @Override public boolean isAcceptableRevocationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) { - return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + return isAcceptable(hashAlgorithmId, signatureCreationTime, certificateHashAlgorithmCutoffDates); } @Override public boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime) { - return isAcceptable(hashAlgorithmId, signatureCreationTime, hashAlgorithmCutoffDates); + return isAcceptable(hashAlgorithmId, signatureCreationTime, certificateHashAlgorithmCutoffDates); } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java index b5ae37dbdd..08d1deedc5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java @@ -5,6 +5,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureException; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; @@ -116,10 +117,24 @@ public List verify(InputStream inputS { exceptionCallback.onException(e); } + continue; } OpenPGPSignature.OpenPGPDocumentSignature sig = new OpenPGPSignature.OpenPGPDocumentSignature(signature, signingKey); + try + { + sig.sanitize(signingKey, implementation.policy()); + } + catch (PGPSignatureException e) + { + if (exceptionCallback != null) + { + exceptionCallback.onException(e); + } + continue; + } + if (!sig.createdInBounds(verifyNotBefore, verifyNotAfter)) { continue; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java index 7053b6b66f..d07649ca83 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageInputStream.java @@ -15,6 +15,7 @@ import org.bouncycastle.openpgp.PGPPadding; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureException; import org.bouncycastle.openpgp.PGPSignatureList; import java.io.IOException; @@ -648,22 +649,23 @@ List verify( continue; } - if (!policy.isAcceptablePublicKey(key.getPGPPublicKey())) + OpenPGPSignature.OpenPGPDocumentSignature dataSignature = + new OpenPGPSignature.OpenPGPDocumentSignature(signature, key); + try { - continue; + dataSignature.sanitize(key, policy); } - if (!policy.isAcceptableSignature(signature)) + catch (PGPSignatureException e) { - continue; + // continue } - OpenPGPSignature.OpenPGPDocumentSignature dataSignature = - new OpenPGPSignature.OpenPGPDocumentSignature(signature, key); if (!dataSignature.createdInBounds(processor.getVerifyNotBefore(), processor.getVerifyNotAfter())) { // sig is not in bounds continue; } + try { dataSignature.verify(ops); @@ -747,15 +749,17 @@ void update(byte[] buf, int off, int len) List verify(OpenPGPMessageProcessor processor) { + List verifiedSignatures = new ArrayList<>(); OpenPGPPolicy policy = processor.getImplementation().policy(); for (OpenPGPSignature.OpenPGPDocumentSignature sig : dataSignatures) { - if (!policy.isAcceptablePublicKey(sig.getIssuer().getPGPPublicKey())) + try { - continue; + sig.sanitize(sig.issuer, policy); } - if (!policy.isAcceptableSignature(sig.signature)) + catch (PGPSignatureException e) { + processor.onException(e); continue; } @@ -767,8 +771,9 @@ List verify(OpenPGPMessageProcessor p { processor.onException(e); } + verifiedSignatures.add(sig); } - return dataSignatures; + return verifiedSignatures; } } } From 896f642a36d7bf2f4b8ad21baef2dccfed1f5fee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jan 2025 15:57:23 +0100 Subject: [PATCH 056/154] Do not sign with weak algorithms --- .../openpgp/api/OpenPGPDefaultPolicy.java | 42 +++++++++++++++++++ .../OpenPGPDetachedSignatureGenerator.java | 23 ++++++++-- .../openpgp/api/OpenPGPMessageGenerator.java | 2 +- .../openpgp/api/OpenPGPPolicy.java | 6 +++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java index 4dd1a0f739..db07b189e6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java @@ -16,12 +16,16 @@ public class OpenPGPDefaultPolicy private final Map certificateHashAlgorithmCutoffDates = new HashMap<>(); private final Map symmetricKeyAlgorithmCutoffDates = new HashMap<>(); private final Map publicKeyMinimalBitStrengths = new HashMap<>(); + private int defaultDocumentSignatureHashAlgorithm = HashAlgorithmTags.SHA512; + private int defaultCertificationSignatureHashAlgorithm = HashAlgorithmTags.SHA512; + private int defaultSymmetricKeyAlgorithm = SymmetricKeyAlgorithmTags.AES_128; public OpenPGPDefaultPolicy() { /* * Certification Signature Hash Algorithms */ + setDefaultCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA512); // SHA-3 acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA3_512); acceptCertificationSignatureHashAlgorithm(HashAlgorithmTags.SHA3_256); @@ -39,6 +43,7 @@ public OpenPGPDefaultPolicy() /* * Document Signature Hash Algorithms */ + setDefaultDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA512); // SHA-3 acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA3_512); acceptDocumentSignatureHashAlgorithm(HashAlgorithmTags.SHA3_256); @@ -51,6 +56,7 @@ public OpenPGPDefaultPolicy() /* * Symmetric Key Algorithms */ + setDefaultSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_128); acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_256); acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_192); acceptSymmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_128); @@ -163,12 +169,48 @@ public boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithm return isAcceptable(hashAlgorithmId, signatureCreationTime, certificateHashAlgorithmCutoffDates); } + @Override + public int getDefaultCertificationSignatureHashAlgorithm() + { + return defaultCertificationSignatureHashAlgorithm; + } + + public OpenPGPDefaultPolicy setDefaultCertificationSignatureHashAlgorithm(int hashAlgorithmId) + { + defaultCertificationSignatureHashAlgorithm = hashAlgorithmId; + return this; + } + + @Override + public int getDefaultDocumentSignatureHashAlgorithm() + { + return defaultDocumentSignatureHashAlgorithm; + } + + public OpenPGPDefaultPolicy setDefaultDocumentSignatureHashAlgorithm(int hashAlgorithmId) + { + defaultDocumentSignatureHashAlgorithm = hashAlgorithmId; + return this; + } + @Override public boolean isAcceptableSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId) { return isAcceptable(symmetricKeyAlgorithmId, symmetricKeyAlgorithmCutoffDates); } + @Override + public int getDefaultSymmetricKeyAlgorithm() + { + return defaultSymmetricKeyAlgorithm; + } + + public OpenPGPDefaultPolicy setDefaultSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId) + { + defaultSymmetricKeyAlgorithm = symmetricKeyAlgorithmId; + return this; + } + @Override public boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitStrength) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index 45793ce60a..fbab54cae1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -1,6 +1,5 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignature; @@ -10,11 +9,14 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; import java.util.List; public class OpenPGPDetachedSignatureGenerator { private final OpenPGPImplementation implementation; + private final OpenPGPPolicy policy; private int signatureType = PGPSignature.BINARY_DOCUMENT; private final List signatureGenerators = new ArrayList<>(); @@ -26,8 +28,14 @@ public OpenPGPDetachedSignatureGenerator() } public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; + this.policy = policy; } public OpenPGPDetachedSignatureGenerator setBinarySignature() @@ -68,11 +76,18 @@ public OpenPGPDetachedSignatureGenerator addSigningKey(OpenPGPKey key, char[] pa private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) { PreferredAlgorithms hashPreferences = key.getHashAlgorithmPreferences(); - if (hashPreferences == null || hashPreferences.getPreferences().length == 0) + if (hashPreferences != null) { - return HashAlgorithmTags.SHA512; + int[] pref = Arrays.stream(hashPreferences.getPreferences()) + .filter(it -> policy.isAcceptableDocumentSignatureHashAlgorithm(it, new Date())) + .toArray(); + if (pref.length != 0) + { + return pref[0]; + } } - return hashPreferences.getPreferences()[0]; + + return policy.getDefaultDocumentSignatureHashAlgorithm(); } public List sign(InputStream inputStream) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 730a8eaee2..631de6f053 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -554,7 +554,7 @@ else if (configuration.recipients.isEmpty()) } } - return HashAlgorithmTags.SHA512; + return policy.getDefaultDocumentSignatureHashAlgorithm(); }; /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java index 3b3a740736..1e3b199a47 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java @@ -139,8 +139,14 @@ default boolean isKnownSignatureSubpacket(int signatureSubpacketTag) boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + int getDefaultCertificationSignatureHashAlgorithm(); + + int getDefaultDocumentSignatureHashAlgorithm(); + boolean isAcceptableSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId); + int getDefaultSymmetricKeyAlgorithm(); + boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitStrength); OpenPGPNotationRegistry getNotationRegistry(); From 2d22d5aef3da98d813b4af1f9d6203aa624f7db2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 13:16:05 +0100 Subject: [PATCH 057/154] Add javadoc to OpenPGPDetachedSignatureGenerator --- .../OpenPGPDetachedSignatureGenerator.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index fbab54cae1..bf2a85e3c2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -13,6 +13,18 @@ import java.util.Date; import java.util.List; +/** + * High-Level OpenPGP Signature Generator for Detached Signatures. + * Detached signatures can be stored and distributed as a distinct object alongside the signed data. + * They are used for example to sign Release files of some Linux software distributions. + *

+ * To use this class, instantiate it, optionally providing a concrete {@link OpenPGPImplementation} and + * {@link OpenPGPPolicy} for algorithm policing. + * Then, add the desired {@link OpenPGPKey} you want to use for signing the data via one or more + * calls to {@link #addSigningKey(OpenPGPKey, char[])}. + * Lastly, retrieve a list of detached {@link OpenPGPSignature.OpenPGPDocumentSignature signatures} by calling + * {@link #sign(InputStream)}, passing in an {@link InputStream} containing the data you want to sign. + */ public class OpenPGPDetachedSignatureGenerator { private final OpenPGPImplementation implementation; @@ -22,34 +34,77 @@ public class OpenPGPDetachedSignatureGenerator private final List signatureGenerators = new ArrayList<>(); private final List signingKeys = new ArrayList<>(); + /** + * Instantiate a signature generator using the default {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. + */ public OpenPGPDetachedSignatureGenerator() { this(OpenPGPImplementation.getInstance()); } + /** + * Instantiate a signature generator using the passed in {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. + * + * @param implementation custom OpenPGP implementation + */ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation) { this(implementation, implementation.policy()); } + /** + * Instantiate a signature generator using a custom {@link OpenPGPImplementation} and custom {@link OpenPGPPolicy}. + * + * @param implementation custom OpenPGP implementation + * @param policy custom OpenPGP policy + */ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; this.policy = policy; } + /** + * Set the type of generated signatures to {@link PGPSignature#BINARY_DOCUMENT}. + * Binary signatures are calculated over the plaintext as is. + * Binary signatures are the default. + * + * @return this + */ public OpenPGPDetachedSignatureGenerator setBinarySignature() { this.signatureType = PGPSignature.BINARY_DOCUMENT; return this; } + /** + * Set the type of generated signatures to {@link PGPSignature#CANONICAL_TEXT_DOCUMENT}. + * Text signatures are calculated over modified plaintext, which is first transformed by canonicalizing + * line endings to CR-LF (

0x0D0A
). + * This is useful, if the plaintext is transported via a channel that may not retain the original message + * encoding. + * + * @return this + */ public OpenPGPDetachedSignatureGenerator setCanonicalTextDocument() { this.signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; return this; } + /** + * Add an {@link OpenPGPKey} as signing key. + * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, + * this method will throw an {@link InvalidSigningKeyException}. + * Otherwise, all capable signing subkeys will be used to create detached signatures. + * + * @param key OpenPGP key + * @param passphrase passphrase to unlock the signing key + * @return this + * + * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey + * @throws PGPException if signing fails + */ public OpenPGPDetachedSignatureGenerator addSigningKey(OpenPGPKey key, char[] passphrase) throws PGPException { @@ -90,6 +145,16 @@ private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key return policy.getDefaultDocumentSignatureHashAlgorithm(); } + /** + * Pass in an {@link InputStream} containing the data that shall be signed and return a list of detached + * signatures. + * + * @param inputStream data to be signed + * @return detached signatures + * + * @throws IOException if something goes wrong processing the data + * @throws PGPException if signing fails + */ public List sign(InputStream inputStream) throws IOException, PGPException { From 0c1575eaceec92e62cfac047ad36d0805ed406f7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 14:00:22 +0100 Subject: [PATCH 058/154] Add javadoc to OpenPGPDetachedSignatureProcessor --- .../OpenPGPDetachedSignatureProcessor.java | 136 ++++++++++++++++-- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java index 08d1deedc5..3e97aea9a4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureProcessor.java @@ -15,10 +15,29 @@ import java.util.Date; import java.util.List; +/** + * High-Level Processor for Messages Signed Using Detached OpenPGP Signatures. + *

+ * To use this class, first instantiate the processor, optionally passing in a concrete + * {@link OpenPGPImplementation} and {@link OpenPGPPolicy}. + * Then, pass in any detached signatures you want to verify using {@link #addSignatures(InputStream)}. + * Next, provide the expected issuers {@link OpenPGPCertificate OpenPGPCertificates} for signature + * verification using {@link #addVerificationCertificate(OpenPGPCertificate)}. + * Signatures for which no certificate was provided, and certificates for which no signature was added, + * are ignored. + * Optionally, you can specify a validity date range for the signatures using + * {@link #verifyNotBefore(Date)} and {@link #verifyNotAfter(Date)}. + * Signatures outside this range will be ignored as invalid. + * Lastly, provide an {@link InputStream} containing the original plaintext data, over which you want to + * verify the detached signatures using {@link #process(InputStream)}. + * As a result you will receive a list containing all processed + * {@link OpenPGPSignature.OpenPGPDocumentSignature OpenPGPDocumentSignatures}. + * For these, you can check validity by calling {@link OpenPGPSignature.OpenPGPDocumentSignature#isValid()}. + */ public class OpenPGPDetachedSignatureProcessor { - private final OpenPGPImplementation implementation; + private final OpenPGPPolicy policy; private final OpenPGPKeyMaterialPool.OpenPGPCertificatePool certificatePool = new OpenPGPKeyMaterialPool.OpenPGPCertificatePool(); private final List pgpSignatures = new ArrayList<>(); private Date verifyNotAfter = new Date(); // now @@ -26,16 +45,44 @@ public class OpenPGPDetachedSignatureProcessor private OpenPGPMessageProcessor.PGPExceptionCallback exceptionCallback = null; + /** + * Instantiate a signature processor using the default {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. + */ public OpenPGPDetachedSignatureProcessor() { this(OpenPGPImplementation.getInstance()); } + /** + * Instantiate a signature processor using a custom {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. + * + * @param implementation custom OpenPGP implementation + */ public OpenPGPDetachedSignatureProcessor(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + /** + * Instantiate a signature processor using a custom {@link OpenPGPImplementation} and custom {@link OpenPGPPolicy}. + * + * @param implementation custom OpenPGP implementation + * @param policy custom OpenPGP policy + */ + public OpenPGPDetachedSignatureProcessor(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; + this.policy = policy; } + /** + * Read one or more {@link PGPSignature detached signatures} from the provided {@link InputStream} and + * add them to the processor. + * + * @param inputStream input stream of armored or unarmored detached OpenPGP signatures + * @return this + * @throws IOException if something goes wrong reading from the stream + */ public OpenPGPDetachedSignatureProcessor addSignatures(InputStream inputStream) throws IOException { @@ -47,40 +94,94 @@ public OpenPGPDetachedSignatureProcessor addSignatures(InputStream inputStream) { if (next instanceof PGPSignatureList) { - PGPSignatureList signatureList = (PGPSignatureList) next; - for (PGPSignature signature : signatureList) - { - pgpSignatures.add(signature); - } + addSignatures((PGPSignatureList) next); } else if (next instanceof PGPSignature) { - PGPSignature signature = (PGPSignature) next; - pgpSignatures.add(signature); + addSignature((PGPSignature) next); } } return this; } + /** + * Add one or more {@link PGPSignature detached signatures} from the given {@link PGPSignatureList} to the + * processor. + * + * @param signatures detached signature list + * @return this + */ + public OpenPGPDetachedSignatureProcessor addSignatures(PGPSignatureList signatures) + { + for (PGPSignature signature : signatures) + { + addSignature(signature); + } + return this; + } + + /** + * Add a single {@link PGPSignature detached signature} to the processor. + * + * @param signature detached signature + * @return this + */ + public OpenPGPDetachedSignatureProcessor addSignature(PGPSignature signature) + { + pgpSignatures.add(signature); + return this; + } + + /** + * Add an issuers {@link OpenPGPCertificate} for signature verification. + * + * @param certificate OpenPGP certificate + * @return this + */ public OpenPGPDetachedSignatureProcessor addVerificationCertificate(OpenPGPCertificate certificate) { this.certificatePool.addItem(certificate); return this; } + /** + * Reject detached signatures made before

date
. + * By default, this value is set to the beginning of time. + * + * @param date date + * @return this + */ public OpenPGPDetachedSignatureProcessor verifyNotBefore(Date date) { this.verifyNotBefore = date; return this; } + /** + * Reject detached signatures made after the given
date
. + * By default, this value is set to the current time at instantiation time, in order to prevent + * verification of signatures from the future. + * + * @param date date + * @return this + */ public OpenPGPDetachedSignatureProcessor verifyNotAfter(Date date) { this.verifyNotAfter = date; return this; } - public List verify(InputStream inputStream) + /** + * Process the plaintext data from the given {@link InputStream} and return a list of processed + * detached signatures. + * Note: This list will NOT contain any malformed signatures, or signatures for which no verification key was found. + * Correctness of these signatures can be checked via {@link OpenPGPSignature.OpenPGPDocumentSignature#isValid()}. + * + * @param inputStream data over which the detached signatures are calculated + * @return list of processed detached signatures + * @throws IOException if the data cannot be processed + */ + public List process(InputStream inputStream) throws IOException { List documentSignatures = new ArrayList<>(); @@ -91,18 +192,21 @@ public List verify(InputStream inputS KeyIdentifier identifier = OpenPGPSignature.getMostExpressiveIdentifier(signature.getKeyIdentifiers()); if (identifier == null) { + // Missing issuer -> ignore sig continue; } OpenPGPCertificate certificate = certificatePool.provide(identifier); if (certificate == null) { + // missing cert -> ignore sig continue; } OpenPGPCertificate.OpenPGPComponentKey signingKey = certificate.getKey(identifier); if (signingKey == null) { + // unbound signing subkey -> ignore sig continue; } @@ -124,7 +228,8 @@ public List verify(InputStream inputS new OpenPGPSignature.OpenPGPDocumentSignature(signature, signingKey); try { - sig.sanitize(signingKey, implementation.policy()); + // sanitize signature (required subpackets, check algorithm policy...) + sig.sanitize(signingKey, policy); } catch (PGPSignatureException e) { @@ -135,10 +240,13 @@ public List verify(InputStream inputS continue; } + // check allowed date range if (!sig.createdInBounds(verifyNotBefore, verifyNotAfter)) { continue; } + + // sig qualifies for further processing :) documentSignatures.add(sig); } @@ -158,6 +266,7 @@ public List verify(InputStream inputS { try { + // verify the signature. Correctness can be checked via sig.verify(); } catch (PGPException e) @@ -172,6 +281,13 @@ public List verify(InputStream inputS return documentSignatures; } + /** + * Add a callback to which any OpenPGP-related exceptions are forwarded. + * Useful for debugging purposes. + * + * @param callback callback + * @return this + */ public OpenPGPDetachedSignatureProcessor setExceptionCallback(OpenPGPMessageProcessor.PGPExceptionCallback callback) { this.exceptionCallback = callback; From a80072d91a923019a28530172a730e5cb0b5ff8e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 15:23:25 +0100 Subject: [PATCH 059/154] OpenPGPCertificate, OpenPGPKey: Allow passing in custom OpenPGPPolicy --- .../openpgp/api/OpenPGPCertificate.java | 71 ++++++++++++++++--- .../bouncycastle/openpgp/api/OpenPGPKey.java | 52 ++++++++++++-- 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index b0dcc4d716..f075f219ba 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -68,6 +68,7 @@ public class OpenPGPCertificate { final OpenPGPImplementation implementation; + final OpenPGPPolicy policy; private final PGPKeyRing keyRing; @@ -78,20 +79,41 @@ public class OpenPGPCertificate // proper functionality with secret key components. private final Map componentSignatureChains; + /** + * Instantiate an {@link OpenPGPCertificate} from a parksed {@link PGPKeyRing} using the default + * {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. + * + * @param keyRing key ring + */ public OpenPGPCertificate(PGPKeyRing keyRing) { this(keyRing, OpenPGPImplementation.getInstance()); } /** - * Instantiate an {@link OpenPGPCertificate} from a parsed {@link PGPPublicKeyRing}. + * Instantiate an {@link OpenPGPCertificate} from a parsed {@link PGPKeyRing} + * using the provided {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. * * @param keyRing public key ring * @param implementation OpenPGP implementation */ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementation) + { + this(keyRing, implementation, implementation.policy()); + } + + /** + * Instantiate an {@link OpenPGPCertificate} from a parsed {@link PGPKeyRing} + * using the provided {@link OpenPGPImplementation} and provided {@link OpenPGPPolicy}. + * + * @param keyRing public key ring + * @param implementation OpenPGP implementation + * @param policy OpenPGP policy + */ + public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; + this.policy = policy; this.keyRing = keyRing; this.subkeys = new HashMap<>(); @@ -136,9 +158,18 @@ public static OpenPGPCertificate fromAsciiArmor( OpenPGPImplementation implementation) throws IOException { - return fromBytes( - armor.getBytes(StandardCharsets.UTF_8), - implementation); + return fromAsciiArmor(armor, implementation, implementation.policy()); + } + + public static OpenPGPCertificate fromAsciiArmor( + String armor, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) + throws IOException + { + return fromBytes(armor.getBytes(StandardCharsets.UTF_8), + implementation, + policy); } public static OpenPGPCertificate fromInputStream(InputStream inputStream) @@ -147,11 +178,20 @@ public static OpenPGPCertificate fromInputStream(InputStream inputStream) return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); } - public static OpenPGPCertificate fromInputStream(InputStream inputStream, OpenPGPImplementation implementation) + public static OpenPGPCertificate fromInputStream(InputStream inputStream, + OpenPGPImplementation implementation) + throws IOException + { + return fromInputStream(inputStream, implementation, implementation.policy()); + } + + public static OpenPGPCertificate fromInputStream(InputStream inputStream, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) throws IOException { byte[] bytes = Streams.readAll(inputStream); - return fromBytes(bytes, implementation); + return fromBytes(bytes, implementation, policy); } public static OpenPGPCertificate fromBytes(byte[] bytes) @@ -164,6 +204,15 @@ public static OpenPGPCertificate fromBytes( byte[] bytes, OpenPGPImplementation implementation) throws IOException + { + return fromBytes(bytes, implementation, implementation.policy()); + } + + public static OpenPGPCertificate fromBytes( + byte[] bytes, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) + throws IOException { ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); InputStream decoderStream = PGPUtil.getDecoderStream(bIn); @@ -177,11 +226,11 @@ public static OpenPGPCertificate fromBytes( if (object instanceof PGPSecretKeyRing) { - return new OpenPGPKey((PGPSecretKeyRing) object, implementation); + return new OpenPGPKey((PGPSecretKeyRing) object, implementation, policy); } else if (object instanceof PGPPublicKeyRing) { - return new OpenPGPCertificate((PGPPublicKeyRing) object, implementation); + return new OpenPGPCertificate((PGPPublicKeyRing) object, implementation, policy); } else { @@ -557,7 +606,7 @@ private boolean isBoundBy(OpenPGPCertificateComponent component, } // Chain needs to be valid (signatures correct) - if (chain.isValid(implementation.pgpContentVerifierBuilderProvider(), implementation.policy())) + if (chain.isValid(implementation.pgpContentVerifierBuilderProvider(), policy)) { // Chain needs to not contain a revocation signature, otherwise the component is considered revoked return !chain.isRevocation(); @@ -1871,8 +1920,8 @@ public boolean isEffectiveAt(Date evaluationDate) public boolean isValid() throws PGPSignatureException { - return isValid(getRootKey().getCertificate().implementation.pgpContentVerifierBuilderProvider(), - getRootKey().getCertificate().implementation.policy()); + OpenPGPCertificate cert = getRootKey().getCertificate(); + return isValid(cert.implementation.pgpContentVerifierBuilderProvider(), cert.policy); } public boolean isValid(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, OpenPGPPolicy policy) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 861e1aca56..5eeab7d9e7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -48,14 +48,28 @@ public OpenPGPKey(PGPSecretKeyRing keyRing) } /** - * Create an {@link OpenPGPKey} instance based on a {@link PGPSecretKeyRing}. + * Create an {@link OpenPGPKey} instance based on a {@link PGPSecretKeyRing}, + * a provided {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. * * @param keyRing secret key ring * @param implementation OpenPGP implementation */ public OpenPGPKey(PGPSecretKeyRing keyRing, OpenPGPImplementation implementation) { - super(keyRing, implementation); + this(keyRing, implementation, implementation.policy()); + } + + /** + * Create an {@link OpenPGPKey} instance based on a {@link PGPSecretKeyRing}, + * a provided {@link OpenPGPImplementation} and {@link OpenPGPPolicy}. + * + * @param keyRing secret key ring + * @param implementation OpenPGP implementation + * @param policy OpenPGP policy + */ + public OpenPGPKey(PGPSecretKeyRing keyRing, OpenPGPImplementation implementation, OpenPGPPolicy policy) + { + super(keyRing, implementation, policy); // Process and map secret keys this.secretKeys = new HashMap<>(); @@ -107,10 +121,20 @@ public static OpenPGPKey fromAsciiArmor( String armor, OpenPGPImplementation implementation) throws IOException + { + return fromAsciiArmor(armor, implementation, implementation.policy()); + } + + public static OpenPGPKey fromAsciiArmor( + String armor, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) + throws IOException { return fromBytes( armor.getBytes(StandardCharsets.UTF_8), - implementation); + implementation, + policy); } public static OpenPGPKey fromInputStream(InputStream inputStream) @@ -119,7 +143,16 @@ public static OpenPGPKey fromInputStream(InputStream inputStream) return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); } - public static OpenPGPKey fromInputStream(InputStream inputStream, OpenPGPImplementation implementation) + public static OpenPGPKey fromInputStream(InputStream inputStream, + OpenPGPImplementation implementation) + throws IOException + { + return fromInputStream(inputStream, implementation, implementation.policy()); + } + + public static OpenPGPKey fromInputStream(InputStream inputStream, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) throws IOException { return fromBytes(Streams.readAll(inputStream), implementation); @@ -136,6 +169,15 @@ public static OpenPGPKey fromBytes( byte[] bytes, OpenPGPImplementation implementation) throws IOException + { + return fromBytes(bytes, implementation, implementation.policy()); + } + + public static OpenPGPKey fromBytes( + byte[] bytes, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) + throws IOException { ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); InputStream decoderStream = PGPUtil.getDecoderStream(bIn); @@ -149,7 +191,7 @@ public static OpenPGPKey fromBytes( } PGPSecretKeyRing keyRing = (PGPSecretKeyRing) object; - return new OpenPGPKey(keyRing, implementation); + return new OpenPGPKey(keyRing, implementation, policy); } public OpenPGPSecretKey getPrimarySecretKey() From 7dc743eb949ff5cdce7a86eb578a470642e67c0f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 16:10:19 +0100 Subject: [PATCH 060/154] OpenPGPCertificate, OpenPGPKey: Replace factory methods with OpenPGPKeyReader class --- .../openpgp/api/OpenPGPCertificate.java | 106 -------------- .../bouncycastle/openpgp/api/OpenPGPKey.java | 90 ------------ .../openpgp/api/OpenPGPKeyReader.java | 137 ++++++++++++++++++ .../openpgp/api/util/DebugPrinter.java | 5 +- .../openpgp/api/test/HardRevocationTest.java | 116 --------------- .../api/test/OpenPGPCertificateTest.java | 7 +- .../api/test/OpenPGPMessageGeneratorTest.java | 9 +- .../api/test/OpenPGPMessageProcessorTest.java | 63 ++++---- .../StaticV6OpenPGPMessageGeneratorTest.java | 7 +- 9 files changed, 189 insertions(+), 351 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java delete mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index f075f219ba..ac8538fe3c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -31,13 +31,11 @@ import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.util.io.Streams; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; @@ -134,110 +132,6 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati } } - /** - * Parse an {@link OpenPGPCertificate} (or {@link OpenPGPKey}) from its ASCII armored representation. - * @param armor ASCII armored key or certificate - * @return certificate or key - * @throws IOException - */ - public static OpenPGPCertificate fromAsciiArmor(String armor) - throws IOException - { - return fromAsciiArmor(armor, OpenPGPImplementation.getInstance()); - } - - /** - * Parse an {@link OpenPGPCertificate} (or {@link OpenPGPKey}) from its ASCII armored representation. - * @param armor ASCII armored key or certificate - * @param implementation OpenPGP implementation - * @return certificate or key - * @throws IOException - */ - public static OpenPGPCertificate fromAsciiArmor( - String armor, - OpenPGPImplementation implementation) - throws IOException - { - return fromAsciiArmor(armor, implementation, implementation.policy()); - } - - public static OpenPGPCertificate fromAsciiArmor( - String armor, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - return fromBytes(armor.getBytes(StandardCharsets.UTF_8), - implementation, - policy); - } - - public static OpenPGPCertificate fromInputStream(InputStream inputStream) - throws IOException - { - return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); - } - - public static OpenPGPCertificate fromInputStream(InputStream inputStream, - OpenPGPImplementation implementation) - throws IOException - { - return fromInputStream(inputStream, implementation, implementation.policy()); - } - - public static OpenPGPCertificate fromInputStream(InputStream inputStream, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - byte[] bytes = Streams.readAll(inputStream); - return fromBytes(bytes, implementation, policy); - } - - public static OpenPGPCertificate fromBytes(byte[] bytes) - throws IOException - { - return fromBytes(bytes, OpenPGPImplementation.getInstance()); - } - - public static OpenPGPCertificate fromBytes( - byte[] bytes, - OpenPGPImplementation implementation) - throws IOException - { - return fromBytes(bytes, implementation, implementation.policy()); - } - - public static OpenPGPCertificate fromBytes( - byte[] bytes, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); - InputStream decoderStream = PGPUtil.getDecoderStream(bIn); - BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); - PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); - Object object = objectFactory.nextObject(); - - // TODO: Is it dangerous, if we don't explicitly fail upon encountering secret key material here? - // Could it lead to a situation where we need to be cautious with the certificate API design to - // prevent the user from doing dangerous things like accidentally publishing their private key? - - if (object instanceof PGPSecretKeyRing) - { - return new OpenPGPKey((PGPSecretKeyRing) object, implementation, policy); - } - else if (object instanceof PGPPublicKeyRing) - { - return new OpenPGPCertificate((PGPPublicKeyRing) object, implementation, policy); - } - else - { - throw new IOException("Neither a certificate, nor secret key."); - } - } - /** * Return the primary key of the certificate. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 5eeab7d9e7..0a4ed4db9e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -1,26 +1,19 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; -import org.bouncycastle.util.io.Streams; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -111,89 +104,6 @@ public List getComponents() return components; } - public static OpenPGPKey fromAsciiArmor(String armor) - throws IOException - { - return fromAsciiArmor(armor, OpenPGPImplementation.getInstance()); - } - - public static OpenPGPKey fromAsciiArmor( - String armor, - OpenPGPImplementation implementation) - throws IOException - { - return fromAsciiArmor(armor, implementation, implementation.policy()); - } - - public static OpenPGPKey fromAsciiArmor( - String armor, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - return fromBytes( - armor.getBytes(StandardCharsets.UTF_8), - implementation, - policy); - } - - public static OpenPGPKey fromInputStream(InputStream inputStream) - throws IOException - { - return fromInputStream(inputStream, OpenPGPImplementation.getInstance()); - } - - public static OpenPGPKey fromInputStream(InputStream inputStream, - OpenPGPImplementation implementation) - throws IOException - { - return fromInputStream(inputStream, implementation, implementation.policy()); - } - - public static OpenPGPKey fromInputStream(InputStream inputStream, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - return fromBytes(Streams.readAll(inputStream), implementation); - } - - public static OpenPGPKey fromBytes( - byte[] bytes) - throws IOException - { - return fromBytes(bytes, OpenPGPImplementation.getInstance()); - } - - public static OpenPGPKey fromBytes( - byte[] bytes, - OpenPGPImplementation implementation) - throws IOException - { - return fromBytes(bytes, implementation, implementation.policy()); - } - - public static OpenPGPKey fromBytes( - byte[] bytes, - OpenPGPImplementation implementation, - OpenPGPPolicy policy) - throws IOException - { - ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); - InputStream decoderStream = PGPUtil.getDecoderStream(bIn); - BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); - PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); - - Object object = objectFactory.nextObject(); - if (!(object instanceof PGPSecretKeyRing)) - { - throw new IOException("Not a secret key."); - } - - PGPSecretKeyRing keyRing = (PGPSecretKeyRing) object; - return new OpenPGPKey(keyRing, implementation, policy); - } - public OpenPGPSecretKey getPrimarySecretKey() { return getSecretKey(getPrimaryKey()); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java new file mode 100644 index 0000000000..0be9039f80 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -0,0 +1,137 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.util.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class OpenPGPKeyReader +{ + private final OpenPGPImplementation implementation; + private final OpenPGPPolicy policy; + + public OpenPGPKeyReader() + { + this(OpenPGPImplementation.getInstance()); + } + + public OpenPGPKeyReader(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + public OpenPGPKeyReader(OpenPGPImplementation implementation, OpenPGPPolicy policy) + { + this.implementation = implementation; + this.policy = policy; + } + + public OpenPGPCertificate parseCertificate(String armored) + throws IOException + { + OpenPGPCertificate certificate = parseCertificateOrKey(armored); + if (certificate instanceof OpenPGPKey) + { + throw new IOException("Could not parse OpenPGPCertificate: Is OpenPGPKey."); + } + return certificate; + } + + public OpenPGPCertificate parseCertificate(InputStream inputStream) + throws IOException + { + OpenPGPCertificate certificate = parseCertificateOrKey(inputStream); + if (certificate instanceof OpenPGPKey) + { + throw new IOException("Could not parse OpenPGPCertificate: Is OpenPGPKey."); + } + return certificate; + } + + public OpenPGPCertificate parseCertificate(byte[] bytes) + throws IOException + { + OpenPGPCertificate certificate = parseCertificateOrKey(bytes); + if (certificate instanceof OpenPGPKey) + { + throw new IOException("Could not parse OpenPGPCertificate: Is OpenPGPKey."); + } + return certificate; + } + + public OpenPGPCertificate parseCertificateOrKey(String armored) + throws IOException + { + return parseCertificateOrKey(armored.getBytes(StandardCharsets.UTF_8)); + } + + public OpenPGPCertificate parseCertificateOrKey(InputStream inputStream) + throws IOException + { + return parseCertificateOrKey(Streams.readAll(inputStream)); + } + + public OpenPGPCertificate parseCertificateOrKey(byte[] bytes) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + Object object = objectFactory.nextObject(); + + // TODO: Is it dangerous, if we don't explicitly fail upon encountering secret key material here? + // Could it lead to a situation where we need to be cautious with the certificate API design to + // prevent the user from doing dangerous things like accidentally publishing their private key? + + if (object instanceof PGPSecretKeyRing) + { + return new OpenPGPKey((PGPSecretKeyRing) object, implementation, policy); + } + else if (object instanceof PGPPublicKeyRing) + { + return new OpenPGPCertificate((PGPPublicKeyRing) object, implementation, policy); + } + else + { + throw new IOException("Neither a certificate, nor secret key."); + } + } + + public OpenPGPKey parseKey(String armored) + throws IOException + { + return parseKey(armored.getBytes(StandardCharsets.UTF_8)); + } + + public OpenPGPKey parseKey(InputStream inputStream) + throws IOException + { + return parseKey(Streams.readAll(inputStream)); + } + + public OpenPGPKey parseKey(byte[] bytes) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + + Object object = objectFactory.nextObject(); + if (!(object instanceof PGPSecretKeyRing)) + { + throw new IOException("Not a secret key."); + } + + PGPSecretKeyRing keyRing = (PGPSecretKeyRing) object; + return new OpenPGPKey(keyRing, implementation, policy); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java index 2bf7acac82..97c9a8fea8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java @@ -3,6 +3,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPDefaultPolicy; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; import java.io.IOException; @@ -111,8 +112,8 @@ public class DebugPrinter public static void main(String[] args) throws IOException { - - OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(v6SecretKey); + OpenPGPKeyReader reader = new OpenPGPKeyReader(); + OpenPGPCertificate certificate = reader.parseCertificate(v6SecretKey); // -DM System.out.println System.out.println(toString(certificate, new Date())); } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java deleted file mode 100644 index 1c810867c1..0000000000 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/HardRevocationTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.bouncycastle.openpgp.api.test; - -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.openpgp.api.OpenPGPCertificate; - -public class HardRevocationTest extends AbstractPacketTest -{ - - @Override - public String getName() - { - return "HardRevocationTest"; - } - - @Override - public void performTest() throws Exception - { - String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + - "\n" + - "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\n" + - "6sXYWB8=\n" + - "=13Sf\n" + - "-----END PGP PUBLIC KEY BLOCK-----\n"; - OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(CERT); - String msg = "Hello, World"; - String SIG1 = "-----BEGIN PGP SIGNATURE-----\n" + - "\n" + - "wsC7BAABCgBvBYJa564ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + - "cy5zZXF1b2lhLXBncC5vcmfM0EN4Ei0bQv6UO9BRq2wtUfV948cRynRMBb8TSGCG\n" + - "tBYhBOMsttqCApG3522xqAitUcrkcPAGAAAlNwf+L0KQK9i/xmYKOMV2EX13QUoZ\n" + - "vvb/pHGZaCQ9JtvEF2l2DT0DqByZ+tOv5Y4isU+un7CraoyvyajAwR0Yqk937B6C\n" + - "HQHKMkmIl+5R4/xqSoWYmOidbrgilojPMBEhB3INQ8/THjjFijtLzitVhnWBd7+u\n" + - "s0kcqnWnOdx2By4aDe+UEiyCfSE02e/0tIsM71RqiU91zH6dl6+q8nml7PsYuTFV\n" + - "V09oQTbBuuvUe+YgN/uvyKVIsA64lQ+YhqEeIA8Quek7fHhW+du9OIhSPsbYodyx\n" + - "VWMTXwSWKGNvZNAkpmgUYqFjS2Cx5ZUWblZLjrNKBwnnmt50qvUN7+o2pjlnfA==\n" + - "=UuXb\n" + - "-----END PGP SIGNATURE-----\n"; - - - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index 46ab89f611..bc93843460 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -15,6 +15,7 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; @@ -30,6 +31,8 @@ public class OpenPGPCertificateTest extends AbstractPacketTest { + private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); + @Override public String getName() { @@ -53,7 +56,7 @@ public void performTest() private void testOpenPGPv6Key() throws IOException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); isTrue("Test key has no identities", key.getIdentities().isEmpty()); @@ -772,7 +775,7 @@ private void testPKSignsPKRevocationSuperseded() private void signatureValidityTest(String cert, TestSignature... testSignatures) throws IOException { - OpenPGPCertificate certificate = OpenPGPCertificate.fromAsciiArmor(cert); + OpenPGPCertificate certificate = reader.parseCertificate(cert); for (TestSignature test : testSignatures) { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java index 0f512e4e15..818d301371 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -6,6 +6,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; import org.bouncycastle.util.encoders.Hex; @@ -18,6 +19,8 @@ public class OpenPGPMessageGeneratorTest extends AbstractPacketTest { + private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); + @Override public String getName() { @@ -125,7 +128,7 @@ private void unarmoredCompressedLiteralDataPacket() private void seipd2EncryptedMessage() throws IOException, PGPException { - OpenPGPCertificate cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT); + OpenPGPCertificate cert = reader.parseCertificate(OpenPGPTestKeys.V6_CERT); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); gen.addEncryptionCertificate(cert); @@ -141,7 +144,7 @@ private void seipd2EncryptedMessage() private void seipd1EncryptedMessage() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.BOB_KEY); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); gen.addEncryptionCertificate(key); @@ -157,7 +160,7 @@ private void seipd1EncryptedMessage() private void seipd2EncryptedSignedMessage() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() .setIsPadded(true) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 0c47bdcdc7..6524bf15e8 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -9,6 +9,7 @@ import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; @@ -31,6 +32,8 @@ public class OpenPGPMessageProcessorTest { private static final byte[] PLAINTEXT = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); + private PGPSessionKey encryptionSessionKey; @Override @@ -238,7 +241,7 @@ private void roundTripV4KeyEncryptedMessageAlice() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -247,7 +250,7 @@ private void roundTripV4KeyEncryptedMessageAlice() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -263,7 +266,7 @@ private void roundTripV4KeyEncryptedMessageBob() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.BOB_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -272,7 +275,7 @@ private void roundTripV4KeyEncryptedMessageBob() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY)); + processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.BOB_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -289,7 +292,7 @@ private void roundTripV4KeyEncryptedMessageCarol() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.CAROL_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.CAROL_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -298,7 +301,7 @@ private void roundTripV4KeyEncryptedMessageCarol() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.CAROL_KEY)); + processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.CAROL_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -314,7 +317,7 @@ private void roundTripV4KeyEncryptedMessageCarol() private void roundTripV6KeyEncryptedMessage() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() .setArmored(true) @@ -345,8 +348,8 @@ private void encryptWithV4V6KeyDecryptWithV4() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.V6_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -355,7 +358,7 @@ private void encryptWithV4V6KeyDecryptWithV4() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -372,8 +375,8 @@ private void encryptWithV4V6KeyDecryptWithV6() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)); - gen.addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.V6_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -382,7 +385,7 @@ private void encryptWithV4V6KeyDecryptWithV6() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY)); + .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.V6_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -397,7 +400,7 @@ private void encryptWithV4V6KeyDecryptWithV6() private void encryptDecryptWithLockedKey() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY_LOCKED); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY_LOCKED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream encOut = new OpenPGPMessageGenerator() @@ -453,7 +456,7 @@ private void encryptDecryptWithLockedKey() private void encryptDecryptWithMissingKey() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream encOut = new OpenPGPMessageGenerator() @@ -485,7 +488,7 @@ private void inlineSignWithV4KeyAlice() { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey aliceKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY); + OpenPGPKey aliceKey = reader.parseKey(OpenPGPTestKeys.ALICE_KEY); gen.addSigningKey(aliceKey); OutputStream signOut = gen.open(bOut); @@ -495,7 +498,7 @@ private void inlineSignWithV4KeyAlice() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate aliceCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT); + OpenPGPCertificate aliceCert = reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() .addVerificationCertificate(aliceCert); @@ -517,7 +520,7 @@ private void inlineSignWithV4KeyBob() { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey bobKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + OpenPGPKey bobKey = reader.parseKey(OpenPGPTestKeys.BOB_KEY); gen.addSigningKey(bobKey); OutputStream signOut = gen.open(bOut); @@ -527,7 +530,7 @@ private void inlineSignWithV4KeyBob() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate bobCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT); + OpenPGPCertificate bobCert = reader.parseCertificate(OpenPGPTestKeys.BOB_CERT); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() .addVerificationCertificate(bobCert); @@ -548,7 +551,7 @@ private void inlineSignWithV4KeyCarol() { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey carolKey = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.CAROL_KEY); + OpenPGPKey carolKey = reader.parseKey(OpenPGPTestKeys.CAROL_KEY); gen.addSigningKey(carolKey); OutputStream signOut = gen.open(bOut); @@ -558,7 +561,7 @@ private void inlineSignWithV4KeyCarol() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate carolCert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.CAROL_CERT); + OpenPGPCertificate carolCert = reader.parseCertificate(OpenPGPTestKeys.CAROL_CERT); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() .addVerificationCertificate(carolCert); @@ -579,7 +582,7 @@ private void inlineSignWithV6Key() { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey v6Key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey v6Key = reader.parseKey(OpenPGPTestKeys.V6_KEY); gen.addSigningKey(v6Key); OutputStream signOut = gen.open(bOut); @@ -589,7 +592,7 @@ private void inlineSignWithV6Key() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate v6Cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.V6_CERT); + OpenPGPCertificate v6Cert = reader.parseCertificate(OpenPGPTestKeys.V6_CERT); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() .addVerificationCertificate(v6Cert); @@ -609,7 +612,7 @@ private void verifyMessageByRevokedKey() throws PGPException, IOException { // Create a minimal signed message - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.ALICE_KEY); OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); gen.addSigningKey(key); @@ -619,7 +622,7 @@ private void verifyMessageByRevokedKey() oOut.close(); // Load the certificate and import its revocation signature - OpenPGPCertificate cert = OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT); + OpenPGPCertificate cert = reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT); cert = OpenPGPCertificate.join(cert, OpenPGPTestKeys.ALICE_REVOCATION_CERT); // Process the signed message using the revoked key @@ -640,8 +643,8 @@ private void incompleteMessageProcessing() throws IOException, PGPException { OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() - .addEncryptionCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.ALICE_CERT)) - .addSigningKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY)); + .addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)) + .addSigningKey(reader.parseKey(OpenPGPTestKeys.BOB_KEY)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream out = gen.open(bOut); @@ -650,8 +653,8 @@ private void incompleteMessageProcessing() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addVerificationCertificate(OpenPGPCertificate.fromAsciiArmor(OpenPGPTestKeys.BOB_CERT)) - .addDecryptionKey(OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.ALICE_KEY)); + .addVerificationCertificate(reader.parseCertificate(OpenPGPTestKeys.BOB_CERT)) + .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream in = processor.process(bIn); // read a single byte (not the entire message) @@ -680,7 +683,7 @@ private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() "IwBVELjaaSGpdOuIHkETYssCNfqPSv0rNmaTDq78xItvhjuc4lRaKkpF9DdE\n" + "=I5BA\n" + "-----END PGP MESSAGE-----"; - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.BOB_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.BOB_KEY); OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); processor.addDecryptionKey(key); OpenPGPMessageInputStream oIn = processor.process(new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8))); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java index be7c540e5e..92375a46bf 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java @@ -6,6 +6,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; import org.bouncycastle.openpgp.api.OpenPGPPolicy; @@ -20,6 +21,8 @@ public class StaticV6OpenPGPMessageGeneratorTest extends AbstractPacketTest { + private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); + KeyIdentifier signingKeyIdentifier = new KeyIdentifier( Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9")); KeyIdentifier encryptionKeyIdentifier = new KeyIdentifier( @@ -42,7 +45,7 @@ public void performTest() private void staticEncryptedMessage() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); OpenPGPMessageGenerator gen = getStaticGenerator() .addEncryptionCertificate(key); @@ -58,7 +61,7 @@ private void staticEncryptedMessage() private void staticSignedMessage() throws IOException, PGPException { - OpenPGPKey key = OpenPGPKey.fromAsciiArmor(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); OpenPGPMessageGenerator gen = getStaticGenerator() .addSigningKey(key); From 6af5d392d1d314ce260288ecc12c4e9fa604c6d3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 17:30:42 +0100 Subject: [PATCH 061/154] Add OpenPGPApi class --- .../bouncycastle/openpgp/api/OpenPGPApi.java | 63 +++++++++++++++++++ .../openpgp/api/OpenPGPKeyEditor.java | 40 +++++++++++- .../openpgp/api/OpenPGPMessageProcessor.java | 7 ++- .../openpgp/api/bc/BcOpenPGPApi.java | 45 +++++++++++++ 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java new file mode 100644 index 0000000000..83a77b6926 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -0,0 +1,63 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.PGPException; + +import java.util.Date; + +public abstract class OpenPGPApi +{ + private final OpenPGPImplementation implementation; + private final OpenPGPPolicy policy; + + public OpenPGPApi(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + public OpenPGPApi(OpenPGPImplementation implementation, OpenPGPPolicy policy) + { + this.implementation = implementation; + this.policy = policy; + } + + public OpenPGPKeyReader readKeyOrCertificate() + { + return new OpenPGPKeyReader(implementation, policy); + } + + public abstract OpenPGPV6KeyGenerator generateKey() + throws PGPException; + + public abstract OpenPGPV6KeyGenerator generateKey(Date creationTime) + throws PGPException; + + public abstract OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) + throws PGPException; + + public OpenPGPMessageGenerator signAndOrEncryptMessage() + { + return new OpenPGPMessageGenerator(implementation, policy); + } + + public OpenPGPDetachedSignatureGenerator createDetachedSignature() + { + return new OpenPGPDetachedSignatureGenerator(implementation, policy); + } + + public OpenPGPMessageProcessor decryptAndOrVerifyMessage() + { + return new OpenPGPMessageProcessor(implementation, policy); + } + + public OpenPGPDetachedSignatureProcessor verifyDetachedSignature() + { + return new OpenPGPDetachedSignatureProcessor(implementation, policy); + } + + public OpenPGPKeyEditor editKey(OpenPGPKey key) + { + return new OpenPGPKeyEditor(key, implementation, policy); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index a0536b5d15..5e06e98615 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -5,6 +5,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; @@ -17,6 +18,7 @@ public class OpenPGPKeyEditor { private final OpenPGPImplementation implementation; + private final OpenPGPPolicy policy; private OpenPGPKey key; public OpenPGPKeyEditor(OpenPGPKey key) @@ -25,9 +27,15 @@ public OpenPGPKeyEditor(OpenPGPKey key) } public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation) + { + this(key, implementation, implementation.policy()); + } + + public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.key = key; this.implementation = implementation; + this.policy = policy; } public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) @@ -77,7 +85,37 @@ public OpenPGPKeyEditor addUserId(String userId, PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); - this.key = new OpenPGPKey(secretKeyRing, implementation); + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + return this; + } + + public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey subkey, + char[] oldPassphrase, + char[] newPassphrase, + boolean useAEAD) + { + OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(subkey); + if (secretKey == null) + { + throw new IllegalArgumentException("Subkey is not part of the key."); + } + + try + { + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); + PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( + secretKey.getPGPSecretKey(), + implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), + implementation.pbeSecretKeyEncryptorFactory(useAEAD) + .build( + newPassphrase, + secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), + implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); + secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); + key = new OpenPGPKey(secretKeys, implementation, policy); + } catch (PGPException e) { + throw new RuntimeException(e); + } return this; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index 0c6f3f6c7b..4f97469c20 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -42,9 +42,14 @@ public OpenPGPMessageProcessor() * @param implementation openpgp implementation */ public OpenPGPMessageProcessor(OpenPGPImplementation implementation) + { + this(implementation, implementation.policy()); + } + + public OpenPGPMessageProcessor(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; - this.configuration = new Configuration(implementation.policy()); + this.configuration = new Configuration(policy); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java new file mode 100644 index 0000000000..28aec6ded2 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java @@ -0,0 +1,45 @@ +package org.bouncycastle.openpgp.api.bc; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPPolicy; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; + +import java.util.Date; + +public class BcOpenPGPApi + extends OpenPGPApi +{ + public BcOpenPGPApi() + { + super(new BcOpenPGPImplementation()); + } + + public BcOpenPGPApi(OpenPGPPolicy policy) + { + super(new BcOpenPGPImplementation(), policy); + } + + @Override + public OpenPGPV6KeyGenerator generateKey() + throws PGPException + { + return new BcOpenPGPV6KeyGenerator(); + } + + @Override + public OpenPGPV6KeyGenerator generateKey(Date creationTime) + throws PGPException + { + return new BcOpenPGPV6KeyGenerator(creationTime); + } + + @Override + public OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) + throws PGPException + { + return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); + } +} From e6330c477cd26479d5f043d2476b31a2e0904df2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 8 Jan 2025 17:30:51 +0100 Subject: [PATCH 062/154] Add tests for OpenPGPKeyEditor --- .../api/test/OpenPGPKeyEditorTest.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java new file mode 100644 index 0000000000..9513407b03 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -0,0 +1,99 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; + +import java.io.IOException; + +public class OpenPGPKeyEditorTest + extends AbstractPacketTest +{ + + @Override + public String getName() + { + return "OpenPGPKeyEditorTest"; + } + + @Override + public void performTest() + throws Exception + { + OpenPGPApi api = new BcOpenPGPApi(); + + performTestWith(api); + } + + private void performTestWith(OpenPGPApi api) + throws PGPException, IOException + { + unmodifiedKeyTest(api); + addUserIdTest(api); + changePassphraseNoAEADTest(api); + changePassphraseAEADTest(api); + } + + private void unmodifiedKeyTest(OpenPGPApi api) + throws PGPException + { + OpenPGPKey key = api.generateKey() + .ed25519x25519Key("Alice ", null); + OpenPGPKey editedKey = api.editKey(key) + .done(); + + isTrue("Key was not changed, so the reference MUST be the same", + key == editedKey); + } + + private void addUserIdTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(OpenPGPTestKeys.V6_KEY); + isNull(key.getPrimaryUserId()); + + key = api.editKey(key) + .addUserId("Alice ", null) + .done(); + + isEquals("Alice ", key.getPrimaryUserId().getUserId()); + } + + private void changePassphraseNoAEADTest(OpenPGPApi api) + throws IOException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + isFalse(key.getPrimarySecretKey().isLocked()); + + key = api.editKey(key) + .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), false) + .done(); + isTrue(key.getPrimarySecretKey().isLocked()); + isTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); + isEquals(SecretKeyPacket.USAGE_SHA1, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); + } + + private void changePassphraseAEADTest(OpenPGPApi api) + throws IOException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + isFalse(key.getPrimarySecretKey().isLocked()); + + key = api.editKey(key) + .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), true) + .done(); + isTrue(key.getPrimarySecretKey().isLocked()); + isTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); + isEquals(SecretKeyPacket.USAGE_AEAD, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); + } + + public static void main(String[] args) + { + runTest(new OpenPGPKeyEditorTest()); + } +} From 69ed3f186b5c036ac1241e36b9112494f673e0a7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 10:53:04 +0100 Subject: [PATCH 063/154] Throw exception if key has no usable encryption subkeys --- .../openpgp/api/OpenPGPMessageGenerator.java | 11 +++++++++-- .../exception/InvalidEncryptionKeyException.java | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 631de6f053..428cddf4e3 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -3,7 +3,6 @@ import org.bouncycastle.bcpg.AEADAlgorithmTags; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; -import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; @@ -23,6 +22,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.api.exception.InvalidEncryptionKeyException; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; @@ -78,6 +78,7 @@ public OpenPGPMessageGenerator(OpenPGPImplementation implementation, OpenPGPPoli * @return this */ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate) + throws InvalidEncryptionKeyException { return addEncryptionCertificate(recipientCertificate, config.encryptionKeySelector); } @@ -91,8 +92,14 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip * @param subkeySelector selector for encryption subkeys * @return this */ - public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate, SubkeySelector subkeySelector) + public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate, + SubkeySelector subkeySelector) + throws InvalidEncryptionKeyException { + if (subkeySelector.select(recipientCertificate, config.policy).isEmpty()) + { + throw new InvalidEncryptionKeyException("Key does not have valid encryption subkeys."); + } config.recipients.add(new Recipient(recipientCertificate, implementation.policy(), subkeySelector)); return this; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java new file mode 100644 index 0000000000..0b720662dd --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java @@ -0,0 +1,16 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPException; + +/** + * Exception that gets thrown if the user tries to encrypt a message for an + * {@link org.bouncycastle.openpgp.api.OpenPGPCertificate} that does not contain any usable, valid encryption keys. + */ +public class InvalidEncryptionKeyException + extends PGPException +{ + public InvalidEncryptionKeyException(String message) + { + super(message); + } +} From 5cc2a667c85669d5e1c9c6d2c17cc83311d23ebb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 10:57:47 +0100 Subject: [PATCH 064/154] Key generator: Only try to add non-null user-ids --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index c0c11e581d..398106728a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -107,8 +107,12 @@ public OpenPGPV6KeyGenerator( public OpenPGPKey classicKey(String userId, char[] passphrase) throws PGPException { - return withPrimaryKey() - .addUserId(userId) + WithPrimaryKey builder = withPrimaryKey(); + if (userId != null) + { + builder.addUserId(userId); + } + return builder .addSigningSubkey() .addEncryptionSubkey() .build(passphrase); @@ -127,7 +131,7 @@ public OpenPGPKey classicKey(String userId, char[] passphrase) public OpenPGPKey ed25519x25519Key(String userId, char[] passphrase) throws PGPException { - return withPrimaryKey(new KeyPairGeneratorCallback() + WithPrimaryKey builder = withPrimaryKey(new KeyPairGeneratorCallback() { public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) throws PGPException @@ -150,9 +154,14 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) { return generator.generateX25519KeyPair(); } - }) - .addUserId(userId) - .build(passphrase); + }); + + if (userId != null) + { + builder.addUserId(userId); + } + + return builder.build(passphrase); } @@ -169,7 +178,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) public OpenPGPKey ed448x448Key(String userId, char[] passphrase) throws PGPException { - return withPrimaryKey(new KeyPairGeneratorCallback() + WithPrimaryKey builder = withPrimaryKey(new KeyPairGeneratorCallback() { public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) throws PGPException @@ -192,9 +201,14 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) { return generator.generateX448KeyPair(); } - }) - .addUserId(userId) - .build(passphrase); + }); + + if (userId != null) + { + builder.addUserId(userId); + } + + return builder.build(passphrase); } /** From 1b1c4ab244d0f7e429ec79c3b8dbabd9cd10ca57 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 12:54:44 +0100 Subject: [PATCH 065/154] Fix JcePBESecretKeyDecryptorBuilder setting provider --- .../api/jcajce/JcaOpenPGPImplementation.java | 3 ++- .../JcePBESecretKeyDecryptorBuilderProvider.java | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java index bf475123a2..477908d080 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java @@ -81,7 +81,8 @@ public PBESecretKeyDecryptorBuilderProvider pbeSecretKeyDecryptorBuilderProvider { JcaPGPDigestCalculatorProviderBuilder dp = new JcaPGPDigestCalculatorProviderBuilder(); dp.setProvider(provider); - JcePBESecretKeyDecryptorBuilderProvider p = new JcePBESecretKeyDecryptorBuilderProvider(dp); + JcePBESecretKeyDecryptorBuilderProvider p = new JcePBESecretKeyDecryptorBuilderProvider(dp) + .setProvider(provider); return p; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java index bc67caa84c..1e1af8ffc5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilderProvider.java @@ -4,20 +4,34 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import java.security.Provider; + public class JcePBESecretKeyDecryptorBuilderProvider implements PBESecretKeyDecryptorBuilderProvider { private final JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder; + private Provider provider; public JcePBESecretKeyDecryptorBuilderProvider(JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder) { this.digestCalculatorProviderBuilder = digestCalculatorProviderBuilder; } + public JcePBESecretKeyDecryptorBuilderProvider setProvider(Provider provider) + { + this.provider = provider; + return this; + } + @Override public PBESecretKeyDecryptorBuilder provide() throws PGPException { - return new JcePBESecretKeyDecryptorBuilder(digestCalculatorProviderBuilder.build()); + JcePBESecretKeyDecryptorBuilder b = new JcePBESecretKeyDecryptorBuilder(digestCalculatorProviderBuilder.build()); + if (provider != null) + { + b.setProvider(provider); + } + return b; } } From c469e3b0e2aab7e2a1bc77e4a1c529cf7148e160 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 12:56:12 +0100 Subject: [PATCH 066/154] Implement JcaOpenPGPApi --- .../openpgp/api/jcajce/JcaOpenPGPApi.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java new file mode 100644 index 0000000000..e46afd31b5 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java @@ -0,0 +1,60 @@ +package org.bouncycastle.openpgp.api.jcajce; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPPolicy; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; + +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Date; + +public class JcaOpenPGPApi + extends OpenPGPApi +{ + private final Provider provider; + + public JcaOpenPGPApi(Provider provider) + { + this(provider, CryptoServicesRegistrar.getSecureRandom()); + } + + public JcaOpenPGPApi(Provider provider, SecureRandom random) + { + super(new JcaOpenPGPImplementation(provider, random)); + this.provider = provider; + } + + public JcaOpenPGPApi(Provider provider, OpenPGPPolicy policy) + { + this(provider, CryptoServicesRegistrar.getSecureRandom(), policy); + } + + public JcaOpenPGPApi(Provider provider, SecureRandom random, OpenPGPPolicy policy) + { + super(new JcaOpenPGPImplementation(provider, random), policy); + this.provider = provider; + } + + @Override + public OpenPGPV6KeyGenerator generateKey() + throws PGPException + { + return new JcaOpenPGPV6KeyGenerator(provider); + } + + @Override + public OpenPGPV6KeyGenerator generateKey(Date creationTime) + throws PGPException + { + return new JcaOpenPGPV6KeyGenerator(creationTime, provider); + } + + @Override + public OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + throws PGPException + { + return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection, provider); + } +} From 01a4b06abab1ce7be835e0fc865b66534d4dadd0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 12:56:24 +0100 Subject: [PATCH 067/154] Improve OpenPGPKeyEditorTest --- .../api/test/OpenPGPKeyEditorTest.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index 9513407b03..6f97ab3402 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -2,11 +2,13 @@ import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import java.io.IOException; @@ -25,7 +27,9 @@ public void performTest() throws Exception { OpenPGPApi api = new BcOpenPGPApi(); + performTestWith(api); + api = new JcaOpenPGPApi(new BouncyCastleProvider()); performTestWith(api); } @@ -34,8 +38,8 @@ private void performTestWith(OpenPGPApi api) { unmodifiedKeyTest(api); addUserIdTest(api); - changePassphraseNoAEADTest(api); - changePassphraseAEADTest(api); + changePassphraseUnprotectedToCFBTest(api); + changePassphraseUnprotectedToAEADTest(api); } private void unmodifiedKeyTest(OpenPGPApi api) @@ -55,16 +59,17 @@ private void addUserIdTest(OpenPGPApi api) { OpenPGPKey key = api.readKeyOrCertificate() .parseKey(OpenPGPTestKeys.V6_KEY); - isNull(key.getPrimaryUserId()); + isNull("Expect primary user-id to be null", key.getPrimaryUserId()); key = api.editKey(key) .addUserId("Alice ", null) .done(); - isEquals("Alice ", key.getPrimaryUserId().getUserId()); + isEquals("Expect the new user-id to be primary now", + "Alice ", key.getPrimaryUserId().getUserId()); } - private void changePassphraseNoAEADTest(OpenPGPApi api) + private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) throws IOException { OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); @@ -73,23 +78,28 @@ private void changePassphraseNoAEADTest(OpenPGPApi api) key = api.editKey(key) .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), false) .done(); - isTrue(key.getPrimarySecretKey().isLocked()); - isTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); - isEquals(SecretKeyPacket.USAGE_SHA1, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); + isTrue("Expect key to be locked", key.getPrimarySecretKey().isLocked()); + isTrue("Expect sw0rdf1sh to be the correct passphrase", + key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); + isEquals("Expect use of USAGE_CHECKSUM for key protection", + SecretKeyPacket.USAGE_SHA1, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); } - private void changePassphraseAEADTest(OpenPGPApi api) + private void changePassphraseUnprotectedToAEADTest(OpenPGPApi api) throws IOException { OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); - isFalse(key.getPrimarySecretKey().isLocked()); + isFalse("Expect key to be unprotected", key.getPrimarySecretKey().isLocked()); key = api.editKey(key) .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), true) .done(); - isTrue(key.getPrimarySecretKey().isLocked()); - isTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); - isEquals(SecretKeyPacket.USAGE_AEAD, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); + isTrue("Expect key to be locked after changing passphrase", + key.getPrimarySecretKey().isLocked()); + isTrue("Expect sw0rdf1sh to be the correct passphrase using AEAD", + key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray())); + isEquals("Expect use of AEAD for key protection", + SecretKeyPacket.USAGE_AEAD, key.getPrimarySecretKey().getPGPSecretKey().getS2KUsage()); } public static void main(String[] args) From 7c846d6fc1e409bf132e602bc268d7e969abb6ca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 10 Jan 2025 16:00:49 +0100 Subject: [PATCH 068/154] Introduce SignatureParameters class to simplify key editor --- .../openpgp/api/OpenPGPCertificate.java | 5 + .../bouncycastle/openpgp/api/OpenPGPKey.java | 14 +- .../openpgp/api/OpenPGPKeyEditor.java | 142 ++++++++++++++--- .../openpgp/api/SignatureParameters.java | 143 ++++++++++++++++++ .../api/exception/KeyPassphraseException.java | 12 ++ .../api/test/OpenPGPKeyEditorTest.java | 90 ++++++++++- 6 files changed, 380 insertions(+), 26 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index ac8538fe3c..72f74a3f28 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -693,6 +693,11 @@ public OpenPGPCertificate getCertificate() */ public abstract String toDetailString(); + public boolean isBound() + { + return isBoundAt(new Date()); + } + /** * Return true, if this component is - at evaluation time - properly bound to its certificate. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 0a4ed4db9e..def65dd79e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -9,6 +9,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.exception.KeyPassphraseException; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; @@ -254,11 +255,18 @@ public PGPPrivateKey unlock(char[] passphrase) throws PGPException { PBESecretKeyDecryptor decryptor = null; - if (passphrase != null) + try + { + if (passphrase != null) + { + decryptor = decryptorBuilderProvider.provide().build(passphrase); + } + return getPGPSecretKey().extractPrivateKey(decryptor); + } + catch (PGPException e) { - decryptor = decryptorBuilderProvider.provide().build(passphrase); + throw new KeyPassphraseException(e); } - return getPGPSecretKey().extractPrivateKey(decryptor); } public boolean isPassphraseCorrect(char[] passphrase) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index 5e06e98615..3dc310cdeb 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -11,8 +11,6 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import java.util.Date; - public class OpenPGPKeyEditor extends AbstractOpenPGPKeySignatureGenerator { @@ -38,18 +36,57 @@ public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation, Op this.policy = policy; } + public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, + SignatureParameters.Callback callback) + throws PGPException + { + SignatureParameters parameters = SignatureParameters.directKeySignatureParameters(policy); + if (callback != null) + { + parameters = callback.apply(parameters); + } + + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + dkSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + dkSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + dkSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPSignature dkSig = dkSigGen.generateCertification(publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, dkSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + return this; + } + public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) throws PGPException { - return addUserId(userId, PGPSignature.POSITIVE_CERTIFICATION, null, HashAlgorithmTags.SHA3_512, new Date(), primaryKeyPassphrase); + return addUserId(userId, primaryKeyPassphrase, null); } public OpenPGPKeyEditor addUserId(String userId, - int certificationType, - SignatureSubpacketsFunction userIdSubpackets, - int hashAlgorithmId, - Date bindingTime, - char[] primaryKeyPassphrase) + char[] primaryKeyPassphrase, + SignatureParameters.Callback callback) throws PGPException { if (userId == null || userId.trim().isEmpty()) @@ -57,29 +94,35 @@ public OpenPGPKeyEditor addUserId(String userId, throw new IllegalArgumentException("User-ID cannot be null or empty."); } - if (!PGPSignature.isCertification(certificationType)) + SignatureParameters parameters = SignatureParameters.certificationSignatureParameters(policy); + if (callback != null) { - throw new IllegalArgumentException("Signature type MUST be a certification type (0x10 - 0x13)"); + parameters = callback.apply(parameters); } PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder(publicPrimaryKey.getAlgorithm(), hashAlgorithmId), + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - uidSigGen.init(certificationType, privatePrimaryKey); + uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); - PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - subpackets.setIssuerFingerprint(true, publicPrimaryKey); - subpackets.setSignatureCreationTime(bindingTime); + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); - if (userIdSubpackets != null) - { - subpackets = userIdSubpackets.apply(subpackets); - } - uidSigGen.setHashedSubpackets(subpackets.generate()); + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + // Inject UID and signature into the certificate PGPSignature uidSig = uidSigGen.generateCertification(userId, publicPrimaryKey); PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId, uidSig); PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); @@ -113,7 +156,9 @@ public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); key = new OpenPGPKey(secretKeys, implementation, policy); - } catch (PGPException e) { + } + catch (PGPException e) + { throw new RuntimeException(e); } return this; @@ -123,4 +168,59 @@ public OpenPGPKey done() { return key; } + + public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, + char[] primaryKeyPassphrase) + throws PGPException + { + return revokeUserId(userId, primaryKeyPassphrase, null); + } + + public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, + char[] primaryKeyPassphrase, + SignatureParameters.Callback callback) + throws PGPException + { + if (!key.getComponents().contains(userId)) + { + throw new IllegalArgumentException("UserID is not part of the certificate."); + } + + SignatureParameters parameters = SignatureParameters.certificationRevocationSignatureParameters(policy); + if (callback != null) + { + parameters = callback.apply(parameters); + } + + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPSignature uidSig = uidSigGen.generateCertification(userId.getUserId(), publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId.getUserId(), uidSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + return this; + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java new file mode 100644 index 0000000000..f61a2728a1 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -0,0 +1,143 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.util.Arrays; + +import java.util.Date; + +public class SignatureParameters +{ + private int signatureType; + private Date signatureCreationTime = new Date(); + private int signatureHashAlgorithmId; + private SignatureSubpacketsFunction hashedSubpacketsFunction; + private SignatureSubpacketsFunction unhashedSubpacketsFunction; + + private final int[] allowedSignatureTypes; + + private SignatureParameters(int... allowedSignatureTypes) + { + this.allowedSignatureTypes = allowedSignatureTypes; + } + + public static SignatureParameters directKeySignatureParameters(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.DIRECT_KEY) + .setSignatureType(PGPSignature.DIRECT_KEY) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + + public static SignatureParameters certificationSignatureParameters(OpenPGPPolicy policy) + { + return new SignatureParameters( + PGPSignature.DEFAULT_CERTIFICATION, + PGPSignature.NO_CERTIFICATION, + PGPSignature.CASUAL_CERTIFICATION, + PGPSignature.POSITIVE_CERTIFICATION) + .setSignatureType(PGPSignature.POSITIVE_CERTIFICATION) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + + public static SignatureParameters subkeyBindingSignatureParameters(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.SUBKEY_BINDING) + .setSignatureType(PGPSignature.SUBKEY_BINDING) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + + public static SignatureParameters primaryKeyBindingSignatureParameters(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.PRIMARYKEY_BINDING) + .setSignatureType(PGPSignature.PRIMARYKEY_BINDING) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + + public static SignatureParameters certificationRevocationSignatureParameters(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.CERTIFICATION_REVOCATION) + .setSignatureType(PGPSignature.CERTIFICATION_REVOCATION) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + + public SignatureParameters setSignatureType(int signatureType) + { + if (!Arrays.contains(allowedSignatureTypes, signatureType)) + { + throw new IllegalArgumentException("Illegal signature type provided."); + } + + this.signatureType = signatureType; + return this; + } + + public int getSignatureType() + { + return signatureType; + } + + public SignatureParameters setSignatureCreationTime(Date signatureCreationTime) + { + this.signatureCreationTime = signatureCreationTime; + return this; + } + + public Date getSignatureCreationTime() + { + return signatureCreationTime; + } + + public SignatureParameters setSignatureHashAlgorithm(int signatureHashAlgorithmId) + { + this.signatureHashAlgorithmId = signatureHashAlgorithmId; + return this; + } + + public int getSignatureHashAlgorithmId() + { + return signatureHashAlgorithmId; + } + + public SignatureParameters setHashedSubpacketsFunction(SignatureSubpacketsFunction subpacketsFunction) + { + this.hashedSubpacketsFunction = subpacketsFunction; + return this; + } + + public PGPSignatureSubpacketGenerator applyToHashedSubpackets(PGPSignatureSubpacketGenerator hashedSubpackets) + { + if (hashedSubpacketsFunction != null) + { + return hashedSubpacketsFunction.apply(hashedSubpackets); + } + return hashedSubpackets; + } + + public SignatureParameters setUnhashedSubpacketsFunction(SignatureSubpacketsFunction subpacketsFunction) + { + this.unhashedSubpacketsFunction = subpacketsFunction; + return this; + } + + public PGPSignatureSubpacketGenerator applyToUnhashedSubpackets(PGPSignatureSubpacketGenerator unhashedSubpackets) + { + if (unhashedSubpacketsFunction != null) + { + return unhashedSubpacketsFunction.apply(unhashedSubpackets); + } + return unhashedSubpackets; + } + + public interface Callback + { + default SignatureParameters apply(SignatureParameters parameters) + { + return parameters; + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java new file mode 100644 index 0000000000..48f9140ad4 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java @@ -0,0 +1,12 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPException; + +public class KeyPassphraseException + extends PGPException +{ + public KeyPassphraseException(Exception cause) + { + super("Cannot unlock secret key", cause); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index 6f97ab3402..e1bed98e18 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -1,16 +1,22 @@ package org.bouncycastle.openpgp.api.test; import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.sig.RevocationReasonTags; import org.bouncycastle.bcpg.test.AbstractPacketTest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.SignatureParameters; +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import java.io.IOException; +import java.util.Date; public class OpenPGPKeyEditorTest extends AbstractPacketTest @@ -36,13 +42,28 @@ public void performTest() private void performTestWith(OpenPGPApi api) throws PGPException, IOException { - unmodifiedKeyTest(api); + doNothingTest(api); + addUserIdTest(api); + softRevokeUserIdTest(api); + hardRevokeUserIdTest(api); + /* + addEncryptionSubkeyTest(api); + revokeEncryptionSubkeyTest(api); + + addSigningSubkeyTest(api); + revokeSigningSubkeyTest(api); + + extendExpirationTimeTest(api); + revokeCertificateTest(api); + + + */ changePassphraseUnprotectedToCFBTest(api); changePassphraseUnprotectedToAEADTest(api); } - private void unmodifiedKeyTest(OpenPGPApi api) + private void doNothingTest(OpenPGPApi api) throws PGPException { OpenPGPKey key = api.generateKey() @@ -69,6 +90,71 @@ private void addUserIdTest(OpenPGPApi api) "Alice ", key.getPrimaryUserId().getUserId()); } + private void softRevokeUserIdTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(OpenPGPTestKeys.ALICE_KEY); + Date now = new Date(); + Date oneHourAgo = new Date(new Date().getTime() - (1000 * 60 * 60)); + OpenPGPCertificate.OpenPGPUserId userId = key.getPrimaryUserId(now); + isNotNull(userId); + isTrue(userId.isBound()); + isEquals("Alice Lovelace ", userId.getUserId()); + + key = api.editKey(key) + .revokeUserId(userId, null, new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + parameters.setSignatureCreationTime(now); + parameters.setHashedSubpacketsFunction(new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.setRevocationReason(true, RevocationReasonTags.USER_NO_LONGER_VALID, ""); + return subpackets; + } + }); + return parameters; + } + }) + .done(); + isTrue(key.getPrimaryUserId().isBoundAt(oneHourAgo)); + isFalse(key.getPrimaryUserId().isBoundAt(now)); + } + + + private void hardRevokeUserIdTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(OpenPGPTestKeys.ALICE_KEY); + Date now = new Date(); + Date oneHourAgo = new Date(new Date().getTime() - (1000 * 60 * 60)); + OpenPGPCertificate.OpenPGPUserId userId = key.getPrimaryUserId(now); + isNotNull(userId); + isTrue(userId.isBound()); + isEquals("Alice Lovelace ", userId.getUserId()); + + key = api.editKey(key) + .revokeUserId(userId, null, new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + parameters.setSignatureCreationTime(now); + // no reason -> hard revocation + return parameters; + } + }) + .done(); + isFalse(key.getPrimaryUserId().isBoundAt(oneHourAgo)); + isFalse(key.getPrimaryUserId().isBoundAt(now)); + } + private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) throws IOException { From ccf03281bc76cc43453db98e5f0d495e04593565 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 11 Jan 2025 16:08:58 +0100 Subject: [PATCH 069/154] Add package-info with architectural overview --- .../openpgp/api/package-info.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/package-info.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/package-info.java b/pg/src/main/java/org/bouncycastle/openpgp/api/package-info.java new file mode 100644 index 0000000000..099545ae27 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/package-info.java @@ -0,0 +1,23 @@ +/** + * The
api
package contains a high-level OpenPGP API layer on top of the + *
openpgp
mid-level API. + * It is tailored to provide a modern OpenPGP experience, following the guidance from rfc9580 ("OpenPGP v6"), + * while also being interoperable with rfc4880 ("OpenPGP v4"). + *

+ * From an architectural point of view, the hierarchy of the individual layers is as follows: + *

    + *
  • + *
    api
    specifies a high-level API using mid-level implementations from
    openpgp
    . + * This layer strives to be easy to use, hard to misuse and secure by default. + *
  • + *
  • + *
    openpgp
    defines a powerful, flexible, but quite verbose API using packet definitions + * from
    bcpg
    . + *
  • + *
  • + *
    bcpg
    implements serialization / deserialization of OpenPGP packets. + * It does not contain any business logic. + *
  • + *
+ */ +package org.bouncycastle.openpgp.api; \ No newline at end of file From dd86c215a4a3d6ef3efc8a85be2e7f1923f60921 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 11 Jan 2025 19:44:23 +0100 Subject: [PATCH 070/154] Simplify OpenPGPKeyGenerator to not allow for per-key passphrases Instead, users should generate the key with one passphrase and then use the OpenPGPKeyEditor to change the passphrase of individual subkeys one by one. --- .../AbstractOpenPGPKeySignatureGenerator.java | 13 + .../bouncycastle/openpgp/api/OpenPGPApi.java | 3 +- .../openpgp/api/OpenPGPV6KeyGenerator.java | 808 ++++++------------ .../openpgp/api/SignatureParameters.java | 16 +- .../openpgp/api/bc/BcOpenPGPApi.java | 5 +- .../api/bc/BcOpenPGPV6KeyGenerator.java | 20 +- .../openpgp/api/jcajce/JcaOpenPGPApi.java | 4 +- .../api/jcajce/JcaOpenPGPV6KeyGenerator.java | 12 +- .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 34 +- .../api/test/OpenPGPCertificateTest.java | 5 +- .../api/test/OpenPGPKeyEditorTest.java | 3 +- .../api/test/OpenPGPV6KeyGeneratorTest.java | 244 +++--- 12 files changed, 424 insertions(+), 743 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java index 927131bce9..d575114ce2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java @@ -8,7 +8,10 @@ import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; public abstract class AbstractOpenPGPKeySignatureGenerator { @@ -178,4 +181,14 @@ public void setEncryptionSubkeySubpackets(SignatureSubpacketsFunction encryption { this.encryptionSubkeySubpackets = encryptionSubkeySubpackets; } + + protected KeyPairGeneratorCallback generatePrimaryKey = new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generatePrimaryKey(); + } + }; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java index 83a77b6926..46333ab0c7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -31,8 +31,7 @@ public abstract OpenPGPV6KeyGenerator generateKey() public abstract OpenPGPV6KeyGenerator generateKey(Date creationTime) throws PGPException; - public abstract OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, - Date creationTime, + public abstract OpenPGPV6KeyGenerator generateKey(Date creationTime, boolean aeadProtection) throws PGPException; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 398106728a..b61202aebc 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -24,7 +24,6 @@ import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; @@ -36,11 +35,6 @@ public class OpenPGPV6KeyGenerator extends AbstractOpenPGPKeySignatureGenerator { - /** - * Hash algorithm for key signatures if no other one is provided during construction. - */ - public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; - // SECONDS private static final long SECONDS_PER_MINUTE = 60; private static final long SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; @@ -52,7 +46,6 @@ public class OpenPGPV6KeyGenerator private final Configuration configuration; // contains BC or JCA/JCE implementations public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, - int signatureHashAlgorithmId, boolean aead, Date creationTime) throws PGPException @@ -60,7 +53,6 @@ public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, this( implementationProvider, implementationProvider.pgpKeyPairGeneratorProvider(), - implementationProvider.pgpContentSignerBuilderProvider(signatureHashAlgorithmId), implementationProvider.pgpDigestCalculatorProvider(), implementationProvider.pbeSecretKeyEncryptorFactory(aead), implementationProvider.keyFingerPrintCalculator(), @@ -72,7 +64,6 @@ public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, * Generate a new OpenPGP key generator for v6 keys. * * @param kpGenProvider key pair generator provider - * @param contentSignerBuilderProvider content signer builder provider * @param digestCalculatorProvider digest calculator provider * @param keyEncryptionBuilderProvider secret key encryption builder provider (AEAD) * @param keyFingerPrintCalculator calculator for key fingerprints @@ -81,54 +72,52 @@ public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, public OpenPGPV6KeyGenerator( OpenPGPImplementation implementationProvider, PGPKeyPairGeneratorProvider kpGenProvider, - PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, PBESecretKeyEncryptorFactory keyEncryptionBuilderProvider, KeyFingerPrintCalculator keyFingerPrintCalculator, Date creationTime) { this.implementationProvider = implementationProvider; - this.configuration = new Configuration(creationTime, kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); + this.configuration = new Configuration(creationTime, kpGenProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); } /** * Generate an OpenPGP key consisting of a certify-only primary key, * a dedicated signing-subkey and dedicated encryption-subkey. - * The key will carry the provided user-id and be protected using the provided passphrase. + * The key will optionally carry the provided user-id. * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type, * {@link PGPKeyPairGenerator#generateSigningSubkey()} for the signing-subkey type and * {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the encryption-subkey key type. * - * @param userId user id - * @param passphrase nullable passphrase. + * @param userId nullable user id * @return OpenPGP key - * @throws PGPException if the key cannot be generated + * @throws PGPException if the key cannot be prepared */ - public OpenPGPKey classicKey(String userId, char[] passphrase) + public WithPrimaryKey classicKey(String userId) throws PGPException { - WithPrimaryKey builder = withPrimaryKey(); + WithPrimaryKey builder = withPrimaryKey() + .addSigningSubkey() + .addEncryptionSubkey(); + if (userId != null) { builder.addUserId(userId); } - return builder - .addSigningSubkey() - .addEncryptionSubkey() - .build(passphrase); + + return builder; } /** * Generate an OpenPGP key consisting of an Ed25519 certify-only primary key, * a dedicated Ed25519 sign-only subkey and dedicated X25519 encryption-only subkey. - * The key will carry the provided user-id and be protected using the provided passphrase. + * The key will optionally carry the provided user-id. * - * @param userId user id - * @param passphrase nullable passphrase + * @param userId nullable user id * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public OpenPGPKey ed25519x25519Key(String userId, char[] passphrase) + public WithPrimaryKey ed25519x25519Key(String userId) throws PGPException { WithPrimaryKey builder = withPrimaryKey(new KeyPairGeneratorCallback() @@ -161,21 +150,20 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) builder.addUserId(userId); } - return builder.build(passphrase); + return builder; } /** * Generate an OpenPGP key consisting of an Ed448 certify-only primary key, * a dedicated Ed448 sign-only subkey and dedicated X448 encryption-only subkey. - * The key will carry the provided user-id and be protected using the provided passphrase. + * The key will optionally carry the provided user-id. * - * @param userId user id - * @param passphrase nullable passphrase + * @param userId nullable user id * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public OpenPGPKey ed448x448Key(String userId, char[] passphrase) + public WithPrimaryKey ed448x448Key(String userId) throws PGPException { WithPrimaryKey builder = withPrimaryKey(new KeyPairGeneratorCallback() @@ -208,7 +196,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) builder.addUserId(userId); } - return builder.build(passphrase); + return builder; } /** @@ -216,81 +204,26 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the key type. * - * @param passphrase nullable passphrase to protect the key with - * @return sign-only (+certify) OpenPGP key - * @throws PGPException if the key cannot be generated - */ - public OpenPGPKey signOnlyKey(char[] passphrase) - throws PGPException - { - return signOnlyKey(passphrase, null); - } - - /** - * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences whose subpackets can be - * modified by providing a {@link SignatureSubpacketsFunction}. - * - * @param passphrase nullable passphrase to protect the key with - * @param userSubpackets callback to modify the direct-key signature subpackets with * @return sign-only (+certify) OpenPGP key * @throws PGPException if the key cannot be generated */ - public OpenPGPKey signOnlyKey( - char[] passphrase, - SignatureSubpacketsFunction userSubpackets) + public WithPrimaryKey signOnlyKey() throws PGPException { - PGPKeyPair primaryKeyPair = configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime) - .generatePrimaryKey(); - PBESecretKeyEncryptor encryptor = configuration.keyEncryptorBuilderProvider - .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); - return signOnlyKey(primaryKeyPair, encryptor, userSubpackets); - } - - /** - * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences whose subpackets can be - * modified by providing a {@link SignatureSubpacketsFunction}. - * - * @param primaryKeyPair signing-capable primary key - * @param keyEncryptor nullable encryptor to protect the primary key with - * @param userSubpackets callback to modify the direct-key signature subpackets with - * @return sign-only (+certify) OpenPGP key - * @throws PGPException if the key cannot be generated - */ - public OpenPGPKey signOnlyKey( - PGPKeyPair primaryKeyPair, - PBESecretKeyEncryptor keyEncryptor, - SignatureSubpacketsFunction userSubpackets) - throws PGPException - { - if (primaryKeyPair.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket) - { - throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); - } - - return primaryKeyWithDirectKeySig(primaryKeyPair, - new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator baseSubpackets) + WithPrimaryKey builder = withPrimaryKey( + generatePrimaryKey, + SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() { - // remove unrelated subpackets not needed for sign-only keys - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); - - // replace key flags -> CERTIFY_OTHER|SIGN_DATA - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); - baseSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); - return baseSubpackets; - } - }, - userSubpackets, // apply user-provided subpacket changes - keyEncryptor) - .build(); + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); + return subpackets; + } + })); + + return builder; } /** @@ -302,144 +235,36 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator baseS */ public WithPrimaryKey withPrimaryKey() throws PGPException - { - return withPrimaryKey((SignatureSubpacketsFunction)null); - } - - /** - * Generate an OpenPGP key with a certification-capable primary key. - * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * - * @param keyGenCallback nullable callback to modify the direct-key signatures subpackets - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey withPrimaryKey( - KeyPairGeneratorCallback keyGenCallback) - throws PGPException - { - return withPrimaryKey(keyGenCallback, null); - } - - /** - * Generate an OpenPGP key with a certification-capable primary key. - * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * - * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey withPrimaryKey( - SignatureSubpacketsFunction directKeySubpackets) - throws PGPException { return withPrimaryKey( - new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException + new KeyPairGeneratorCallback() { - return generator.generatePrimaryKey(); + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generatePrimaryKey(); + } } - }, - directKeySubpackets); + ); } - /** - * Generate an OpenPGP key with a certification-capable primary key. - * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * - * @param keyGenCallback callback to specify the primary key type - * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey withPrimaryKey( - KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction directKeySubpackets) - throws PGPException - { - return withPrimaryKey(keyGenCallback, directKeySubpackets, null); - } - /** - * Generate an OpenPGP key with a certification-capable primary key. - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * - * @param primaryKeyPair primary key - * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets - * @return builder - * @throws PGPException if the key cannot be generated - */ public WithPrimaryKey withPrimaryKey( - PGPKeyPair primaryKeyPair, - SignatureSubpacketsFunction directKeySubpackets) + KeyPairGeneratorCallback keyGenCallback) throws PGPException { - return withPrimaryKey( - primaryKeyPair, - directKeySubpackets, - null); + return withPrimaryKey(keyGenCallback, null); } - /** - * Generate an OpenPGP key with a certification-capable primary key. - * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * IMPORTANT: The custom primary key passphrase will only be used, if in the final step the key is retrieved - * using {@link WithPrimaryKey#build()}. - * If instead {@link WithPrimaryKey#build(char[])} is used, the key-specific passphrase is overwritten with the argument - * passed into {@link WithPrimaryKey#build(char[])}. - * - * @param keyGenCallback callback to specify the primary key type - * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets - * @param passphrase nullable passphrase to protect the primary key with - * @return builder - * @throws PGPException if the key cannot be generated - */ public WithPrimaryKey withPrimaryKey( - KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction directKeySubpackets, - char[] passphrase) + KeyPairGeneratorCallback keyGenCallback, + SignatureParameters.Callback preferenceSignatureCallback) throws PGPException { PGPKeyPair primaryKeyPair = keyGenCallback.generateFrom( - configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); - PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider - .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); - return withPrimaryKey(primaryKeyPair, directKeySubpackets, keyEncryptor); - } + configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); - /** - * Generate an OpenPGP key with a certification-capable primary key. - * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. - * The key will carry a direct-key signature, whose subpackets can be modified by overriding the - * given {@link SignatureSubpacketsFunction}. - * IMPORTANT: The custom keyEncryptor will only be used, if in the final step the key is retrieved - * using {@link WithPrimaryKey#build()}. - * If instead {@link WithPrimaryKey#build(char[])} is used, the key-specific encryptor is overwritten with - * an encryptor built from the argument passed into {@link WithPrimaryKey#build(char[])}. - * - * @param primaryKeyPair primary key - * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets - * @param keyEncryptor nullable encryptor to protect the primary key with - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey withPrimaryKey( - final PGPKeyPair primaryKeyPair, - SignatureSubpacketsFunction directKeySubpackets, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { if (primaryKeyPair.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket) { throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); @@ -450,76 +275,47 @@ public WithPrimaryKey withPrimaryKey( throw new PGPException("Primary key MUST use signing-capable algorithm."); } - return primaryKeyWithDirectKeySig( - primaryKeyPair, - new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); - subpackets.setSignatureCreationTime(configuration.keyCreationTime); - subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); - subpackets = directKeySignatureSubpackets.apply(subpackets); - subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); - return subpackets; - } - }, - directKeySubpackets, - keyEncryptor); - } - - /** - * Specify the primary key and attach a direct-key signature. - * The direct-key signature's subpackets will first be modified using the baseSubpackets callback, followed - * by the customSubpackets callback. - * If both baseSubpackets and customSubpackets are null, no direct-key signature will be attached. - * - * @param primaryKeyPair primary key pair - * @param baseSubpackets base signature subpackets callback - * @param customSubpackets user-provided signature subpackets callback - * @param keyEncryptor key encryptor - * @return builder - * @throws PGPException if the key cannot be generated - */ - private WithPrimaryKey primaryKeyWithDirectKeySig( - PGPKeyPair primaryKeyPair, - SignatureSubpacketsFunction baseSubpackets, - SignatureSubpacketsFunction customSubpackets, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { - if (baseSubpackets != null || customSubpackets != null) + SignatureParameters parameters = SignatureParameters.directKeySignatureParameters( + implementationProvider.policy()); + if (preferenceSignatureCallback != null) { - // DK sig - PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( - configuration.contentSignerBuilderProvider.get(primaryKeyPair.getPublicKey()), - primaryKeyPair.getPublicKey()); - dkSigGen.init(PGPSignature.DIRECT_KEY, primaryKeyPair.getPrivateKey()); - - PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - // application-dictated subpackets - if (baseSubpackets != null) - { - subpackets = baseSubpackets.apply(subpackets); - } - - // Allow the user to modify the direct-key signature subpackets - if (customSubpackets != null) - { - subpackets = customSubpackets.apply(subpackets); - } + parameters = preferenceSignatureCallback.apply(parameters); + } - dkSigGen.setHashedSubpackets(subpackets.generate()); + if (parameters == null) + { + // Do not generate Direct-Key Signature for preferences + return new WithPrimaryKey(implementationProvider, configuration, primaryKeyPair); + } - PGPSignature dkSig = dkSigGen.generateCertification(primaryKeyPair.getPublicKey()); - primaryKeyPair = new PGPKeyPair( + PGPSignatureGenerator preferenceSigGen = new PGPSignatureGenerator( + implementationProvider.pgpContentSignerBuilder( + primaryKeyPair.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKeyPair.getPublicKey()); + preferenceSigGen.init(parameters.getSignatureType(), primaryKeyPair.getPrivateKey()); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); + hashedSubpackets = directKeySignatureSubpackets.apply(hashedSubpackets); + hashedSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); + hashedSubpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + preferenceSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + preferenceSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + PGPSignature dkSig = preferenceSigGen.generateCertification(primaryKeyPair.getPublicKey()); + primaryKeyPair = new PGPKeyPair( PGPPublicKey.addCertification(primaryKeyPair.getPublicKey(), dkSig), primaryKeyPair.getPrivateKey()); - } - Key primaryKey = new Key(primaryKeyPair, keyEncryptor); - - return new WithPrimaryKey(implementationProvider, configuration, primaryKey); + return new WithPrimaryKey(implementationProvider, configuration, primaryKeyPair); } /** @@ -530,8 +326,8 @@ public class WithPrimaryKey { private final OpenPGPImplementation implementation; private final Configuration configuration; - private Key primaryKey; - private final List subkeys = new ArrayList(); + private PGPKeyPair primaryKey; + private final List subkeys = new ArrayList(); /** * Builder. @@ -539,7 +335,7 @@ public class WithPrimaryKey * @param implementation cryptographic implementation * @param primaryKey specified primary key */ - private WithPrimaryKey(OpenPGPImplementation implementation, Configuration configuration, Key primaryKey) + private WithPrimaryKey(OpenPGPImplementation implementation, Configuration configuration, PGPKeyPair primaryKey) { this.implementation = implementation; this.configuration = configuration; @@ -563,33 +359,14 @@ public WithPrimaryKey addUserId(String userId) * Attach a User-ID with a positive certification to the key. * The subpackets of the user-id certification can be modified using the userIdSubpackets callback. * - * @param userId user-id - * @param userIdSubpackets callback to modify the certification subpackets + * @param userId user-id + * @param signatureParameters signature parameters * @return builder * @throws PGPException if the user-id cannot be added */ public WithPrimaryKey addUserId( String userId, - SignatureSubpacketsFunction userIdSubpackets) - throws PGPException - { - return addUserId(userId, PGPSignature.POSITIVE_CERTIFICATION, userIdSubpackets); - } - - /** - * Attach a User-ID with a positive certification to the key. - * The subpackets of the user-id certification can be modified using the userIdSubpackets callback. - * - * @param userId user-id - * @param certificationType signature type - * @param userIdSubpackets callback to modify the certification subpackets - * @return builder - * @throws PGPException if the user-id cannot be added - */ - public WithPrimaryKey addUserId( - String userId, - int certificationType, - SignatureSubpacketsFunction userIdSubpackets) + SignatureParameters.Callback signatureParameters) throws PGPException { if (userId == null || userId.trim().length() == 0) @@ -597,29 +374,32 @@ public WithPrimaryKey addUserId( throw new IllegalArgumentException("User-ID cannot be null or empty."); } - if (!PGPSignature.isCertification(certificationType)) + SignatureParameters parameters = SignatureParameters.certificationSignatureParameters(implementation.policy()); + if (signatureParameters != null) { - throw new IllegalArgumentException("Signature type MUST be a certification type (0x10 - 0x13)"); + parameters = signatureParameters.apply(parameters); } PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - configuration.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), - primaryKey.pair.getPublicKey()); - uidSigGen.init(certificationType, primaryKey.pair.getPrivateKey()); - - PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(configuration.keyCreationTime); - - if (userIdSubpackets != null) - { - subpackets = userIdSubpackets.apply(subpackets); - } - uidSigGen.setHashedSubpackets(subpackets.generate()); - - PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.pair.getPublicKey()); - PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userId, uidSig); - primaryKey = new Key(new PGPKeyPair(pubKey, primaryKey.pair.getPrivateKey()), primaryKey.encryptor); + implementation.pgpContentSignerBuilder( + primaryKey.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKey.getPublicKey()); + uidSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, primaryKey.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.getPublicKey()); + PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.getPublicKey(), userId, uidSig); + primaryKey = new PGPKeyPair(pubKey, primaryKey.getPrivateKey()); return this; } @@ -655,7 +435,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback) throws PGPException { - return addEncryptionSubkey(keyGenCallback, (char[])null); + return addEncryptionSubkey(keyGenCallback, null); } /** @@ -670,94 +450,19 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac */ public WithPrimaryKey addEncryptionSubkey( KeyPairGeneratorCallback generatorCallback, - SignatureSubpacketsFunction bindingSubpacketsCallback) + SignatureParameters.Callback bindingSubpacketsCallback) throws PGPException { PGPKeyPairGenerator generator = configuration.kpGenProvider.get( - primaryKey.pair.getPublicKey().getVersion(), + primaryKey.getPublicKey().getVersion(), configuration.keyCreationTime ); PGPKeyPair subkey = generatorCallback.generateFrom(generator); + subkey = subkey.asSubkey(implementation.keyFingerPrintCalculator()); - return addEncryptionSubkey(subkey, bindingSubpacketsCallback, null); - } - - /** - * Add an encryption-capable subkey to the OpenPGP key. - * The subkey will be protected using the provided subkey passphrase. - * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved - * using {@link #build()}. - * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument - * passed into {@link #build(char[])}. - * See {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the key type. - * - * @param passphrase nullable subkey passphrase - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey addEncryptionSubkey(char[] passphrase) - throws PGPException - { - return addEncryptionSubkey(new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateEncryptionSubkey(); - } - }, passphrase); - } - - /** - * Add an encryption-capable subkey to the OpenPGP key. - * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. - * The subkey will be protected using the provided subkey passphrase. - * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved - * using {@link #build()}. - * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument - * passed into {@link #build(char[])}. - * - * @param keyGenCallback callback to specify the key type - * @param passphrase nullable passphrase for the encryption subkey - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, - char[] passphrase) - throws PGPException - { - return addEncryptionSubkey(keyGenCallback, null, passphrase); - } - - /** - * Add an encryption-capable subkey to the OpenPGP key. - * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. - * The binding signatures subpackets can be modified by overriding the {@link SignatureSubpacketsFunction}. - * The subkey will be protected using the provided subkey passphrase. - * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved - * using {@link #build()}. - * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument - * passed into {@link #build(char[])}. - * - * @param keyGenCallback callback to specify the key type - * @param bindingSignatureCallback nullable callback to modify the binding signature subpackets - * @param passphrase nullable passphrase for the encryption subkey - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction bindingSignatureCallback, - char[] passphrase) - throws PGPException - { - PGPKeyPair subkey = keyGenCallback.generateFrom( - configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); - subkey = subkey.asSubkey(configuration.keyFingerprintCalculator); - PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); - return addEncryptionSubkey(subkey, bindingSignatureCallback, keyEncryptor); + return addEncryptionSubkey(subkey, bindingSubpacketsCallback); } - /** * Add an encryption-capable subkey to the OpenPGP key. * IMPORTANT: The custom key encryptor will only be used, if in the final step the key is retrieved @@ -767,14 +472,12 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * * @param encryptionSubkey encryption subkey * @param bindingSubpacketsCallback nullable callback to modify the subkey binding signature subpackets - * @param keyEncryptor nullable encryptor to encrypt the encryption subkey * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey( PGPKeyPair encryptionSubkey, - SignatureSubpacketsFunction bindingSubpacketsCallback, - PBESecretKeyEncryptor keyEncryptor) + SignatureParameters.Callback bindingSubpacketsCallback) throws PGPException { if (!(encryptionSubkey.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket)) @@ -786,16 +489,44 @@ public WithPrimaryKey addEncryptionSubkey( { throw new PGPException("Encryption key MUST use encryption-capable algorithm."); } - // generate binding signature - PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(configuration.keyCreationTime); - subpackets = encryptionSubkeySubpackets.apply(subpackets); - - // allow subpacket customization - PGPPublicKey publicSubkey = getPublicSubKey(encryptionSubkey, bindingSubpacketsCallback, subpackets); - Key subkey = new Key(new PGPKeyPair(publicSubkey, encryptionSubkey.getPrivateKey()), keyEncryptor); - subkeys.add(subkey); + + SignatureParameters parameters = SignatureParameters.subkeyBindingSignatureParameters( + implementation.policy()) + .setSignatureCreationTime(configuration.keyCreationTime); + if (bindingSubpacketsCallback != null) + { + parameters = bindingSubpacketsCallback.apply(parameters); + } + + if (parameters != null) + { + PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + primaryKey.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKey.getPublicKey()); + bindingSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets.setIssuerFingerprint(true, primaryKey.getPublicKey()); + hashedSubpackets = encryptionSubkeySubpackets.apply(hashedSubpackets); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + bindingSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + bindingSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + PGPSignature bindingSig = bindingSigGen.generateCertification( + primaryKey.getPublicKey(), encryptionSubkey.getPublicKey()); + PGPPublicKey publicSubkey = PGPPublicKey.addCertification(encryptionSubkey.getPublicKey(), bindingSig); + encryptionSubkey = new PGPKeyPair(publicSubkey, encryptionSubkey.getPrivateKey()); + } + + subkeys.add(encryptionSubkey); return this; } @@ -820,47 +551,6 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) }); } - /** - * Add a signing-capable subkey to the OpenPGP key. - * The binding signature will contain a primary-key back-signature. - * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. - * - * @param keyGenCallback callback to specify the signing-subkey type - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback) - throws PGPException - { - return addSigningSubkey(keyGenCallback, null); - } - - /** - * Add a signing-capable subkey to the OpenPGP key. - * See {@link PGPKeyPairGenerator#generateSigningSubkey()} for the key type. - * The binding signature will contain a primary-key back-signature. - * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved - * using {@link #build()}. - * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument - * passed into {@link #build(char[])}. - * - * @param passphrase nullable passphrase - * @return builder - * @throws PGPException if the key cannot be generated - */ - public WithPrimaryKey addSigningSubkey(char[] passphrase) - throws PGPException - { - return addSigningSubkey(new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateSigningSubkey(); - } - }, passphrase); - } - /** * Add a signing-capable subkey to the OpenPGP key. * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. @@ -871,15 +561,13 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) * passed into {@link #build(char[])}. * * @param keyGenCallback callback to specify the signing-key type - * @param passphrase nullable passphrase * @return builder * @throws PGPException if the key cannot be generated */ - public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, - char[] passphrase) + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback) throws PGPException { - return addSigningSubkey(keyGenCallback, null, null, passphrase); + return addSigningSubkey(keyGenCallback, null, null); } /** @@ -896,20 +584,17 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, * @param keyGenCallback callback to specify the signing-key type * @param bindingSignatureCallback callback to modify the contents of the signing subkey binding signature * @param backSignatureCallback callback to modify the contents of the embedded primary key binding signature - * @param passphrase nullable passphrase * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction bindingSignatureCallback, - SignatureSubpacketsFunction backSignatureCallback, - char[] passphrase) + SignatureParameters.Callback bindingSignatureCallback, + SignatureParameters.Callback backSignatureCallback) throws PGPException { PGPKeyPair subkey = keyGenCallback.generateFrom(configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); subkey = subkey.asSubkey(configuration.keyFingerprintCalculator); - PBESecretKeyEncryptor keyEncryptor = configuration.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); - return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); + return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback); } /** @@ -926,15 +611,13 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, * @param signingSubkey signing subkey * @param bindingSignatureCallback callback to modify the contents of the signing subkey binding signature * @param backSignatureCallback callback to modify the contents of the embedded primary key binding signature - * @param keyEncryptor nullable encryptor to protect the signing subkey * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, - SignatureSubpacketsFunction bindingSignatureCallback, - SignatureSubpacketsFunction backSignatureCallback, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException + SignatureParameters.Callback bindingSignatureCallback, + SignatureParameters.Callback backSignatureCallback) + throws PGPException { if (!(signingSubkey.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket)) { @@ -946,40 +629,87 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, throw new PGPException("Signing key MUST use signing-capable algorithm."); } - PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); - backSigSubpackets.setIssuerFingerprint(true, signingSubkey.getPublicKey()); - backSigSubpackets.setSignatureCreationTime(configuration.keyCreationTime); + SignatureParameters parameters = SignatureParameters.primaryKeyBindingSignatureParameters( + implementation.policy()) + .setSignatureCreationTime(configuration.keyCreationTime); if (backSignatureCallback != null) { - backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); + parameters = backSignatureCallback.apply(parameters); } - PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); - bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - bindingSigSubpackets.setSignatureCreationTime(configuration.keyCreationTime); - - bindingSigSubpackets = signingSubkeySubpackets.apply(bindingSigSubpackets); + // Generate PrimaryKeySignature (Back-Signature) + PGPSignature backSig = null; + if (parameters != null) + { + PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder(signingSubkey.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + signingSubkey.getPublicKey()); + backSigGen.init(parameters.getSignatureType(), signingSubkey.getPrivateKey()); + + // Hashed backsig subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, signingSubkey.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + backSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed backsig subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + backSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + backSig = backSigGen.generateCertification( + primaryKey.getPublicKey(), signingSubkey.getPublicKey()); + } - PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( - configuration.contentSignerBuilderProvider.get(signingSubkey.getPublicKey()), - signingSubkey.getPublicKey()); - backSigGen.init(PGPSignature.PRIMARYKEY_BINDING, signingSubkey.getPrivateKey()); - backSigGen.setHashedSubpackets(backSigSubpackets.generate()); - PGPSignature backSig = backSigGen.generateCertification( - primaryKey.pair.getPublicKey(), signingSubkey.getPublicKey()); - try + parameters = SignatureParameters.subkeyBindingSignatureParameters(implementation.policy()) + .setSignatureCreationTime(configuration.keyCreationTime); + if (bindingSignatureCallback != null) { - bindingSigSubpackets.addEmbeddedSignature(false, backSig); + parameters = bindingSignatureCallback.apply(parameters); } - catch (IOException e) - { - throw new PGPException("Cannot embed back-signature.", e); + + // Create SubkeyBindingSignature + if (parameters != null) + { + PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder(primaryKey.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKey.getPublicKey()); + bindingSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + + // Hashed binding subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, primaryKey.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = signingSubkeySubpackets.apply(hashedSubpackets); + if (backSig != null) + { + try + { + hashedSubpackets.addEmbeddedSignature(true, backSig); + } + catch (IOException e) + { + throw new PGPException("Cannot embed back-signature.", e); + } + } + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + bindingSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed binding subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + bindingSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + PGPSignature bindingSig = bindingSigGen.generateCertification(primaryKey.getPublicKey(), signingSubkey.getPublicKey()); + PGPPublicKey signingPubKey = PGPPublicKey.addCertification(signingSubkey.getPublicKey(), bindingSig); + signingSubkey = new PGPKeyPair(signingPubKey, signingSubkey.getPrivateKey()); } - PGPPublicKey signingPubKey = getPublicSubKey(signingSubkey, bindingSignatureCallback, bindingSigSubpackets); - signingSubkey = new PGPKeyPair(signingPubKey, signingSubkey.getPrivateKey()); - subkeys.add(new Key(signingSubkey, keyEncryptor)); + subkeys.add(signingSubkey); return this; } @@ -994,23 +724,23 @@ public OpenPGPKey build() throws PGPException { PGPSecretKey primarySecretKey = new PGPSecretKey( - primaryKey.pair.getPrivateKey(), - primaryKey.pair.getPublicKey(), + primaryKey.getPrivateKey(), + primaryKey.getPublicKey(), configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), true, - primaryKey.encryptor); + null); List keys = new ArrayList(); keys.add(primarySecretKey); for (Iterator it = subkeys.iterator(); it.hasNext();) { - Key key = (Key)it.next(); + PGPKeyPair key = (PGPKeyPair)it.next(); PGPSecretKey subkey = new PGPSecretKey( - key.pair.getPrivateKey(), - key.pair.getPublicKey(), + key.getPrivateKey(), + key.getPublicKey(), configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), false, - key.encryptor); + null); keys.add(subkey); } @@ -1030,29 +760,29 @@ public OpenPGPKey build(char[] passphrase) throws PGPException { PBESecretKeyEncryptor primaryKeyEncryptor = configuration.keyEncryptorBuilderProvider - .build(passphrase, primaryKey.pair.getPublicKey().getPublicKeyPacket()); - sanitizeKeyEncryptor(primaryKeyEncryptor); + .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); PGPSecretKey primarySecretKey = new PGPSecretKey( - primaryKey.pair.getPrivateKey(), - primaryKey.pair.getPublicKey(), + primaryKey.getPrivateKey(), + primaryKey.getPublicKey(), configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), true, primaryKeyEncryptor); + sanitizeKeyEncryptor(primaryKeyEncryptor); List keys = new ArrayList(); keys.add(primarySecretKey); for (Iterator it = subkeys.iterator(); it.hasNext();) { - Key key = (Key)it.next(); + PGPKeyPair key = (PGPKeyPair)it.next(); PBESecretKeyEncryptor subkeyEncryptor = configuration.keyEncryptorBuilderProvider - .build(passphrase, key.pair.getPublicKey().getPublicKeyPacket()); - sanitizeKeyEncryptor(subkeyEncryptor); + .build(passphrase, key.getPublicKey().getPublicKeyPacket()); PGPSecretKey subkey = new PGPSecretKey( - key.pair.getPrivateKey(), - key.pair.getPublicKey(), + key.getPrivateKey(), + key.getPublicKey(), configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), false, subkeyEncryptor); + sanitizeKeyEncryptor(subkeyEncryptor); keys.add(subkey); } @@ -1086,24 +816,6 @@ else if (s2k.getType() == S2K.ARGON_2) } } } - - private PGPPublicKey getPublicSubKey(PGPKeyPair encryptionSubkey, SignatureSubpacketsFunction bindingSubpacketsCallback, PGPSignatureSubpacketGenerator subpackets) - throws PGPException - { - if (bindingSubpacketsCallback != null) - { - subpackets = bindingSubpacketsCallback.apply(subpackets); - } - - PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( - configuration.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), - primaryKey.pair.getPublicKey()); - bindingSigGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.pair.getPrivateKey()); - bindingSigGen.setHashedSubpackets(subpackets.generate()); - - PGPSignature bindingSig = bindingSigGen.generateCertification(primaryKey.pair.getPublicKey(), encryptionSubkey.getPublicKey()); - return PGPPublicKey.addCertification(encryptionSubkey.getPublicKey(), bindingSig); - } } /** @@ -1113,39 +825,21 @@ private static class Configuration { final Date keyCreationTime; final PGPKeyPairGeneratorProvider kpGenProvider; - final PGPContentSignerBuilderProvider contentSignerBuilderProvider; final PGPDigestCalculatorProvider digestCalculatorProvider; final PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider; final KeyFingerPrintCalculator keyFingerprintCalculator; public Configuration(Date keyCreationTime, PGPKeyPairGeneratorProvider keyPairGeneratorProvider, - PGPContentSignerBuilderProvider contentSignerBuilderProvider, - PGPDigestCalculatorProvider digestCalculatorProvider, - PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider, - KeyFingerPrintCalculator keyFingerPrintCalculator) + PGPDigestCalculatorProvider digestCalculatorProvider, + PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider, + KeyFingerPrintCalculator keyFingerPrintCalculator) { this.keyCreationTime = new Date((keyCreationTime.getTime() / 1000) * 1000); this.kpGenProvider = keyPairGeneratorProvider; - this.contentSignerBuilderProvider = contentSignerBuilderProvider; this.digestCalculatorProvider = digestCalculatorProvider; this.keyEncryptorBuilderProvider = keyEncryptorBuilderProvider; this.keyFingerprintCalculator = keyFingerPrintCalculator; } } - - /** - * Tuple of a {@link PGPKeyPair} and (nullable) {@link PBESecretKeyEncryptor}. - */ - private static class Key - { - private final PGPKeyPair pair; - private final PBESecretKeyEncryptor encryptor; - - public Key(PGPKeyPair key, PBESecretKeyEncryptor encryptor) - { - this.pair = key; - this.encryptor = encryptor; - } - } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index f61a2728a1..5224815e42 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -109,7 +109,7 @@ public SignatureParameters setHashedSubpacketsFunction(SignatureSubpacketsFuncti return this; } - public PGPSignatureSubpacketGenerator applyToHashedSubpackets(PGPSignatureSubpacketGenerator hashedSubpackets) + PGPSignatureSubpacketGenerator applyToHashedSubpackets(PGPSignatureSubpacketGenerator hashedSubpackets) { if (hashedSubpacketsFunction != null) { @@ -124,7 +124,7 @@ public SignatureParameters setUnhashedSubpacketsFunction(SignatureSubpacketsFunc return this; } - public PGPSignatureSubpacketGenerator applyToUnhashedSubpackets(PGPSignatureSubpacketGenerator unhashedSubpackets) + PGPSignatureSubpacketGenerator applyToUnhashedSubpackets(PGPSignatureSubpacketGenerator unhashedSubpackets) { if (unhashedSubpacketsFunction != null) { @@ -139,5 +139,17 @@ default SignatureParameters apply(SignatureParameters parameters) { return parameters; } + + static Callback applyToHashedSubpackets(SignatureSubpacketsFunction function) + { + return new Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + return parameters.setHashedSubpacketsFunction(function); + } + }; + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java index 28aec6ded2..aab2565e2c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java @@ -35,11 +35,10 @@ public OpenPGPV6KeyGenerator generateKey(Date creationTime) } @Override - public OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, - Date creationTime, + public OpenPGPV6KeyGenerator generateKey(Date creationTime, boolean aeadProtection) throws PGPException { - return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); + return new BcOpenPGPV6KeyGenerator(creationTime, aeadProtection); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java index 3c85e7ef08..ea70edef41 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java @@ -30,30 +30,18 @@ public BcOpenPGPV6KeyGenerator() public BcOpenPGPV6KeyGenerator(Date creationTime) throws PGPException { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); - } - - /** - * Create a new key generator for OpenPGP v6 keys. - * Signatures on the key will be generated using the specified {@code signatureHashAlgorithm}. - * - * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation - */ - public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) - throws PGPException - { - this(signatureHashAlgorithm, new Date(), true); + this(creationTime, true); } /** * Create a new OpenPGP key generator for v6 keys. * - * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures + * @param aeadProtection whether the key shall be protected using AEAD. If false, the key is protected using CFB. */ - public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + public BcOpenPGPV6KeyGenerator(Date creationTime, boolean aeadProtection) throws PGPException { - super(new BcOpenPGPImplementation(), signatureHashAlgorithm, aeadProtection, creationTime); + super(new BcOpenPGPImplementation(), aeadProtection, creationTime); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java index e46afd31b5..9cf43f0413 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java @@ -52,9 +52,9 @@ public OpenPGPV6KeyGenerator generateKey(Date creationTime) } @Override - public OpenPGPV6KeyGenerator generateKey(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + public OpenPGPV6KeyGenerator generateKey(Date creationTime, boolean aeadProtection) throws PGPException { - return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection, provider); + return new JcaOpenPGPV6KeyGenerator(creationTime, aeadProtection, provider); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java index e11f6b462e..34eddc683e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -20,27 +20,19 @@ public JcaOpenPGPV6KeyGenerator(Provider provider) public JcaOpenPGPV6KeyGenerator(Date creationTime, Provider provider) throws PGPException { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true, provider); - } - - public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Provider provider) - throws PGPException - { - this(signatureHashAlgorithm, new Date(), true, provider); + this(creationTime, true, provider); } /** * Create a new OpenPGP key generator for v6 keys. * - * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures */ - public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection, Provider provider) + public JcaOpenPGPV6KeyGenerator(Date creationTime, boolean aeadProtection, Provider provider) throws PGPException { super( new JcaOpenPGPImplementation(provider, new SecureRandom()), - signatureHashAlgorithm, aeadProtection, creationTime); } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 684e82d777..5ef861edbe 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -2,13 +2,16 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPImplementation; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.SignatureParameters; +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; @@ -22,6 +25,8 @@ public class BcOpenPGPV6KeyGeneratorTest extends AbstractPgpKeyPairTest { + private final OpenPGPApi api = new BcOpenPGPApi(); + @Override public String getName() { @@ -36,21 +41,24 @@ public void performTest() } private void testGenerateMinimalKey() - throws PGPException, IOException { + throws PGPException, IOException + { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( - new BcOpenPGPImplementation(), HashAlgorithmTags.SHA3_512, false, creationTime); + OpenPGPV6KeyGenerator gen = api.generateKey(creationTime, false); OpenPGPKey key = gen.withPrimaryKey( - PGPKeyPairGenerator::generateEd25519KeyPair, - subpackets -> + PGPKeyPairGenerator::generateEd25519KeyPair, + SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() { - subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); - return subpackets; - }, - "hello".toCharArray()) + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); + return subpackets; + } + })) .addUserId("Alice ") - .addEncryptionSubkey((char[]) null) - .addSigningSubkey((char[]) null) + .addEncryptionSubkey() + .addSigningSubkey() .build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index bc93843460..2d8a065520 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -17,6 +17,7 @@ import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.util.UTCUtil; @@ -807,7 +808,7 @@ private void testGetPrimaryUserId() OpenPGPKey key = gen.withPrimaryKey() .addUserId("Old non-primary ") .addUserId("New primary ", - new SignatureSubpacketsFunction() + SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) @@ -817,7 +818,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa subpackets.setPrimaryUserID(false, true); return subpackets; } - }) + })) .build(null); isEquals("Expect to find valid, explicit primary user ID", key.getUserId("New primary "), diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index e1bed98e18..18a588aa2d 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -67,7 +67,8 @@ private void doNothingTest(OpenPGPApi api) throws PGPException { OpenPGPKey key = api.generateKey() - .ed25519x25519Key("Alice ", null); + .ed25519x25519Key("Alice ") + .build(); OpenPGPKey editedKey = api.editKey(key) .done(); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 7ed7efe239..684ff3ba82 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -22,11 +22,14 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; @@ -48,56 +51,35 @@ public void performTest() throws Exception { // Run tests using the BC implementation - performTests(new APIProvider() - { - @Override - public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, - Date creationTime, - boolean aeadProtection) - throws PGPException - { - return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); - } - }); + performTestsWith(new BcOpenPGPApi()); // Run tests using the JCA/JCE implementation - performTests(new APIProvider() - { - @Override - public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, - Date creationTime, - boolean aeadProtection) - throws PGPException - { - return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection, - new BouncyCastleProvider()); - } - }); + performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); } - private void performTests(APIProvider apiProvider) + private void performTestsWith(OpenPGPApi api) throws PGPException, IOException { - testGenerateCustomKey(apiProvider); + testGenerateCustomKey(api); - testGenerateSignOnlyKeyBaseCase(apiProvider); - testGenerateAEADProtectedSignOnlyKey(apiProvider); - testGenerateCFBProtectedSignOnlyKey(apiProvider); + testGenerateSignOnlyKeyBaseCase(api); + testGenerateAEADProtectedSignOnlyKey(api); + testGenerateCFBProtectedSignOnlyKey(api); - testGenerateClassicKeyBaseCase(apiProvider); - testGenerateProtectedTypicalKey(apiProvider); + testGenerateClassicKeyBaseCase(api); + testGenerateProtectedTypicalKey(api); - testGenerateEd25519x25519Key(apiProvider); - testGenerateEd448x448Key(apiProvider); + testGenerateEd25519x25519Key(api); + testGenerateEd448x448Key(api); - testEnforcesPrimaryOrSubkeyType(apiProvider); + testEnforcesPrimaryOrSubkeyType(api); } - private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) + private void testGenerateSignOnlyKeyBaseCase(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(); - OpenPGPKey key = generator.signOnlyKey(null); + OpenPGPV6KeyGenerator generator = api.generateKey(); + OpenPGPKey key = generator.signOnlyKey().build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); @@ -121,11 +103,11 @@ private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) isEquals("Key MUST be unprotected", SecretKeyPacket.USAGE_NONE, primaryKey.getS2KUsage()); } - private void testGenerateAEADProtectedSignOnlyKey(APIProvider apiProvider) + private void testGenerateAEADProtectedSignOnlyKey(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(true); - OpenPGPKey key = generator.signOnlyKey("passphrase".toCharArray()); + OpenPGPV6KeyGenerator generator = api.generateKey(new Date(), true); + OpenPGPKey key = generator.signOnlyKey().build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); @@ -139,11 +121,11 @@ private void testGenerateAEADProtectedSignOnlyKey(APIProvider apiProvider) .build("passphrase".toCharArray()))); } - private void testGenerateCFBProtectedSignOnlyKey(APIProvider apiProvider) + private void testGenerateCFBProtectedSignOnlyKey(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(false); - OpenPGPKey key = generator.signOnlyKey("passphrase".toCharArray()); + OpenPGPV6KeyGenerator generator = api.generateKey(new Date(), false); + OpenPGPKey key = generator.signOnlyKey().build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator it = secretKeys.getSecretKeys(); @@ -157,13 +139,13 @@ private void testGenerateCFBProtectedSignOnlyKey(APIProvider apiProvider) .build("passphrase".toCharArray()))); } - private void testGenerateClassicKeyBaseCase(APIProvider apiProvider) + private void testGenerateClassicKeyBaseCase(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); + OpenPGPV6KeyGenerator generator = api.generateKey(creationTime); OpenPGPKey key = generator - .classicKey("Alice ", null); + .classicKey("Alice ").build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); Iterator keys = secretKeys.getSecretKeys(); @@ -218,13 +200,13 @@ private void testGenerateClassicKeyBaseCase(APIProvider apiProvider) } } - private void testGenerateProtectedTypicalKey(APIProvider apiProvider) + private void testGenerateProtectedTypicalKey(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); + OpenPGPV6KeyGenerator generator = api.generateKey(creationTime); OpenPGPKey key = generator - .classicKey("Alice ", "passphrase".toCharArray()); + .classicKey("Alice ").build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); // Test creation time @@ -253,14 +235,14 @@ private void testGenerateProtectedTypicalKey(APIProvider apiProvider) } } - private void testGenerateEd25519x25519Key(APIProvider apiProvider) + private void testGenerateEd25519x25519Key(OpenPGPApi api) throws PGPException { Date currentTime = currentTimeRounded(); String userId = "Foo "; - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); + OpenPGPV6KeyGenerator generator = api.generateKey(currentTime); - OpenPGPKey key = generator.ed25519x25519Key(userId, null); + OpenPGPKey key = generator.ed25519x25519Key(userId).build(); PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator iterator = secretKey.getSecretKeys(); @@ -301,14 +283,14 @@ private void testGenerateEd25519x25519Key(APIProvider apiProvider) isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags()); } - private void testGenerateEd448x448Key(APIProvider apiProvider) + private void testGenerateEd448x448Key(OpenPGPApi api) throws PGPException { Date currentTime = currentTimeRounded(); String userId = "Foo "; - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); + OpenPGPV6KeyGenerator generator = api.generateKey(currentTime); - OpenPGPKey key = generator.ed448x448Key(userId, null); + OpenPGPKey key = generator.ed448x448Key(userId).build(); PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator iterator = secretKey.getSecretKeys(); @@ -349,61 +331,61 @@ private void testGenerateEd448x448Key(APIProvider apiProvider) isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags()); } - private void testGenerateCustomKey(APIProvider apiProvider) + private void testGenerateCustomKey(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); + OpenPGPV6KeyGenerator generator = api.generateKey(creationTime, false); OpenPGPKey key = generator .withPrimaryKey( - new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException + new KeyPairGeneratorCallback() { - return generator.generateRsaKeyPair(4096); - } - }, - new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateRsaKeyPair(4096); + } + }, + SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() { - subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); - subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER); + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER); - subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); - subpackets.setFeature(false, Features.FEATURE_SEIPD_V2); + subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); + subpackets.setFeature(false, Features.FEATURE_SEIPD_V2); - subpackets.addNotationData(false, true, - "notation@example.com", "CYBER"); + subpackets.addNotationData(false, true, + "notation@example.com", "CYBER"); - subpackets.setPreferredKeyServer(false, "https://example.com/openpgp/cert.asc"); - return subpackets; - } - }, - "primary-key-passphrase".toCharArray()) - .addUserId("Alice ", PGPSignature.DEFAULT_CERTIFICATION, null) + subpackets.setPreferredKeyServer(false, "https://example.com/openpgp/cert.asc"); + return subpackets; + } + })) + .addUserId("Alice ") .addSigningSubkey( - new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException + new KeyPairGeneratorCallback() { - return generator.generateEd448KeyPair(); - } - }, - new SignatureSubpacketsFunction() - { - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator bindingSubpackets) + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateEd448KeyPair(); + } + }, + SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() { - bindingSubpackets.addNotationData(false, true, - "notation@example.com", "ZAUBER"); - return bindingSubpackets; - } - }, - null, - "signing-key-passphrase".toCharArray()) + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.addNotationData(false, true, + "notation@example.com", "ZAUBER"); + return subpackets; + } + }), + null) .addEncryptionSubkey( new KeyPairGeneratorCallback() { @@ -412,9 +394,20 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) { return generator.generateX448KeyPair(); } - }, - "encryption-key-passphrase".toCharArray()) - .build(); + }) + .build("primary-key-passphrase".toCharArray()); + OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getEncryptionKeys().get(0); + OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0); + key = api.editKey(key) + .changePassphrase(encryptionKey, + "primary-key-passphrase".toCharArray(), + "encryption-key-passphrase".toCharArray(), + false) + .changePassphrase(signingKey, + "primary-key-passphrase".toCharArray(), + "signing-key-passphrase".toCharArray(), + false) + .done(); PGPSecretKeyRing secretKey = key.getPGPKeyRing(); Iterator keyIt = secretKey.getSecretKeys(); @@ -439,7 +432,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) isFalse("Unexpected additional UID", uids.hasNext()); PGPSignature uidSig = (PGPSignature)primaryKey.getPublicKey().getSignaturesForID(uid).next(); isEquals("UID binding sig type mismatch", - PGPSignature.DEFAULT_CERTIFICATION, uidSig.getSignatureType()); + PGPSignature.POSITIVE_CERTIFICATION, uidSig.getSignatureType()); PGPSecretKey signingSubkey = (PGPSecretKey)keyIt.next(); isEquals("Subkey MUST be Ed448", @@ -480,7 +473,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) encryptionSubkey.extractPrivateKey(keyDecryptorBuilder.build("encryption-key-passphrase".toCharArray()))); } - private void testEnforcesPrimaryOrSubkeyType(final APIProvider apiProvider) + private void testEnforcesPrimaryOrSubkeyType(final OpenPGPApi api) throws PGPException { isNotNull(testException( @@ -492,7 +485,7 @@ private void testEnforcesPrimaryOrSubkeyType(final APIProvider apiProvider) public void operation() throws Exception { - apiProvider.getKeyGenerator().withPrimaryKey( + api.generateKey().withPrimaryKey( new KeyPairGeneratorCallback() { public PGPKeyPair generateFrom(PGPKeyPairGenerator keyGenCallback) @@ -515,10 +508,12 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator keyGenCallback) public void operation() throws Exception { - apiProvider.getKeyGenerator().withPrimaryKey() - .addEncryptionSubkey(new BcPGPKeyPairGeneratorProvider() - .get(6, new Date()) - .generateX25519KeyPair(), null, null); // primary key as subkey is illegal + api.generateKey().withPrimaryKey() + .addEncryptionSubkey( + new BcPGPKeyPairGeneratorProvider() + .get(6, new Date()) + .generateX25519KeyPair(), + null); // primary key as subkey is illegal } } )); @@ -532,39 +527,18 @@ public void operation() public void operation() throws Exception { - apiProvider.getKeyGenerator().withPrimaryKey() - .addSigningSubkey(new BcPGPKeyPairGeneratorProvider() - .get(6, new Date()) - .generateEd25519KeyPair(), null, null, null); // primary key as subkey is illegal + api.generateKey().withPrimaryKey() + .addSigningSubkey( + new BcPGPKeyPairGeneratorProvider() + .get(6, new Date()) + .generateEd25519KeyPair(), + null, + null); // primary key as subkey is illegal } } )); } - private abstract static class APIProvider - { - public OpenPGPV6KeyGenerator getKeyGenerator() - throws PGPException - { - return getKeyGenerator(new Date()); - } - - public OpenPGPV6KeyGenerator getKeyGenerator(Date creationTime) - throws PGPException - { - return getKeyGenerator(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); - } - - public OpenPGPV6KeyGenerator getKeyGenerator(boolean aeadProtection) - throws PGPException - { - return getKeyGenerator(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, new Date(), aeadProtection); - } - - public abstract OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) - throws PGPException; - } - public static void main(String[] args) { runTest(new OpenPGPV6KeyGeneratorTest()); From 7dc03aa9fd1e2645985b514148cbcadfbd9dd549 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 12 Jan 2025 13:18:30 +0100 Subject: [PATCH 071/154] Unify message generator API --- .../bouncycastle/openpgp/api/OpenPGPKey.java | 5 + .../openpgp/api/OpenPGPKeyEditor.java | 189 +++++++++--------- .../openpgp/api/OpenPGPMessageGenerator.java | 139 ++++++++----- .../openpgp/api/OpenPGPV6KeyGenerator.java | 110 +++++----- .../openpgp/api/SignatureParameters.java | 12 +- .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 2 +- .../api/test/OpenPGPCertificateTest.java | 2 +- .../api/test/OpenPGPV6KeyGeneratorTest.java | 4 +- 8 files changed, 258 insertions(+), 205 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index def65dd79e..7b01bfbf12 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -208,6 +208,11 @@ protected OpenPGPCertificateComponent getPublicComponent() return pubKey; } + public OpenPGPKey getOpenPGPKey() + { + return (OpenPGPKey) getCertificate(); + } + @Override public String toDetailString() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index 3dc310cdeb..fee5bf6fd7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -40,41 +40,44 @@ public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, SignatureParameters.Callback callback) throws PGPException { - SignatureParameters parameters = SignatureParameters.directKeySignatureParameters(policy); + SignatureParameters parameters = SignatureParameters.directKeySignature(policy); if (callback != null) { parameters = callback.apply(parameters); } - PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); - - PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - publicPrimaryKey.getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - publicPrimaryKey); - dkSigGen.init(parameters.getSignatureType(), privatePrimaryKey); - - // Hashed subpackets - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); - hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - dkSigGen.setHashedSubpackets(hashedSubpackets.generate()); - - // Unhashed subpackets - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - dkSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - // Inject signature into the certificate - PGPSignature dkSig = dkSigGen.generateCertification(publicPrimaryKey); - PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, dkSig); - PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); - PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); - - this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + if (parameters != null) + { + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + dkSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + dkSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + dkSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPSignature dkSig = dkSigGen.generateCertification(publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, dkSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } return this; } @@ -94,41 +97,44 @@ public OpenPGPKeyEditor addUserId(String userId, throw new IllegalArgumentException("User-ID cannot be null or empty."); } - SignatureParameters parameters = SignatureParameters.certificationSignatureParameters(policy); + SignatureParameters parameters = SignatureParameters.certification(policy); if (callback != null) { parameters = callback.apply(parameters); } - PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); - - PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - publicPrimaryKey.getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - publicPrimaryKey); - uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); - - // Hashed subpackets - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); - hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); - - // Unhashed subpackets - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - // Inject UID and signature into the certificate - PGPSignature uidSig = uidSigGen.generateCertification(userId, publicPrimaryKey); - PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId, uidSig); - PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); - PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); - - this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + if (parameters != null) + { + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject UID and signature into the certificate + PGPSignature uidSig = uidSigGen.generateCertification(userId, publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId, uidSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } return this; } @@ -186,41 +192,44 @@ public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, throw new IllegalArgumentException("UserID is not part of the certificate."); } - SignatureParameters parameters = SignatureParameters.certificationRevocationSignatureParameters(policy); + SignatureParameters parameters = SignatureParameters.certificationRevocation(policy); if (callback != null) { parameters = callback.apply(parameters); } - PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); - - PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - publicPrimaryKey.getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - publicPrimaryKey); - uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); - - // Hashed subpackets - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); - hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); - - // Unhashed subpackets - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - // Inject signature into the certificate - PGPSignature uidSig = uidSigGen.generateCertification(userId.getUserId(), publicPrimaryKey); - PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId.getUserId(), uidSig); - PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); - PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); - - this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + if (parameters != null) + { + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); + + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPSignature uidSig = uidSigGen.generateCertification(userId.getUserId(), publicPrimaryKey); + PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId.getUserId(), uidSig); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } return this; } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 428cddf4e3..deee1e59dc 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -23,6 +23,7 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.api.exception.InvalidEncryptionKeyException; +import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; @@ -96,11 +97,26 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip SubkeySelector subkeySelector) throws InvalidEncryptionKeyException { - if (subkeySelector.select(recipientCertificate, config.policy).isEmpty()) + List encryptionKeys = + subkeySelector.select(recipientCertificate, config.policy); + if (encryptionKeys.isEmpty()) { - throw new InvalidEncryptionKeyException("Key does not have valid encryption subkeys."); + throw new InvalidEncryptionKeyException("Certificate " + recipientCertificate.getKeyIdentifier() + + " does not have valid encryption subkeys."); } - config.recipients.add(new Recipient(recipientCertificate, implementation.policy(), subkeySelector)); + config.encryptionKeys.addAll(encryptionKeys); + return this; + } + + public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenPGPComponentKey encryptionKey) + throws InvalidEncryptionKeyException + { + if (!encryptionKey.isEncryptionKey()) + { + throw new InvalidEncryptionKeyException("Provided subkey " + encryptionKey.getKeyIdentifier() + + " is not a valid encryption key."); + } + config.encryptionKeys.add(encryptionKey); return this; } @@ -113,11 +129,12 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip */ public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) { - config.passphrases.add(passphrase); + config.messagePassphrases.add(passphrase); return this; } public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) + throws InvalidSigningKeyException { return addSigningKey(signingKey, key -> null); } @@ -134,6 +151,7 @@ public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) public OpenPGPMessageGenerator addSigningKey( OpenPGPKey signingKey, SecretKeyPassphraseProvider signingKeyDecryptorProvider) + throws InvalidSigningKeyException { return addSigningKey(signingKey, signingKeyDecryptorProvider, config.signingKeySelector); } @@ -150,8 +168,47 @@ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey signingKey, SecretKeyPassphraseProvider signingKeyDecryptorProvider, SubkeySelector subkeySelector) + throws InvalidSigningKeyException { - config.signingKeys.add(new Signer(signingKey, implementation.policy(), signingKeyDecryptorProvider, subkeySelector)); + + List publicSigningKeys = + subkeySelector.select(signingKey, implementation.policy()); + + List signingKeys = new ArrayList<>(); + for (OpenPGPCertificate.OpenPGPComponentKey publicKey : publicSigningKeys) + { + OpenPGPKey.OpenPGPSecretKey secretKey = signingKey.getSecretKey(publicKey); + if (secretKey == null) + { + throw new InvalidSigningKeyException("Secret key " + publicKey.getKeyIdentifier() + " is missing from the OpenPGP key."); + } + signingKeys.add(secretKey); + } + + if (signingKeys.isEmpty()) + { + throw new InvalidSigningKeyException("OpenPGP key " + signingKey.getKeyIdentifier() + " does not have any valid signing subkeys."); + } + + for (OpenPGPKey.OpenPGPSecretKey subkey : signingKeys) + { + config.signingKeys.add(new Signer(subkey, signingKeyDecryptorProvider, null)); + } + return this; + } + + public OpenPGPMessageGenerator addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + SecretKeyPassphraseProvider signingKeyPassphraseProvider, + SignatureParameters.Callback signatureParameterCallback) + throws InvalidSigningKeyException + { + if (!signingKey.isSigningKey()) + { + throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + " is not a valid signing key."); + } + + config.signingKeys.add(new Signer(signingKey, signingKeyPassphraseProvider, signatureParameterCallback)); return this; } @@ -266,18 +323,15 @@ private void applyOptionalEncryption( encGen.setSessionKeyExtractionCallback(sessionKeyExtractionCallback); // Setup asymmetric message encryption - for (Recipient recipient : config.recipients) + for (OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey : config.encryptionKeys) { - for (OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey : recipient.encryptionSubkeys()) - { - PublicKeyKeyEncryptionMethodGenerator method = implementation.publicKeyKeyEncryptionMethodGenerator( - encryptionSubkey.getPGPPublicKey()); - encGen.addMethod(method); - } + PublicKeyKeyEncryptionMethodGenerator method = implementation.publicKeyKeyEncryptionMethodGenerator( + encryptionSubkey.getPGPPublicKey()); + encGen.addMethod(method); } // Setup symmetric (password-based) message encryption - for (char[] passphrase : config.passphrases) + for (char[] passphrase : config.messagePassphrases) { PBEKeyEncryptionMethodGenerator skeskGen; switch (encryption.getMode()) @@ -331,19 +385,17 @@ private void applySignatures(OpenPGPMessageOutputStream.Builder builder) Stack signatureGenerators = new Stack<>(); for (Signer s : config.signingKeys) { - for (OpenPGPKey.OpenPGPSecretKey signingSubkey : s.signingSubkeys()) - { - int hashAlgorithm = config.negotiateHashAlgorithm(s.signingKey, signingSubkey); - PGPSignatureGenerator sigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), hashAlgorithm), - signingSubkey.getPGPSecretKey().getPublicKey()); - char[] passphrase = signingSubkey.isLocked() ? s.passphraseProvider.providePassphrase(signingSubkey) : null; - PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); - - sigGen.init(PGPSignature.BINARY_DOCUMENT, privateKey); - signatureGenerators.push(sigGen); - } + OpenPGPKey.OpenPGPSecretKey signingSubkey = s.signingKey; + int hashAlgorithm = config.negotiateHashAlgorithm(signingSubkey.getOpenPGPKey(), signingSubkey); + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), hashAlgorithm), + signingSubkey.getPGPSecretKey().getPublicKey()); + char[] passphrase = signingSubkey.isLocked() ? s.passphraseProvider.providePassphrase(signingSubkey) : null; + PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); + + sigGen.init(PGPSignature.BINARY_DOCUMENT, privateKey); + signatureGenerators.push(sigGen); } // One-Pass-Signatures @@ -445,9 +497,9 @@ public static class Configuration { private boolean isArmored = true; public boolean isPadded = true; - private final List recipients = new ArrayList<>(); + private final List encryptionKeys = new ArrayList<>(); private final List signingKeys = new ArrayList<>(); - private final List passphrases = new ArrayList<>(); + private final List messagePassphrases = new ArrayList<>(); private final OpenPGPPolicy policy; public Configuration(OpenPGPPolicy policy) @@ -495,8 +547,9 @@ public List select(OpenPGPCertificate ce // Encryption method negotiator for when public-key encryption is requested private OpenPGPEncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> { - List certificates = recipients.stream() - .map(it -> it.certificate) + List certificates = encryptionKeys.stream() + .map(OpenPGPCertificate.OpenPGPCertificateComponent::getCertificate) + .distinct() .collect(Collectors.toList()); // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. @@ -523,13 +576,13 @@ else if (OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificate configuration -> { // No encryption methods provided -> Unencrypted message - if (configuration.recipients.isEmpty() && configuration.passphrases.isEmpty()) + if (configuration.encryptionKeys.isEmpty() && configuration.messagePassphrases.isEmpty()) { return MessageEncryptionMechanism.unencrypted(); } // No public-key encryption requested -> password-based encryption - else if (configuration.recipients.isEmpty()) + else if (configuration.encryptionKeys.isEmpty()) { // delegate negotiation to pbe negotiator return passwordBasedEncryptionNegotiator.negotiateEncryption(configuration); @@ -728,29 +781,17 @@ public List encryptionSubkeys() */ static class Signer { - private final OpenPGPKey signingKey; - private final OpenPGPPolicy policy; + private final OpenPGPKey.OpenPGPSecretKey signingKey; private final SecretKeyPassphraseProvider passphraseProvider; - private final SubkeySelector subkeySelector; + private final SignatureParameters.Callback signatureParameters; - public Signer(OpenPGPKey signingKey, - OpenPGPPolicy policy, + public Signer(OpenPGPKey.OpenPGPSecretKey signingKey, SecretKeyPassphraseProvider passphraseProvider, - SubkeySelector subkeySelector) + SignatureParameters.Callback signatureParameters) { this.signingKey = signingKey; - this.policy = policy; this.passphraseProvider = passphraseProvider; - this.subkeySelector = subkeySelector; - } - - public List signingSubkeys() - { - return subkeySelector.select(signingKey, policy) - .stream() - .map(signingKey::getSecretKey) - .distinct() - .collect(Collectors.toList()); + this.signatureParameters = signatureParameters; } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index b61202aebc..a1edd816db 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -210,9 +210,9 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) public WithPrimaryKey signOnlyKey() throws PGPException { - WithPrimaryKey builder = withPrimaryKey( + return withPrimaryKey( generatePrimaryKey, - SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) @@ -222,8 +222,6 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa return subpackets; } })); - - return builder; } /** @@ -275,45 +273,42 @@ public WithPrimaryKey withPrimaryKey( throw new PGPException("Primary key MUST use signing-capable algorithm."); } - SignatureParameters parameters = SignatureParameters.directKeySignatureParameters( + SignatureParameters parameters = SignatureParameters.directKeySignature( implementationProvider.policy()); if (preferenceSignatureCallback != null) { parameters = preferenceSignatureCallback.apply(parameters); } - if (parameters == null) + if (parameters != null) { - // Do not generate Direct-Key Signature for preferences - return new WithPrimaryKey(implementationProvider, configuration, primaryKeyPair); - } + PGPSignatureGenerator preferenceSigGen = new PGPSignatureGenerator( + implementationProvider.pgpContentSignerBuilder( + primaryKeyPair.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKeyPair.getPublicKey()); + preferenceSigGen.init(parameters.getSignatureType(), primaryKeyPair.getPrivateKey()); - PGPSignatureGenerator preferenceSigGen = new PGPSignatureGenerator( - implementationProvider.pgpContentSignerBuilder( - primaryKeyPair.getPublicKey().getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - primaryKeyPair.getPublicKey()); - preferenceSigGen.init(parameters.getSignatureType(), primaryKeyPair.getPrivateKey()); - - // Hashed subpackets - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); - hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); - hashedSubpackets = directKeySignatureSubpackets.apply(hashedSubpackets); - hashedSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); - hashedSubpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - preferenceSigGen.setHashedSubpackets(hashedSubpackets.generate()); - - // Unhashed subpackets - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - preferenceSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - PGPSignature dkSig = preferenceSigGen.generateCertification(primaryKeyPair.getPublicKey()); - primaryKeyPair = new PGPKeyPair( - PGPPublicKey.addCertification(primaryKeyPair.getPublicKey(), dkSig), - primaryKeyPair.getPrivateKey()); + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); + hashedSubpackets = directKeySignatureSubpackets.apply(hashedSubpackets); + hashedSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); + hashedSubpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + preferenceSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + preferenceSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + PGPSignature dkSig = preferenceSigGen.generateCertification(primaryKeyPair.getPublicKey()); + primaryKeyPair = new PGPKeyPair( + PGPPublicKey.addCertification(primaryKeyPair.getPublicKey(), dkSig), + primaryKeyPair.getPrivateKey()); + } return new WithPrimaryKey(implementationProvider, configuration, primaryKeyPair); } @@ -374,32 +369,35 @@ public WithPrimaryKey addUserId( throw new IllegalArgumentException("User-ID cannot be null or empty."); } - SignatureParameters parameters = SignatureParameters.certificationSignatureParameters(implementation.policy()); + SignatureParameters parameters = SignatureParameters.certification(implementation.policy()); if (signatureParameters != null) { parameters = signatureParameters.apply(parameters); } - PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - primaryKey.getPublicKey().getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - primaryKey.getPublicKey()); - uidSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + if (parameters != null) + { + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + primaryKey.getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + primaryKey.getPublicKey()); + uidSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, primaryKey.getPublicKey()); - hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, primaryKey.getPublicKey()); + hashedSubpackets.setSignatureCreationTime(configuration.keyCreationTime); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.getPublicKey()); - PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.getPublicKey(), userId, uidSig); - primaryKey = new PGPKeyPair(pubKey, primaryKey.getPrivateKey()); + PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.getPublicKey()); + PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.getPublicKey(), userId, uidSig); + primaryKey = new PGPKeyPair(pubKey, primaryKey.getPrivateKey()); + } return this; } @@ -490,7 +488,7 @@ public WithPrimaryKey addEncryptionSubkey( throw new PGPException("Encryption key MUST use encryption-capable algorithm."); } - SignatureParameters parameters = SignatureParameters.subkeyBindingSignatureParameters( + SignatureParameters parameters = SignatureParameters.subkeyBinding( implementation.policy()) .setSignatureCreationTime(configuration.keyCreationTime); if (bindingSubpacketsCallback != null) @@ -629,7 +627,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, throw new PGPException("Signing key MUST use signing-capable algorithm."); } - SignatureParameters parameters = SignatureParameters.primaryKeyBindingSignatureParameters( + SignatureParameters parameters = SignatureParameters.primaryKeyBinding( implementation.policy()) .setSignatureCreationTime(configuration.keyCreationTime); if (backSignatureCallback != null) @@ -664,7 +662,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, } - parameters = SignatureParameters.subkeyBindingSignatureParameters(implementation.policy()) + parameters = SignatureParameters.subkeyBinding(implementation.policy()) .setSignatureCreationTime(configuration.keyCreationTime); if (bindingSignatureCallback != null) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index 5224815e42..5cfaa6fa0c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -21,7 +21,7 @@ private SignatureParameters(int... allowedSignatureTypes) this.allowedSignatureTypes = allowedSignatureTypes; } - public static SignatureParameters directKeySignatureParameters(OpenPGPPolicy policy) + public static SignatureParameters directKeySignature(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.DIRECT_KEY) .setSignatureType(PGPSignature.DIRECT_KEY) @@ -29,7 +29,7 @@ public static SignatureParameters directKeySignatureParameters(OpenPGPPolicy pol .setSignatureCreationTime(new Date()); } - public static SignatureParameters certificationSignatureParameters(OpenPGPPolicy policy) + public static SignatureParameters certification(OpenPGPPolicy policy) { return new SignatureParameters( PGPSignature.DEFAULT_CERTIFICATION, @@ -41,7 +41,7 @@ public static SignatureParameters certificationSignatureParameters(OpenPGPPolicy .setSignatureCreationTime(new Date()); } - public static SignatureParameters subkeyBindingSignatureParameters(OpenPGPPolicy policy) + public static SignatureParameters subkeyBinding(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.SUBKEY_BINDING) .setSignatureType(PGPSignature.SUBKEY_BINDING) @@ -49,7 +49,7 @@ public static SignatureParameters subkeyBindingSignatureParameters(OpenPGPPolicy .setSignatureCreationTime(new Date()); } - public static SignatureParameters primaryKeyBindingSignatureParameters(OpenPGPPolicy policy) + public static SignatureParameters primaryKeyBinding(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.PRIMARYKEY_BINDING) .setSignatureType(PGPSignature.PRIMARYKEY_BINDING) @@ -57,7 +57,7 @@ public static SignatureParameters primaryKeyBindingSignatureParameters(OpenPGPPo .setSignatureCreationTime(new Date()); } - public static SignatureParameters certificationRevocationSignatureParameters(OpenPGPPolicy policy) + public static SignatureParameters certificationRevocation(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.CERTIFICATION_REVOCATION) .setSignatureType(PGPSignature.CERTIFICATION_REVOCATION) @@ -140,7 +140,7 @@ default SignatureParameters apply(SignatureParameters parameters) return parameters; } - static Callback applyToHashedSubpackets(SignatureSubpacketsFunction function) + static Callback modifyHashedSubpackets(SignatureSubpacketsFunction function) { return new Callback() { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 5ef861edbe..2238ca37b4 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -47,7 +47,7 @@ private void testGenerateMinimalKey() OpenPGPV6KeyGenerator gen = api.generateKey(creationTime, false); OpenPGPKey key = gen.withPrimaryKey( PGPKeyPairGenerator::generateEd25519KeyPair, - SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index 2d8a065520..d8a0671fbf 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -808,7 +808,7 @@ private void testGetPrimaryUserId() OpenPGPKey key = gen.withPrimaryKey() .addUserId("Old non-primary ") .addUserId("New primary ", - SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 684ff3ba82..4d4f315120 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -347,7 +347,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) return generator.generateRsaKeyPair(4096); } }, - SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) @@ -375,7 +375,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) return generator.generateEd448KeyPair(); } }, - SignatureParameters.Callback.applyToHashedSubpackets(new SignatureSubpacketsFunction() + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) From a6a897b0f801bc3293a9cfbd6ea1fd19b0453ca1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 12:01:41 +0100 Subject: [PATCH 072/154] Add javadoc and allow use of SignatureParameters in message generator --- .../openpgp/api/OpenPGPMessageGenerator.java | 133 +++++++++--------- .../openpgp/api/SignatureParameters.java | 8 ++ 2 files changed, 78 insertions(+), 63 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index deee1e59dc..484a84b67b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -18,10 +18,8 @@ import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPPadding; import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.InvalidEncryptionKeyException; import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; @@ -92,6 +90,7 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip * @param recipientCertificate recipient certificate (public key) * @param subkeySelector selector for encryption subkeys * @return this + * @throws InvalidEncryptionKeyException if the certificate is not capable of encryption */ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate, SubkeySelector subkeySelector) @@ -108,6 +107,14 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip return this; } + /** + * Add a (sub-)key to the set of recipient encryption keys. + * The recipient will be able to decrypt the message using their corresponding secret key. + * + * @param encryptionKey encryption capable subkey + * @return this + * @throws InvalidEncryptionKeyException if the key is not capable of encryption + */ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenPGPComponentKey encryptionKey) throws InvalidEncryptionKeyException { @@ -133,46 +140,38 @@ public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) return this; } - public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) - throws InvalidSigningKeyException - { - return addSigningKey(signingKey, key -> null); - } - /** * Sign the message using a secret signing key. * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by * calling {@link Configuration#setSigningKeySelector(SubkeySelector)}. * * @param signingKey OpenPGP key - * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. * @return this + * @throws InvalidSigningKeyException if the key is not capable of signing messages */ - public OpenPGPMessageGenerator addSigningKey( - OpenPGPKey signingKey, - SecretKeyPassphraseProvider signingKeyDecryptorProvider) + public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) throws InvalidSigningKeyException { - return addSigningKey(signingKey, signingKeyDecryptorProvider, config.signingKeySelector); + return addSigningKey(signingKey, key -> null); } /** * Sign the message using a secret signing key. + * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by + * calling {@link Configuration#setSigningKeySelector(SubkeySelector)}. * * @param signingKey OpenPGP key * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. - * @param subkeySelector selector for selecting signing subkey(s) * @return this + * @throws InvalidSigningKeyException if the key is not capable of signing messages */ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey signingKey, - SecretKeyPassphraseProvider signingKeyDecryptorProvider, - SubkeySelector subkeySelector) + SecretKeyPassphraseProvider signingKeyDecryptorProvider) throws InvalidSigningKeyException { - List publicSigningKeys = - subkeySelector.select(signingKey, implementation.policy()); + config.signingKeySelector.select(signingKey, implementation.policy()); List signingKeys = new ArrayList<>(); for (OpenPGPCertificate.OpenPGPComponentKey publicKey : publicSigningKeys) @@ -197,6 +196,19 @@ public OpenPGPMessageGenerator addSigningKey( return this; } + /** + * Sign the message using a signing-capable (sub-)key. + * If the signing key is protected with a passphrase, the given {@link SecretKeyPassphraseProvider} can be + * used to unlock the key. + * The signature can be customized by providing a {@link SignatureParameters.Callback}, which can change + * the signature type, creation time and signature subpackets. + * + * @param signingKey signing-capable subkey + * @param signingKeyPassphraseProvider provider for key passphrases + * @param signatureParameterCallback callback to modify the signature + * @return this + * @throws InvalidSigningKeyException if the key cannot be used for signing + */ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, SecretKeyPassphraseProvider signingKeyPassphraseProvider, @@ -224,6 +236,11 @@ public OpenPGPMessageGenerator setArmored(boolean armored) return this; } + /** + * Set metadata (filename, modification date, binary format) from a file. + * @param file file + * @return this + */ public OpenPGPMessageGenerator setFileMetadata(File file) { this.filename = file.getName(); @@ -232,6 +249,13 @@ public OpenPGPMessageGenerator setFileMetadata(File file) return this; } + /** + * Set a callback which fires once the session key for message encryption is known. + * This callback can be used to extract the session key, e.g. to emit it to the user (in case of SOP). + * + * @param callback callback + * @return this + */ public OpenPGPMessageGenerator setSessionKeyExtractionCallback( PGPEncryptedDataGenerator.SessionKeyExtractionCallback callback) { @@ -386,15 +410,39 @@ private void applySignatures(OpenPGPMessageOutputStream.Builder builder) for (Signer s : config.signingKeys) { OpenPGPKey.OpenPGPSecretKey signingSubkey = s.signingKey; - int hashAlgorithm = config.negotiateHashAlgorithm(signingSubkey.getOpenPGPKey(), signingSubkey); + + SignatureParameters parameters = SignatureParameters.dataSignature(config.policy) + .setSignatureCreationTime(new Date()) + .setSignatureHashAlgorithm( + config.negotiateHashAlgorithm(signingSubkey.getOpenPGPKey(), signingSubkey)); + if (s.signatureParameters != null) + { + parameters = s.signatureParameters.apply(parameters); + } + + if (parameters == null) + { + throw new IllegalStateException("SignatureParameters callback MUST NOT return null."); + } + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( implementation.pgpContentSignerBuilder( - signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), hashAlgorithm), + signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), signingSubkey.getPGPSecretKey().getPublicKey()); char[] passphrase = signingSubkey.isLocked() ? s.passphraseProvider.providePassphrase(signingSubkey) : null; PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); - sigGen.init(PGPSignature.BINARY_DOCUMENT, privateKey); + sigGen.init(parameters.getSignatureType(), privateKey); + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + hashedSubpackets.setIssuerFingerprint(true, signingSubkey.getPGPSecretKey().getPublicKey()); + sigGen.setHashedSubpackets(hashedSubpackets.generate()); + + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + signatureGenerators.push(sigGen); } @@ -735,47 +783,6 @@ public MessageEncryptionMechanism negotiateEncryption() } } - /** - * Tuple representing a recipients OpenPGP certificate. - */ - static class Recipient - { - private final OpenPGPCertificate certificate; - private final OpenPGPPolicy policy; - private final SubkeySelector subkeySelector; - - /** - * Create a {@link Recipient}. - * - * @param certificate OpenPGP certificate (public key) - * @param subkeySelector selector to select encryption-capable subkeys from the certificate - */ - public Recipient(PGPPublicKeyRing certificate, SubkeySelector subkeySelector, OpenPGPImplementation implementation) - { - this(new OpenPGPCertificate(certificate, implementation), implementation.policy(), subkeySelector); - } - - public Recipient(OpenPGPCertificate certificate, OpenPGPPolicy policy, SubkeySelector subkeySelector) - { - this.certificate = certificate; - this.policy = policy; - this.subkeySelector = subkeySelector; - } - - /** - * Return a set of {@link PGPPublicKey subkeys} which will be used for message encryption. - * - * @return encryption capable subkeys for this recipient - */ - public List encryptionSubkeys() - { - return subkeySelector.select(certificate, policy) - .stream() - .distinct() - .collect(Collectors.toList()); - } - } - /** * Tuple representing an OpenPGP key used for signing. */ diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index 5cfaa6fa0c..9e22c9fd39 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -65,6 +65,14 @@ public static SignatureParameters certificationRevocation(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + public static SignatureParameters dataSignature(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.BINARY_DOCUMENT, PGPSignature.CANONICAL_TEXT_DOCUMENT) + .setSignatureType(PGPSignature.BINARY_DOCUMENT) + .setSignatureHashAlgorithm(policy.getDefaultDocumentSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + public SignatureParameters setSignatureType(int signatureType) { if (!Arrays.contains(allowedSignatureTypes, signatureType)) From a45c22a1dc5524df219c903efb86fa15b5cb896b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 12:02:00 +0100 Subject: [PATCH 073/154] Run OpenPGPMessageGeneratorTest with both APIs --- .../api/test/OpenPGPMessageGeneratorTest.java | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java index 818d301371..7580772547 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -2,13 +2,16 @@ import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; -import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; @@ -19,7 +22,6 @@ public class OpenPGPMessageGeneratorTest extends AbstractPacketTest { - private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); @Override public String getName() @@ -31,22 +33,29 @@ public String getName() public void performTest() throws Exception { - armoredLiteralDataPacket(); - unarmoredLiteralDataPacket(); + performTestsWith(new BcOpenPGPApi()); + performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); + } + + private void performTestsWith(OpenPGPApi api) + throws PGPException, IOException + { + armoredLiteralDataPacket(api); + unarmoredLiteralDataPacket(api); - armoredCompressedLiteralDataPacket(); - unarmoredCompressedLiteralDataPacket(); + armoredCompressedLiteralDataPacket(api); + unarmoredCompressedLiteralDataPacket(api); - seipd1EncryptedMessage(); - seipd2EncryptedMessage(); + seipd1EncryptedMessage(api); + seipd2EncryptedMessage(api); - seipd2EncryptedSignedMessage(); + seipd2EncryptedSignedMessage(api); } - private void armoredLiteralDataPacket() + private void armoredLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.setIsPadded(false); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -64,10 +73,10 @@ private void armoredLiteralDataPacket() bOut.toString()); } - private void unarmoredLiteralDataPacket() + private void unarmoredLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.setArmored(false); // disable ASCII armor gen.setIsPadded(false); // disable padding @@ -82,10 +91,10 @@ private void unarmoredLiteralDataPacket() isEncodingEqual(Hex.decode("cb1362000000000048656c6c6f2c20576f726c6421"), bOut.toByteArray()); } - private void armoredCompressedLiteralDataPacket() + private void armoredCompressedLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.setIsPadded(false); OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); configuration.setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); @@ -105,10 +114,10 @@ private void armoredCompressedLiteralDataPacket() bOut.toString()); } - private void unarmoredCompressedLiteralDataPacket() + private void unarmoredCompressedLiteralDataPacket(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.setArmored(false); // no armor gen.setIsPadded(false); OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); @@ -125,12 +134,12 @@ private void unarmoredCompressedLiteralDataPacket() isEncodingEqual(Hex.decode("c815013b2d9cc400021ea93939f93a0ae1f94539298a00"), bOut.toByteArray()); } - private void seipd2EncryptedMessage() + private void seipd2EncryptedMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPCertificate cert = reader.parseCertificate(OpenPGPTestKeys.V6_CERT); + OpenPGPCertificate cert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.V6_CERT); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.addEncryptionCertificate(cert); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); @@ -141,12 +150,12 @@ private void seipd2EncryptedMessage() System.out.println(bOut); } - private void seipd1EncryptedMessage() + private void seipd1EncryptedMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.BOB_KEY); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.addEncryptionCertificate(key); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); @@ -157,12 +166,12 @@ private void seipd1EncryptedMessage() System.out.println(bOut); } - private void seipd2EncryptedSignedMessage() + private void seipd2EncryptedSignedMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setIsPadded(true) .setArmored(true) .addSigningKey(key) From ffb0f5f43fe4917662ba793e8428d2267921578c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 12:30:48 +0100 Subject: [PATCH 074/154] Formatting --- .../openpgp/api/OpenPGPMessageGenerator.java | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 484a84b67b..8c51fa78e9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -38,6 +38,21 @@ import java.util.Stack; import java.util.stream.Collectors; +/** + * Generator for OpenPGP messages. + * This class can generate armored/unarmored, encrypted and/or signed OpenPGP message artifacts. + * By default, the generator will merely pack plaintext into an armored + * {@link org.bouncycastle.bcpg.LiteralDataPacket}. + * If however, the user provides one or more recipient certificates/keys + * ({@link #addEncryptionCertificate(OpenPGPCertificate)} / + * {@link #addEncryptionCertificate(OpenPGPCertificate.OpenPGPComponentKey)}) + * or message passphrases {@link #addEncryptionPassphrase(char[])}, the message will be encrypted. + * The encryption mechanism is automatically decided, based on the provided recipient certificates, aiming to maximize + * interoperability. + * If the user provides one or more signing keys by calling {@link #addSigningKey(OpenPGPKey)} or + * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, SecretKeyPassphraseProvider, SignatureParameters.Callback)}, + * the message will be signed. + */ public class OpenPGPMessageGenerator { public static final int BUFFER_SIZE = 1024; @@ -179,14 +194,16 @@ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey secretKey = signingKey.getSecretKey(publicKey); if (secretKey == null) { - throw new InvalidSigningKeyException("Secret key " + publicKey.getKeyIdentifier() + " is missing from the OpenPGP key."); + throw new InvalidSigningKeyException("Secret key " + publicKey.getKeyIdentifier() + + " is missing from the OpenPGP key."); } signingKeys.add(secretKey); } if (signingKeys.isEmpty()) { - throw new InvalidSigningKeyException("OpenPGP key " + signingKey.getKeyIdentifier() + " does not have any valid signing subkeys."); + throw new InvalidSigningKeyException("OpenPGP key " + signingKey.getKeyIdentifier() + + " does not have any valid signing subkeys."); } for (OpenPGPKey.OpenPGPSecretKey subkey : signingKeys) @@ -217,7 +234,8 @@ public OpenPGPMessageGenerator addSigningKey( { if (!signingKey.isSigningKey()) { - throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + " is not a valid signing key."); + throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + + " is not a valid signing key."); } config.signingKeys.add(new Signer(signingKey, signingKeyPassphraseProvider, signatureParameterCallback)); @@ -368,7 +386,8 @@ private void applyOptionalEncryption( case SEIPDv2: // v6 uses symmetric-key encrypted session key packets version 6 (SKESKv6) using AEAD - skeskGen = implementation.pbeKeyEncryptionMethodGenerator(passphrase, S2K.Argon2Params.memoryConstrainedParameters()); + skeskGen = implementation.pbeKeyEncryptionMethodGenerator( + passphrase, S2K.Argon2Params.memoryConstrainedParameters()); break; default: continue; } @@ -430,7 +449,8 @@ private void applySignatures(OpenPGPMessageOutputStream.Builder builder) signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), parameters.getSignatureHashAlgorithmId()), signingSubkey.getPGPSecretKey().getPublicKey()); - char[] passphrase = signingSubkey.isLocked() ? s.passphraseProvider.providePassphrase(signingSubkey) : null; + char[] passphrase = signingSubkey.isLocked() ? + s.passphraseProvider.providePassphrase(signingSubkey) : null; PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); sigGen.init(parameters.getSignatureType(), privateKey); @@ -538,7 +558,9 @@ public interface CompressionNegotiator public interface HashAlgorithmNegotiator { - int negotiateHashAlgorithm(OpenPGPKey key, OpenPGPCertificate.OpenPGPComponentKey subkey, OpenPGPPolicy policy); + int negotiateHashAlgorithm(OpenPGPKey key, + OpenPGPCertificate.OpenPGPComponentKey subkey, + OpenPGPPolicy policy); } public static class Configuration @@ -615,7 +637,8 @@ else if (OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificate else { return MessageEncryptionMechanism.integrityProtected( - OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight(certificates, configuration.policy)); + OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight( + certificates, configuration.policy)); } }; @@ -666,8 +689,8 @@ else if (configuration.encryptionKeys.isEmpty()) }; /** - * Replace the default {@link OpenPGPEncryptionNegotiator} that gets to decide, which {@link MessageEncryptionMechanism} mode - * to use if only password-based encryption is used. + * Replace the default {@link OpenPGPEncryptionNegotiator} that gets to decide, which + * {@link MessageEncryptionMechanism} mode to use if only password-based encryption is used. * * @param pbeNegotiator custom PBE negotiator. * @return this @@ -679,8 +702,8 @@ public Configuration setPasswordBasedEncryptionNegotiator(OpenPGPEncryptionNegot } /** - * Replace the default {@link OpenPGPEncryptionNegotiator} that decides, which {@link MessageEncryptionMechanism} - * mode to use if public-key encryption is used. + * Replace the default {@link OpenPGPEncryptionNegotiator} that decides, which + * {@link MessageEncryptionMechanism} mode to use if public-key encryption is used. * * @param pkbeNegotiator custom encryption negotiator that gets to decide if PK-based encryption is used * @return this From 8c6f99505be008e7f8139989ecdb991f6e4cd70a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 12:34:18 +0100 Subject: [PATCH 075/154] Move BC-specific key generation test to general key generation tests --- .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 94 ------------------- .../api/test/OpenPGPV6KeyGeneratorTest.java | 40 ++++++++ .../openpgp/test/RegressionTest.java | 2 - 3 files changed, 40 insertions(+), 96 deletions(-) delete mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java deleted file mode 100644 index 2238ca37b4..0000000000 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.bouncycastle.openpgp.api.test; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.bcpg.PacketFormat; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.api.OpenPGPApi; -import org.bouncycastle.openpgp.api.SignatureParameters; -import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.OpenPGPKey; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; -import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Date; -import java.util.Iterator; - -public class BcOpenPGPV6KeyGeneratorTest - extends AbstractPgpKeyPairTest -{ - private final OpenPGPApi api = new BcOpenPGPApi(); - - @Override - public String getName() - { - return "OpenPGPV6KeyGeneratorTest"; - } - - @Override - public void performTest() - throws Exception - { - testGenerateMinimalKey(); - } - - private void testGenerateMinimalKey() - throws PGPException, IOException - { - Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator gen = api.generateKey(creationTime, false); - OpenPGPKey key = gen.withPrimaryKey( - PGPKeyPairGenerator::generateEd25519KeyPair, - SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() - { - @Override - public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) - { - subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); - return subpackets; - } - })) - .addUserId("Alice ") - .addEncryptionSubkey() - .addSigningSubkey() - .build(); - PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); - - // Test creation time - for (PGPPublicKey k : secretKeys.toCertificate()) - { - isEquals(creationTime, k.getCreationTime()); - for (Iterator it = k.getSignatures(); it.hasNext(); ) { - PGPSignature sig = it.next(); - isEquals(creationTime, sig.getCreationTime()); - } - } - - PGPPublicKey primaryKey = secretKeys.getPublicKey(); - // Test UIDs - Iterator uids = primaryKey.getUserIDs(); - isEquals("Alice ", uids.next()); - isFalse(uids.hasNext()); - - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); - BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); - secretKeys.encode(pOut); - pOut.close(); - aOut.close(); - System.out.println(bOut); - } - - public static void main(String[] args) - { - runTest(new BcOpenPGPV6KeyGeneratorTest()); - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 4d4f315120..c254d23cc9 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -61,6 +61,7 @@ private void performTestsWith(OpenPGPApi api) throws PGPException, IOException { testGenerateCustomKey(api); + testGenerateMinimalKey(api); testGenerateSignOnlyKeyBaseCase(api); testGenerateAEADProtectedSignOnlyKey(api); @@ -473,6 +474,45 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) encryptionSubkey.extractPrivateKey(keyDecryptorBuilder.build("encryption-key-passphrase".toCharArray()))); } + private void testGenerateMinimalKey(OpenPGPApi api) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator gen = api.generateKey(creationTime, false); + OpenPGPKey key = gen.withPrimaryKey( + PGPKeyPairGenerator::generateEd25519KeyPair, + SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); + return subpackets; + } + })) + .addUserId("Alice ") + .addEncryptionSubkey() + .addSigningSubkey() + .build(); + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); + + // Test creation time + for (PGPPublicKey k : secretKeys.toCertificate()) + { + isEquals(creationTime, k.getCreationTime()); + for (Iterator it = k.getSignatures(); it.hasNext(); ) { + PGPSignature sig = it.next(); + isEquals(creationTime, sig.getCreationTime()); + } + } + + PGPPublicKey primaryKey = secretKeys.getPublicKey(); + // Test UIDs + Iterator uids = primaryKey.getUserIDs(); + isEquals("Alice ", uids.next()); + isFalse(uids.hasNext()); + } + private void testEnforcesPrimaryOrSubkeyType(final OpenPGPApi api) throws PGPException { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index b9c8b866ea..e7db0b0388 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -7,7 +7,6 @@ import org.bouncycastle.openpgp.api.test.OpenPGPMessageProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; import org.bouncycastle.openpgp.api.test.StaticV6OpenPGPMessageGeneratorTest; -import org.bouncycastle.openpgp.api.test.BcOpenPGPV6KeyGeneratorTest; import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; @@ -93,7 +92,6 @@ public class RegressionTest new PGPKeyPairGeneratorTest(), new OpenPGPV6KeyGeneratorTest(), new PGPKeyRingGeneratorTest(), - new BcOpenPGPV6KeyGeneratorTest(), new OpenPGPMessageGeneratorTest(), new OpenPGPMessageProcessorTest(), From 1b59694510bb87a856638ae956ba86b8fd1d6e37 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 12:59:48 +0100 Subject: [PATCH 076/154] Javadoc and duplication removal --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index a1edd816db..d3929a7f0d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -247,7 +247,14 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) ); } - + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The primary key type can be decided using the {@link KeyPairGeneratorCallback}. + * + * @param keyGenCallback callback to decide the key type + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback) throws PGPException @@ -255,6 +262,19 @@ public WithPrimaryKey withPrimaryKey( return withPrimaryKey(keyGenCallback, null); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The primary key type can be decided using the {@link KeyPairGeneratorCallback}. + * The {@link SignatureParameters.Callback} can be used to modify the preferences in the direct-key self signature. + * If the callback itself is null, the generator will create a default direct-key signature. + * If the result of {@link SignatureParameters.Callback#apply(SignatureParameters)} is null, no direct-key + * signature will be generated for the key. + * + * @param keyGenCallback callback to decide the key type + * @param preferenceSignatureCallback callback to modify the direct-key signature + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback, SignatureParameters.Callback preferenceSignatureCallback) @@ -713,7 +733,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, } /** - * Build the {@link PGPSecretKeyRing OpenPGP key}, allowing individual passphrases for the subkeys. + * Build the {@link PGPSecretKeyRing OpenPGP key} without protecting the secret keys. * * @return OpenPGP key * @throws PGPException if the key cannot be generated @@ -721,29 +741,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, public OpenPGPKey build() throws PGPException { - PGPSecretKey primarySecretKey = new PGPSecretKey( - primaryKey.getPrivateKey(), - primaryKey.getPublicKey(), - configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), - true, - null); - List keys = new ArrayList(); - keys.add(primarySecretKey); - - for (Iterator it = subkeys.iterator(); it.hasNext();) - { - PGPKeyPair key = (PGPKeyPair)it.next(); - PGPSecretKey subkey = new PGPSecretKey( - key.getPrivateKey(), - key.getPublicKey(), - configuration.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), - false, - null); - keys.add(subkey); - } - - PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(keys); - return new OpenPGPKey(secretKeys, implementation); + return build(null); } /** From 7eeaca45d3aa0ff1fec2122d4f1c1fdc5b2fa974 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 13:28:27 +0100 Subject: [PATCH 077/154] Add test for key generation without signatures --- .../api/test/OpenPGPV6KeyGeneratorTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index c254d23cc9..29e60b11b8 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -16,6 +16,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; @@ -74,6 +75,7 @@ private void performTestsWith(OpenPGPApi api) testGenerateEd448x448Key(api); testEnforcesPrimaryOrSubkeyType(api); + testGenerateKeyWithoutSignatures(api); } private void testGenerateSignOnlyKeyBaseCase(OpenPGPApi api) @@ -579,6 +581,90 @@ public void operation() )); } + private void testGenerateKeyWithoutSignatures(OpenPGPApi api) + throws PGPException + { + OpenPGPKey key = api.generateKey() + .withPrimaryKey( + new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generatePrimaryKey(); + } + }, + // No direct-key sig + new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) { + return null; + } + }) + .addSigningSubkey( + new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateSigningSubkey(); + } + }, + // No subkey binding sig + new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + return null; + } + }, + // No primary key binding sig + new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + return null; + } + }) + .addEncryptionSubkey( + new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateEncryptionSubkey(); + } + }, + // No subkey binding sig + new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + return null; + } + }) + .build(); + + PGPPublicKeyRing publicKeys = key.getPGPPublicKeyRing(); + Iterator it = publicKeys.getPublicKeys(); + + PGPPublicKey primaryKey = it.next(); + isFalse(primaryKey.getSignatures().hasNext()); + + PGPPublicKey signingSubkey = it.next(); + isFalse(signingSubkey.getSignatures().hasNext()); + + PGPPublicKey encryptionSubkey = it.next(); + isFalse(encryptionSubkey.getSignatures().hasNext()); + } + public static void main(String[] args) { runTest(new OpenPGPV6KeyGeneratorTest()); From 2b342d5bc3f162a25fb111b3f6fe876bba12240d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 14:21:14 +0100 Subject: [PATCH 078/154] Perform OpenPGPCertificateTests with all APIs --- .../api/test/OpenPGPCertificateTest.java | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index d8a0671fbf..05be82f0f1 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -7,19 +7,21 @@ import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; -import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; @@ -32,7 +34,6 @@ public class OpenPGPCertificateTest extends AbstractPacketTest { - private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); @Override public String getName() @@ -44,20 +45,27 @@ public String getName() public void performTest() throws Exception { - testOpenPGPv6Key(); - - testBaseCasePrimaryKeySigns(); - testBaseCaseSubkeySigns(); - testPKSignsPKRevokedNoSubpacket(); - testSKSignsPKRevokedNoSubpacket(); - testPKSignsPKRevocationSuperseded(); - testGetPrimaryUserId(); + performTestsWith(new BcOpenPGPApi()); + performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); } - private void testOpenPGPv6Key() + private void performTestsWith(OpenPGPApi api) + throws IOException, PGPException + { + testOpenPGPv6Key(api); + + testBaseCasePrimaryKeySigns(api); + testBaseCaseSubkeySigns(api); + testPKSignsPKRevokedNoSubpacket(api); + testSKSignsPKRevokedNoSubpacket(api); + testPKSignsPKRevocationSuperseded(api); + testGetPrimaryUserId(api); + } + + private void testOpenPGPv6Key(OpenPGPApi api) throws IOException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); isTrue("Test key has no identities", key.getIdentities().isEmpty()); @@ -105,7 +113,7 @@ private void testOpenPGPv6Key() encryptionKeyFeatures.getFeatures()); } - private void testBaseCasePrimaryKeySigns() + private void testBaseCasePrimaryKeySigns(OpenPGPApi api) throws IOException { // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__primary_key_signs_and_is_not_revoked__base_case_ @@ -231,10 +239,10 @@ private void testBaseCasePrimaryKeySigns() "=5KMU\n" + "-----END PGP SIGNATURE-----\n", true); - signatureValidityTest(cert, t0, t1, t2, t3); + signatureValidityTest(api, cert, t0, t1, t2, t3); } - private void testBaseCaseSubkeySigns() + private void testBaseCaseSubkeySigns(OpenPGPApi api) throws IOException { // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__subkey_signs__primary_key_is_not_revoked__base_case_ @@ -360,10 +368,10 @@ private void testBaseCaseSubkeySigns() "=CIl0\n" + "-----END PGP SIGNATURE-----\n", true); - signatureValidityTest(cert, t0, t1, t2, t3); + signatureValidityTest(api, cert, t0, t1, t2, t3); } - private void testPKSignsPKRevokedNoSubpacket() + private void testPKSignsPKRevokedNoSubpacket(OpenPGPApi api) throws IOException { String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -496,10 +504,10 @@ private void testPKSignsPKRevokedNoSubpacket() "=5KMU\n" + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); - signatureValidityTest(cert, t0, t1, t2, t3); + signatureValidityTest(api, cert, t0, t1, t2, t3); } - private void testSKSignsPKRevokedNoSubpacket() + private void testSKSignsPKRevokedNoSubpacket(OpenPGPApi api) throws IOException { // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__subkey_signs__primary_key_is_revoked__revoked__no_subpacket @@ -633,10 +641,10 @@ private void testSKSignsPKRevokedNoSubpacket() "=CIl0\n" + "-----END PGP SIGNATURE-----\n", false, "Hard revocations invalidate key at all times"); - signatureValidityTest(cert, t0, t1, t2, t3); + signatureValidityTest(api, cert, t0, t1, t2, t3); } - private void testPKSignsPKRevocationSuperseded() + private void testPKSignsPKRevocationSuperseded(OpenPGPApi api) throws IOException { // https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Key_revocation_test__primary_key_signs_and_is_revoked__revoked__superseded @@ -770,13 +778,13 @@ private void testPKSignsPKRevocationSuperseded() "=5KMU\n" + "-----END PGP SIGNATURE-----\n", true); - signatureValidityTest(CERT, t0, t1, t2, t3); + signatureValidityTest(api, CERT, t0, t1, t2, t3); } - private void signatureValidityTest(String cert, TestSignature... testSignatures) + private void signatureValidityTest(OpenPGPApi api, String cert, TestSignature... testSignatures) throws IOException { - OpenPGPCertificate certificate = reader.parseCertificate(cert); + OpenPGPCertificate certificate = api.readKeyOrCertificate().parseCertificate(cert); for (TestSignature test : testSignatures) { @@ -798,13 +806,13 @@ private void signatureValidityTest(String cert, TestSignature... testSignatures) } } - private void testGetPrimaryUserId() + private void testGetPrimaryUserId(OpenPGPApi api) throws PGPException { Date now = new Date((new Date().getTime() / 1000) * 1000); Date oneHourAgo = new Date(now.getTime() - 1000 * 60 * 60); - OpenPGPV6KeyGenerator gen = new BcOpenPGPV6KeyGenerator(oneHourAgo); + OpenPGPV6KeyGenerator gen = api.generateKey(oneHourAgo); OpenPGPKey key = gen.withPrimaryKey() .addUserId("Old non-primary ") .addUserId("New primary ", From fa281a744d537f9d369b92ee02c7f7cb61c2ee6f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 14:35:53 +0100 Subject: [PATCH 079/154] Perform OpenPGPMessageProcessorTest with all APIs --- .../api/test/OpenPGPMessageProcessorTest.java | 233 +++++++++--------- 1 file changed, 122 insertions(+), 111 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 6524bf15e8..515f10c8af 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -4,10 +4,12 @@ import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; +import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; @@ -16,6 +18,8 @@ import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor; import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.io.Streams; @@ -46,39 +50,46 @@ public String getName() public void performTest() throws Exception { - testVerificationOfSEIPD1MessageWithTamperedCiphertext(); + performTestsWith(new BcOpenPGPApi()); + performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); + } + + private void performTestsWith(OpenPGPApi api) + throws Exception + { + testVerificationOfSEIPD1MessageWithTamperedCiphertext(api); - roundtripUnarmoredPlaintextMessage(); - roundtripArmoredPlaintextMessage(); - roundTripCompressedMessage(); - roundTripCompressedSymEncMessageMessage(); + roundtripUnarmoredPlaintextMessage(api); + roundtripArmoredPlaintextMessage(api); + roundTripCompressedMessage(api); + roundTripCompressedSymEncMessageMessage(api); - roundTripSymEncMessageWithMultiplePassphrases(); + roundTripSymEncMessageWithMultiplePassphrases(api); - roundTripV4KeyEncryptedMessageAlice(); - roundTripV4KeyEncryptedMessageBob(); - roundTripV4KeyEncryptedMessageCarol(); + roundTripV4KeyEncryptedMessageAlice(api); + roundTripV4KeyEncryptedMessageBob(api); + roundTripV4KeyEncryptedMessageCarol(api); - roundTripV6KeyEncryptedMessage(); - encryptWithV4V6KeyDecryptWithV4(); - encryptWithV4V6KeyDecryptWithV6(); + roundTripV6KeyEncryptedMessage(api); + encryptWithV4V6KeyDecryptWithV4(api); + encryptWithV4V6KeyDecryptWithV6(api); - encryptDecryptWithLockedKey(); - encryptDecryptWithMissingKey(); + encryptDecryptWithLockedKey(api); + encryptDecryptWithMissingKey(api); - inlineSignWithV4KeyAlice(); - inlineSignWithV4KeyBob(); - inlineSignWithV4KeyCarol(); - inlineSignWithV6Key(); + inlineSignWithV4KeyAlice(api); + inlineSignWithV4KeyBob(api); + inlineSignWithV4KeyCarol(api); + inlineSignWithV6Key(api); - verifyMessageByRevokedKey(); - incompleteMessageProcessing(); + verifyMessageByRevokedKey(api); + incompleteMessageProcessing(api); } - private void roundtripUnarmoredPlaintextMessage() + private void roundtripUnarmoredPlaintextMessage(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(false) .setIsPadded(false); @@ -91,7 +102,7 @@ private void roundtripUnarmoredPlaintextMessage() msgOut.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); OpenPGPMessageInputStream plainIn = processor.process(bIn); ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); Streams.pipeAll(plainIn, plainOut); @@ -101,10 +112,10 @@ private void roundtripUnarmoredPlaintextMessage() isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); } - private void roundtripArmoredPlaintextMessage() + private void roundtripArmoredPlaintextMessage(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) .setIsPadded(false); gen.getConfiguration().setCompressionNegotiator( @@ -116,7 +127,7 @@ private void roundtripArmoredPlaintextMessage() msgOut.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); OpenPGPMessageInputStream plainIn = processor.process(bIn); ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); Streams.pipeAll(plainIn, plainOut); @@ -127,10 +138,10 @@ private void roundtripArmoredPlaintextMessage() isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); } - private void roundTripCompressedMessage() + private void roundTripCompressedMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) .setIsPadded(false); gen.getConfiguration().setCompressionNegotiator( @@ -142,7 +153,7 @@ private void roundTripCompressedMessage() msgOut.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); InputStream plainIn = processor.process(bIn); ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); Streams.pipeAll(plainIn, plainOut); @@ -151,10 +162,10 @@ private void roundTripCompressedMessage() isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); } - private void roundTripCompressedSymEncMessageMessage() + private void roundTripCompressedSymEncMessageMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) .addEncryptionPassphrase("lal".toCharArray()) .setSessionKeyExtractionCallback( @@ -174,7 +185,7 @@ private void roundTripCompressedSymEncMessageMessage() isNotNull(encryptionSessionKey); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageInputStream plainIn = new OpenPGPMessageProcessor() + OpenPGPMessageInputStream plainIn = api.decryptAndOrVerifyMessage() .addMessagePassphrase("lal".toCharArray()) .process(bIn); ByteArrayOutputStream plainOut = new ByteArrayOutputStream(); @@ -188,11 +199,11 @@ private void roundTripCompressedSymEncMessageMessage() isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); } - private void roundTripSymEncMessageWithMultiplePassphrases() + private void roundTripSymEncMessageWithMultiplePassphrases(OpenPGPApi api) throws PGPException, IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .addEncryptionPassphrase("orange".toCharArray()) .addEncryptionPassphrase("violet".toCharArray()) .setSessionKeyExtractionCallback(sk -> this.encryptionSessionKey = sk); @@ -208,7 +219,7 @@ private void roundTripSymEncMessageWithMultiplePassphrases() bOut = new ByteArrayOutputStream(); // Try decryption with explicitly set message passphrase - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); processor.addMessagePassphrase("violet".toCharArray()); OpenPGPMessageInputStream decIn = processor.process(bIn); Streams.pipeAll(decIn, bOut); @@ -223,7 +234,7 @@ private void roundTripSymEncMessageWithMultiplePassphrases() // Try decryption with wrong passphrase and then request proper one dynamically bOut = new ByteArrayOutputStream(); bIn = new ByteArrayInputStream(ciphertext); - processor = new OpenPGPMessageProcessor(); + processor = api.decryptAndOrVerifyMessage(); decIn = processor.setMissingMessagePassphraseCallback(new StackPassphraseCallback("orange".toCharArray())) // wrong passphrase, so missing callback is invoked .addMessagePassphrase("yellow".toCharArray()) @@ -237,10 +248,10 @@ private void roundTripSymEncMessageWithMultiplePassphrases() isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void roundTripV4KeyEncryptedMessageAlice() + private void roundTripV4KeyEncryptedMessageAlice(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); @@ -249,8 +260,8 @@ private void roundTripV4KeyEncryptedMessageAlice() enc.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); + processor.addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -262,11 +273,11 @@ private void roundTripV4KeyEncryptedMessageAlice() result.getEncryptionMethod()); } - private void roundTripV4KeyEncryptedMessageBob() + private void roundTripV4KeyEncryptedMessageBob(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.BOB_CERT)); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.BOB_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -274,8 +285,8 @@ private void roundTripV4KeyEncryptedMessageBob() enc.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.BOB_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); + processor.addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -288,11 +299,11 @@ private void roundTripV4KeyEncryptedMessageBob() isEncodingEqual(bOut.toByteArray(), PLAINTEXT); } - private void roundTripV4KeyEncryptedMessageCarol() + private void roundTripV4KeyEncryptedMessageCarol(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.CAROL_CERT)); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.CAROL_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -300,8 +311,8 @@ private void roundTripV4KeyEncryptedMessageCarol() enc.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); - processor.addDecryptionKey(reader.parseKey(OpenPGPTestKeys.CAROL_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); + processor.addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.CAROL_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -314,12 +325,12 @@ private void roundTripV4KeyEncryptedMessageCarol() isEncodingEqual(bOut.toByteArray(), PLAINTEXT); } - private void roundTripV6KeyEncryptedMessage() + private void roundTripV6KeyEncryptedMessage(OpenPGPApi api) throws IOException, PGPException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) .addEncryptionCertificate(key) .setIsPadded(false); @@ -330,7 +341,7 @@ private void roundTripV6KeyEncryptedMessage() msgOut.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() .addDecryptionKey(key); OpenPGPMessageInputStream plainIn = processor.process(bIn); @@ -344,12 +355,12 @@ private void roundTripV6KeyEncryptedMessage() isEncodingEqual(PLAINTEXT, plainOut.toByteArray()); } - private void encryptWithV4V6KeyDecryptWithV4() + private void encryptWithV4V6KeyDecryptWithV4(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.V6_CERT)); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.V6_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -357,8 +368,8 @@ private void encryptWithV4V6KeyDecryptWithV4() enc.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() + .addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -371,12 +382,12 @@ private void encryptWithV4V6KeyDecryptWithV4() result.getEncryptionMethod()); } - private void encryptWithV4V6KeyDecryptWithV6() + private void encryptWithV4V6KeyDecryptWithV6(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.V6_CERT)); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.V6_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); @@ -384,8 +395,8 @@ private void encryptWithV4V6KeyDecryptWithV6() enc.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.V6_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() + .addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY)); OpenPGPMessageInputStream decIn = processor.process(bIn); @@ -397,13 +408,13 @@ private void encryptWithV4V6KeyDecryptWithV6() result.getEncryptionMethod()); } - private void encryptDecryptWithLockedKey() + private void encryptDecryptWithLockedKey(OpenPGPApi api) throws IOException, PGPException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY_LOCKED); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageOutputStream encOut = new OpenPGPMessageGenerator() + OpenPGPMessageOutputStream encOut = api.signAndOrEncryptMessage() .addEncryptionCertificate(key) .open(bOut); @@ -415,7 +426,7 @@ private void encryptDecryptWithLockedKey() // Provide passphrase and key together ByteArrayInputStream bIn = new ByteArrayInputStream(ciphertext); bOut = new ByteArrayOutputStream(); - OpenPGPMessageInputStream decIn = new OpenPGPMessageProcessor() + OpenPGPMessageInputStream decIn = api.decryptAndOrVerifyMessage() .addDecryptionKey(key, OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) .process(bIn); Streams.pipeAll(decIn, bOut); @@ -427,7 +438,7 @@ private void encryptDecryptWithLockedKey() // Provide passphrase and key separate from another bIn = new ByteArrayInputStream(ciphertext); bOut = new ByteArrayOutputStream(); - decIn = new OpenPGPMessageProcessor() + decIn = api.decryptAndOrVerifyMessage() .addDecryptionKey(key) .addDecryptionKeyPassphrase(OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) .process(bIn); @@ -440,7 +451,7 @@ private void encryptDecryptWithLockedKey() // Provide passphrase dynamically bIn = new ByteArrayInputStream(ciphertext); bOut = new ByteArrayOutputStream(); - decIn = new OpenPGPMessageProcessor() + decIn = api.decryptAndOrVerifyMessage() .addDecryptionKey(key) .setMissingOpenPGPKeyPassphraseProvider(k -> OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()) @@ -453,13 +464,13 @@ private void encryptDecryptWithLockedKey() isEncodingEqual(sk.getKey(), result.getSessionKey().getKey()); } - private void encryptDecryptWithMissingKey() + private void encryptDecryptWithMissingKey(OpenPGPApi api) throws IOException, PGPException { - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OutputStream encOut = new OpenPGPMessageGenerator() + OutputStream encOut = api.signAndOrEncryptMessage() .addEncryptionCertificate(key) .open(bOut); @@ -471,7 +482,7 @@ private void encryptDecryptWithMissingKey() // Provide passphrase and key together ByteArrayInputStream bIn = new ByteArrayInputStream(ciphertext); bOut = new ByteArrayOutputStream(); - OpenPGPMessageInputStream decIn = new OpenPGPMessageProcessor() + OpenPGPMessageInputStream decIn = api.decryptAndOrVerifyMessage() .setMissingOpenPGPKeyProvider(id -> key) .process(bIn); Streams.pipeAll(decIn, bOut); @@ -483,12 +494,12 @@ private void encryptDecryptWithMissingKey() isNotNull(result.getSessionKey()); } - private void inlineSignWithV4KeyAlice() + private void inlineSignWithV4KeyAlice(OpenPGPApi api) throws IOException, PGPException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey aliceKey = reader.parseKey(OpenPGPTestKeys.ALICE_KEY); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + OpenPGPKey aliceKey = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY); gen.addSigningKey(aliceKey); OutputStream signOut = gen.open(bOut); @@ -498,8 +509,8 @@ private void inlineSignWithV4KeyAlice() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate aliceCert = reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + OpenPGPCertificate aliceCert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() .addVerificationCertificate(aliceCert); OpenPGPMessageInputStream verifIn = processor.process(bIn); @@ -515,12 +526,12 @@ private void inlineSignWithV4KeyAlice() isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void inlineSignWithV4KeyBob() + private void inlineSignWithV4KeyBob(OpenPGPApi api) throws IOException, PGPException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey bobKey = reader.parseKey(OpenPGPTestKeys.BOB_KEY); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + OpenPGPKey bobKey = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY); gen.addSigningKey(bobKey); OutputStream signOut = gen.open(bOut); @@ -530,8 +541,8 @@ private void inlineSignWithV4KeyBob() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate bobCert = reader.parseCertificate(OpenPGPTestKeys.BOB_CERT); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + OpenPGPCertificate bobCert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.BOB_CERT); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() .addVerificationCertificate(bobCert); OpenPGPMessageInputStream verifIn = processor.process(bIn); @@ -546,12 +557,12 @@ private void inlineSignWithV4KeyBob() isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void inlineSignWithV4KeyCarol() + private void inlineSignWithV4KeyCarol(OpenPGPApi api) throws PGPException, IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey carolKey = reader.parseKey(OpenPGPTestKeys.CAROL_KEY); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + OpenPGPKey carolKey = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.CAROL_KEY); gen.addSigningKey(carolKey); OutputStream signOut = gen.open(bOut); @@ -561,8 +572,8 @@ private void inlineSignWithV4KeyCarol() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate carolCert = reader.parseCertificate(OpenPGPTestKeys.CAROL_CERT); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + OpenPGPCertificate carolCert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.CAROL_CERT); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() .addVerificationCertificate(carolCert); OpenPGPMessageInputStream verifIn = processor.process(bIn); @@ -577,12 +588,12 @@ private void inlineSignWithV4KeyCarol() isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void inlineSignWithV6Key() + private void inlineSignWithV6Key(OpenPGPApi api) throws PGPException, IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - OpenPGPKey v6Key = reader.parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); + OpenPGPKey v6Key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); gen.addSigningKey(v6Key); OutputStream signOut = gen.open(bOut); @@ -592,8 +603,8 @@ private void inlineSignWithV6Key() ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); bOut = new ByteArrayOutputStream(); - OpenPGPCertificate v6Cert = reader.parseCertificate(OpenPGPTestKeys.V6_CERT); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() + OpenPGPCertificate v6Cert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.V6_CERT); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() .addVerificationCertificate(v6Cert); OpenPGPMessageInputStream verifIn = processor.process(bIn); @@ -608,12 +619,12 @@ private void inlineSignWithV6Key() isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void verifyMessageByRevokedKey() + private void verifyMessageByRevokedKey(OpenPGPApi api) throws PGPException, IOException { // Create a minimal signed message - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.ALICE_KEY); - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); gen.addSigningKey(key); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); @@ -622,11 +633,11 @@ private void verifyMessageByRevokedKey() oOut.close(); // Load the certificate and import its revocation signature - OpenPGPCertificate cert = reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT); + OpenPGPCertificate cert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT); cert = OpenPGPCertificate.join(cert, OpenPGPTestKeys.ALICE_REVOCATION_CERT); // Process the signed message using the revoked key - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); processor.addVerificationCertificate(cert); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); OpenPGPMessageInputStream oIn = processor.process(bIn); @@ -639,12 +650,12 @@ private void verifyMessageByRevokedKey() isFalse(sig.isValid()); } - private void incompleteMessageProcessing() + private void incompleteMessageProcessing(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() - .addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)) - .addSigningKey(reader.parseKey(OpenPGPTestKeys.BOB_KEY)); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() + .addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT)) + .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream out = gen.open(bOut); @@ -652,9 +663,9 @@ private void incompleteMessageProcessing() out.close(); ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor() - .addVerificationCertificate(reader.parseCertificate(OpenPGPTestKeys.BOB_CERT)) - .addDecryptionKey(reader.parseKey(OpenPGPTestKeys.ALICE_KEY)); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() + .addVerificationCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.BOB_CERT)) + .addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY)); OpenPGPMessageInputStream in = processor.process(bIn); // read a single byte (not the entire message) @@ -666,7 +677,7 @@ private void incompleteMessageProcessing() isFalse(sig.isValid()); } - private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() + private void testVerificationOfSEIPD1MessageWithTamperedCiphertext(OpenPGPApi api) throws IOException, PGPException { String MSG = "-----BEGIN PGP MESSAGE-----\n" + @@ -683,8 +694,8 @@ private void testVerificationOfSEIPD1MessageWithTamperedCiphertext() "IwBVELjaaSGpdOuIHkETYssCNfqPSv0rNmaTDq78xItvhjuc4lRaKkpF9DdE\n" + "=I5BA\n" + "-----END PGP MESSAGE-----"; - OpenPGPKey key = reader.parseKey(OpenPGPTestKeys.BOB_KEY); - OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY); + OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); processor.addDecryptionKey(key); OpenPGPMessageInputStream oIn = processor.process(new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8))); Streams.drain(oIn); From b1ba857bd12b2f81c9b1f3c7b807723f1d30facd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 14 Jan 2025 15:26:06 +0100 Subject: [PATCH 080/154] Bring OpenPGPDetachedSignatureGenerator in line with the other APIs --- .../OpenPGPDetachedSignatureGenerator.java | 83 +++++++++++-------- .../api/test/OpenPGPMessageProcessorTest.java | 5 +- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index bf2a85e3c2..0fbf4e7de4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -4,6 +4,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import java.io.IOException; @@ -21,7 +22,9 @@ * To use this class, instantiate it, optionally providing a concrete {@link OpenPGPImplementation} and * {@link OpenPGPPolicy} for algorithm policing. * Then, add the desired {@link OpenPGPKey} you want to use for signing the data via one or more - * calls to {@link #addSigningKey(OpenPGPKey, char[])}. + * calls to {@link #addSigningKey(OpenPGPKey, KeyPassphraseProvider)}. + * You have fine-grained control over the signature by using the method + * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, KeyPassphraseProvider, SignatureParameters.Callback)}. * Lastly, retrieve a list of detached {@link OpenPGPSignature.OpenPGPDocumentSignature signatures} by calling * {@link #sign(InputStream)}, passing in an {@link InputStream} containing the data you want to sign. */ @@ -29,7 +32,6 @@ public class OpenPGPDetachedSignatureGenerator { private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; - private int signatureType = PGPSignature.BINARY_DOCUMENT; private final List signatureGenerators = new ArrayList<>(); private final List signingKeys = new ArrayList<>(); @@ -64,34 +66,6 @@ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, O this.policy = policy; } - /** - * Set the type of generated signatures to {@link PGPSignature#BINARY_DOCUMENT}. - * Binary signatures are calculated over the plaintext as is. - * Binary signatures are the default. - * - * @return this - */ - public OpenPGPDetachedSignatureGenerator setBinarySignature() - { - this.signatureType = PGPSignature.BINARY_DOCUMENT; - return this; - } - - /** - * Set the type of generated signatures to {@link PGPSignature#CANONICAL_TEXT_DOCUMENT}. - * Text signatures are calculated over modified plaintext, which is first transformed by canonicalizing - * line endings to CR-LF (
0x0D0A
). - * This is useful, if the plaintext is transported via a channel that may not retain the original message - * encoding. - * - * @return this - */ - public OpenPGPDetachedSignatureGenerator setCanonicalTextDocument() - { - this.signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; - return this; - } - /** * Add an {@link OpenPGPKey} as signing key. * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, @@ -99,13 +73,15 @@ public OpenPGPDetachedSignatureGenerator setCanonicalTextDocument() * Otherwise, all capable signing subkeys will be used to create detached signatures. * * @param key OpenPGP key - * @param passphrase passphrase to unlock the signing key + * @param passphraseProvider provides the passphrase to unlock the signing key * @return this * * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey * @throws PGPException if signing fails */ - public OpenPGPDetachedSignatureGenerator addSigningKey(OpenPGPKey key, char[] passphrase) + public OpenPGPDetachedSignatureGenerator addSigningKey( + OpenPGPKey key, + KeyPassphraseProvider passphraseProvider) throws PGPException { List signingSubkeys = key.getSigningKeys(); @@ -115,12 +91,51 @@ public OpenPGPDetachedSignatureGenerator addSigningKey(OpenPGPKey key, char[] pa } OpenPGPKey.OpenPGPSecretKey signingKey = key.getSecretKey(signingSubkeys.get(0)); + return addSigningKey(signingKey, passphraseProvider, null); + } + + public OpenPGPDetachedSignatureGenerator addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + KeyPassphraseProvider passphraseProvider, + SignatureParameters.Callback signatureCallback) + throws PGPException + { + SignatureParameters parameters = SignatureParameters.dataSignature(policy) + .setSignatureHashAlgorithm(getPreferredHashAlgorithm(signingKey)); + + if (signatureCallback != null) + { + parameters = signatureCallback.apply(parameters); + } + + if(parameters == null) + { + throw new IllegalStateException("SignatureParameters Callback MUST NOT return null."); + } + + if (!signingKey.isSigningKey(parameters.getSignatureCreationTime())) + { + throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + + " is not capable of creating data signatures."); + } + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( implementation.pgpContentSignerBuilder( signingKey.getPublicKey().getPGPPublicKey().getAlgorithm(), - getPreferredHashAlgorithm(signingKey)), + parameters.getSignatureHashAlgorithmId()), signingKey.getPGPPublicKey()); - sigGen.init(signatureType, signingKey.unlock(passphrase)); + + char[] passphrase = signingKey.isLocked() ? passphraseProvider.getKeyPassword(signingKey) : null; + sigGen.init(parameters.getSignatureType(), signingKey.unlock(passphrase)); + + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, signingKey.getPGPPublicKey()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + sigGen.setHashedSubpackets(hashedSubpackets.generate()); + + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); signatureGenerators.add(sigGen); signingKeys.add(signingKey); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 515f10c8af..3081348865 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -11,7 +11,6 @@ import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; -import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; @@ -36,8 +35,6 @@ public class OpenPGPMessageProcessorTest { private static final byte[] PLAINTEXT = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - private final OpenPGPKeyReader reader = new OpenPGPKeyReader(); - private PGPSessionKey encryptionSessionKey; @Override @@ -252,7 +249,7 @@ private void roundTripV4KeyEncryptedMessageAlice(OpenPGPApi api) throws IOException, PGPException { OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.addEncryptionCertificate(reader.parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream enc = gen.open(bOut); From 661ec4547b31387ac8c8a04f90aafa6de07640b1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jan 2025 12:57:22 +0100 Subject: [PATCH 081/154] Add base test for OpenPGPDetachedSignatureGenerator and Processor --- .../openpgp/api/OpenPGPSignature.java | 19 ++++ ...OpenPGPDetachedSignatureProcessorTest.java | 103 ++++++++++++++++++ .../openpgp/test/RegressionTest.java | 4 +- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index ef77299541..6e36db86cc 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -1,6 +1,9 @@ package org.bouncycastle.openpgp.api; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.KeyIdentifier; +import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -14,6 +17,8 @@ import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.util.encoders.Hex; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Locale; @@ -410,6 +415,20 @@ protected String getType() } } + public String toAsciiArmoredString() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = ArmoredOutputStream.builder() + .clearHeaders() + .build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + getSignature().encode(pOut); + pOut.close(); + aOut.close(); + return bOut.toString(); + } + /** * {@link SignatureSubpacket} and the {@link OpenPGPSignature} that contains it. */ diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java new file mode 100644 index 0000000000..f482ae7746 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -0,0 +1,103 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureGenerator; +import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureProcessor; +import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class OpenPGPDetachedSignatureProcessorTest + extends AbstractPacketTest +{ + @Override + public String getName() + { + return "OpenPGPDetachedSignatureProcessorTest"; + } + + @Override + public void performTest() + throws Exception + { + performWith(new BcOpenPGPApi()); + performWith(new JcaOpenPGPApi(new BouncyCastleProvider())); + } + + private void performWith(OpenPGPApi api) + throws PGPException, IOException + { + createVerifyV4Signature(api); + createVerifyV6Signature(api); + } + + private void createVerifyV4Signature(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); + gen.addSigningKey( + api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY), + null); + + byte[] plaintext = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext); + + List signatures = gen.sign(plaintextIn); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature signature = signatures.get(0); + isEquals(4, signature.getSignature().getVersion()); + String armored = signature.toAsciiArmoredString(); + isTrue(armored.startsWith("-----BEGIN PGP SIGNATURE-----\n")); + + // Verify detached signatures + OpenPGPDetachedSignatureProcessor processor = api.verifyDetachedSignature(); + processor.addSignature(signature.getSignature()); + processor.addVerificationCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT)); + + List verified = processor.process(new ByteArrayInputStream(plaintext)); + isEquals(1, verified.size()); + isTrue(verified.get(0).isValid()); + } + + private void createVerifyV6Signature(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); + gen.addSigningKey( + api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY), + null); + + byte[] plaintext = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext); + + List signatures = gen.sign(plaintextIn); + isEquals(1, signatures.size()); + OpenPGPSignature.OpenPGPDocumentSignature signature = signatures.get(0); + isEquals(6, signature.getSignature().getVersion()); + String armored = signature.toAsciiArmoredString(); + isTrue(armored.startsWith("-----BEGIN PGP SIGNATURE-----\n")); + + // Verify detached signatures + OpenPGPDetachedSignatureProcessor processor = api.verifyDetachedSignature(); + processor.addSignature(signature.getSignature()); + processor.addVerificationCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.V6_CERT)); + + List verified = processor.process(new ByteArrayInputStream(plaintext)); + isEquals(1, verified.size()); + isTrue(verified.get(0).isValid()); + } + + public static void main(String[] args) + { + runTest(new OpenPGPDetachedSignatureProcessorTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index e7db0b0388..83463fa3cf 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,6 +3,7 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; +import org.bouncycastle.openpgp.api.test.OpenPGPDetachedSignatureProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPMessageGeneratorTest; import org.bouncycastle.openpgp.api.test.OpenPGPMessageProcessorTest; import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; @@ -95,7 +96,8 @@ public class RegressionTest new OpenPGPMessageGeneratorTest(), new OpenPGPMessageProcessorTest(), - new StaticV6OpenPGPMessageGeneratorTest() + new StaticV6OpenPGPMessageGeneratorTest(), + new OpenPGPDetachedSignatureProcessorTest() }; public static void main(String[] args) From 2e37f2ecdd180e1aa1d14a234c0d636e1e4be0b5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 12:43:19 +0100 Subject: [PATCH 082/154] Optimize DefaultKeyPassphraseProvider --- .../openpgp/api/KeyPassphraseProvider.java | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java index 58c5fac2d6..c2814fb191 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPassphraseProvider.java @@ -22,8 +22,8 @@ public interface KeyPassphraseProvider class DefaultKeyPassphraseProvider implements KeyPassphraseProvider { - private final Map passphraseMap = new HashMap<>(); - private final List unassociatedPassphrases = new ArrayList<>(); + private final Map passphraseMap = new HashMap<>(); + private final List allPassphrases = new ArrayList<>(); private KeyPassphraseProvider callback; public DefaultKeyPassphraseProvider() @@ -33,6 +33,7 @@ public DefaultKeyPassphraseProvider() public DefaultKeyPassphraseProvider(OpenPGPKey key, char[] passphrase) { + allPassphrases.add(passphrase); for (OpenPGPKey.OpenPGPSecretKey subkey : key.getSecretKeys().values()) { passphraseMap.put(subkey, passphrase); @@ -42,48 +43,46 @@ public DefaultKeyPassphraseProvider(OpenPGPKey key, char[] passphrase) @Override public char[] getKeyPassword(OpenPGPKey.OpenPGPSecretKey key) { - if (key.isLocked()) + if (!key.isLocked()) { - char[] passphrase = passphraseMap.get(key); - if (passphrase != null) - { - return passphrase; - } + passphraseMap.put(key, null); + return null; + } - for (char[] unassociatedPassphrase : unassociatedPassphrases) - { - passphrase = unassociatedPassphrase; - if (key.isPassphraseCorrect(passphrase)) - { - addPassphrase(key, passphrase); - return passphrase; - } - } + char[] passphrase = passphraseMap.get(key); + if (passphrase != null) + { + return passphrase; + } - if (callback != null) + for (char[] knownPassphrase : allPassphrases) + { + if (key.isPassphraseCorrect(knownPassphrase)) { - passphrase = callback.getKeyPassword(key); - addPassphrase(key, passphrase); + addPassphrase(key, knownPassphrase); + return knownPassphrase; } - return passphrase; } - else + + if (callback != null) { - return null; + passphrase = callback.getKeyPassword(key); + addPassphrase(key, passphrase); } + return passphrase; } public DefaultKeyPassphraseProvider addPassphrase(char[] passphrase) { boolean found = false; - for (char[] existing : unassociatedPassphrases) + for (char[] existing : allPassphrases) { found |= (Arrays.areEqual(existing, passphrase)); } if (!found) { - unassociatedPassphrases.add(passphrase); + allPassphrases.add(passphrase); } return this; } @@ -92,14 +91,31 @@ public DefaultKeyPassphraseProvider addPassphrase(OpenPGPKey key, char[] passphr { for (OpenPGPKey.OpenPGPSecretKey subkey : key.getSecretKeys().values()) { - addPassphrase(subkey, passphrase); + if (!subkey.isLocked()) + { + passphraseMap.put(subkey, null); + continue; + } + + char[] existentPassphrase = passphraseMap.get(subkey); + if (existentPassphrase == null || !subkey.isPassphraseCorrect(existentPassphrase)) + { + passphraseMap.put(subkey, passphrase); + } } return this; } public DefaultKeyPassphraseProvider addPassphrase(OpenPGPKey.OpenPGPSecretKey key, char[] passphrase) { + if (!key.isLocked()) + { + passphraseMap.put(key, null); + return this; + } + passphraseMap.put(key, passphrase); + return this; } From a46ae61d82f2c269da34763473f62c57b9cc7fb6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 12:43:40 +0100 Subject: [PATCH 083/154] Improve API of OpenPGPDetachedSignatureGenerator --- .../OpenPGPDetachedSignatureGenerator.java | 71 ++++++++++++-- ...OpenPGPDetachedSignatureProcessorTest.java | 95 ++++++++++++++++++- 2 files changed, 154 insertions(+), 12 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index 0fbf4e7de4..423e0d701b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -24,7 +24,7 @@ * Then, add the desired {@link OpenPGPKey} you want to use for signing the data via one or more * calls to {@link #addSigningKey(OpenPGPKey, KeyPassphraseProvider)}. * You have fine-grained control over the signature by using the method - * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, KeyPassphraseProvider, SignatureParameters.Callback)}. + * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, char[], SignatureParameters.Callback)}. * Lastly, retrieve a list of detached {@link OpenPGPSignature.OpenPGPDocumentSignature signatures} by calling * {@link #sign(InputStream)}, passing in an {@link InputStream} containing the data you want to sign. */ @@ -33,8 +33,14 @@ public class OpenPGPDetachedSignatureGenerator private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; + // Below lists all use the same indexing private final List signatureGenerators = new ArrayList<>(); - private final List signingKeys = new ArrayList<>(); + private final List signingKeys = new ArrayList<>(); + private final List signatureCallbacks = new ArrayList<>(); + private final List signingKeyPassphraseProviders = new ArrayList<>(); + + private final KeyPassphraseProvider.DefaultKeyPassphraseProvider defaultKeyPassphraseProvider = + new KeyPassphraseProvider.DefaultKeyPassphraseProvider(); /** * Instantiate a signature generator using the default {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. @@ -66,6 +72,19 @@ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, O this.policy = policy; } + public OpenPGPDetachedSignatureGenerator addKeyPassphrase(char[] passphrase) + { + defaultKeyPassphraseProvider.addPassphrase(passphrase); + return this; + } + + public OpenPGPDetachedSignatureGenerator addSigningKey( + OpenPGPKey key) + throws PGPException + { + return addSigningKey(key, defaultKeyPassphraseProvider); + } + /** * Add an {@link OpenPGPKey} as signing key. * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, @@ -94,11 +113,40 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( return addSigningKey(signingKey, passphraseProvider, null); } + public OpenPGPDetachedSignatureGenerator addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + char[] passphrase, + SignatureParameters.Callback signatureCallback) + throws PGPException + { + return addSigningKey( + signingKey, + defaultKeyPassphraseProvider.addPassphrase(signingKey, passphrase), + signatureCallback); + } + public OpenPGPDetachedSignatureGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, KeyPassphraseProvider passphraseProvider, SignatureParameters.Callback signatureCallback) throws PGPException + { + if (!signingKey.isSigningKey()) + { + throw new InvalidSigningKeyException("Subkey cannot sign."); + } + + signingKeys.add(signingKey); + signingKeyPassphraseProviders.add(passphraseProvider); + signatureCallbacks.add(signatureCallback); + return this; + } + + private PGPSignatureGenerator initSignatureGenerator( + OpenPGPKey.OpenPGPSecretKey signingKey, + KeyPassphraseProvider passphraseProvider, + SignatureParameters.Callback signatureCallback) + throws PGPException { SignatureParameters parameters = SignatureParameters.dataSignature(policy) .setSignatureHashAlgorithm(getPreferredHashAlgorithm(signingKey)); @@ -108,7 +156,7 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( parameters = signatureCallback.apply(parameters); } - if(parameters == null) + if (parameters == null) { throw new IllegalStateException("SignatureParameters Callback MUST NOT return null."); } @@ -125,7 +173,7 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( parameters.getSignatureHashAlgorithmId()), signingKey.getPGPPublicKey()); - char[] passphrase = signingKey.isLocked() ? passphraseProvider.getKeyPassword(signingKey) : null; + char[] passphrase = passphraseProvider.getKeyPassword(signingKey); sigGen.init(parameters.getSignatureType(), signingKey.unlock(passphrase)); PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -137,10 +185,7 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - signatureGenerators.add(sigGen); - signingKeys.add(signingKey); - - return this; + return sigGen; } private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) @@ -173,6 +218,16 @@ private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key public List sign(InputStream inputStream) throws IOException, PGPException { + for (int i = 0; i < signingKeys.size(); i++) + { + OpenPGPKey.OpenPGPSecretKey signingKey = signingKeys.get(i); + KeyPassphraseProvider passphraseProvider = signingKeyPassphraseProviders.get(i); + SignatureParameters.Callback signatureCallback = signatureCallbacks.get(i); + PGPSignatureGenerator signatureGenerator = + initSignatureGenerator(signingKey, passphraseProvider, signatureCallback); + signatureGenerators.add(signatureGenerator); + } + byte[] buf = new byte[2048]; int r; while ((r = inputStream.read(buf)) != -1) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java index f482ae7746..f777f06c16 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -7,13 +7,16 @@ import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureGenerator; import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureProcessor; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPSignature; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; public class OpenPGPDetachedSignatureProcessorTest @@ -38,6 +41,12 @@ private void performWith(OpenPGPApi api) { createVerifyV4Signature(api); createVerifyV6Signature(api); + + keyPassphrasesArePairedUpProperly_keyAddedFirst(api); + keyPassphrasesArePairedUpProperly_passphraseAddedFirst(api); + + missingPassphraseThrows(api); + wrongPassphraseThrows(api); } private void createVerifyV4Signature(OpenPGPApi api) @@ -45,8 +54,7 @@ private void createVerifyV4Signature(OpenPGPApi api) { OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); gen.addSigningKey( - api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY), - null); + api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY)); byte[] plaintext = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext); @@ -73,8 +81,7 @@ private void createVerifyV6Signature(OpenPGPApi api) { OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); gen.addSigningKey( - api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY), - null); + api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY)); byte[] plaintext = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext); @@ -96,6 +103,86 @@ private void createVerifyV6Signature(OpenPGPApi api) isTrue(verified.get(0).isValid()); } + private void missingPassphraseThrows(OpenPGPApi api) + { + isNotNull(testException( + "Cannot unlock secret key", + "KeyPassphraseException", + new TestExceptionOperation() + { + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); + } + + private void wrongPassphraseThrows(OpenPGPApi api) + { + isNotNull(testException( + "Cannot unlock secret key", + "KeyPassphraseException", + new TestExceptionOperation() + { + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addKeyPassphrase("thisIsWrong".toCharArray()) + .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); + } + + private void keyPassphrasesArePairedUpProperly_keyAddedFirst(OpenPGPApi api) + throws PGPException, IOException + { + OpenPGPKey key = api.generateKey(new Date(), false) + .signOnlyKey() + .build("password".toCharArray()); + + OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); + gen.addSigningKey(key); + + gen.addKeyPassphrase("penguin".toCharArray()); + gen.addKeyPassphrase("password".toCharArray()); + gen.addKeyPassphrase("beluga".toCharArray()); + + byte[] plaintext = "arctic\ndeep sea\nice field\n".getBytes(StandardCharsets.UTF_8); + InputStream plaintextIn = new ByteArrayInputStream(plaintext); + + List signatures = gen.sign(plaintextIn); + isEquals(1, signatures.size()); + } + + private void keyPassphrasesArePairedUpProperly_passphraseAddedFirst(OpenPGPApi api) + throws PGPException, IOException + { + OpenPGPKey key = api.generateKey(new Date(), false) + .signOnlyKey() + .build("password".toCharArray()); + + OpenPGPDetachedSignatureGenerator gen = api.createDetachedSignature(); + + gen.addKeyPassphrase("sloth".toCharArray()); + gen.addKeyPassphrase("password".toCharArray()); + gen.addKeyPassphrase("tapir".toCharArray()); + + gen.addSigningKey(key); + + byte[] plaintext = "jungle\ntropics\nswamp\n".getBytes(StandardCharsets.UTF_8); + InputStream plaintextIn = new ByteArrayInputStream(plaintext); + + List signatures = gen.sign(plaintextIn); + isEquals(1, signatures.size()); + } + public static void main(String[] args) { runTest(new OpenPGPDetachedSignatureProcessorTest()); From c26cf16cca84f45af7e214105bb894e44f5d13fa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 12:58:40 +0100 Subject: [PATCH 084/154] Add test for detached signing with non-signing-capable key --- ...OpenPGPDetachedSignatureProcessorTest.java | 91 +++++++++++++++---- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java index f777f06c16..0906e2dca7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -1,16 +1,24 @@ package org.bouncycastle.openpgp.api.test; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.test.AbstractPacketTest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback; import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureGenerator; import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureProcessor; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.openpgp.api.SignatureParameters; +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -47,6 +55,8 @@ private void performWith(OpenPGPApi api) missingPassphraseThrows(api); wrongPassphraseThrows(api); + + noSigningSubkeyFails(api); } private void createVerifyV4Signature(OpenPGPApi api) @@ -110,15 +120,15 @@ private void missingPassphraseThrows(OpenPGPApi api) "KeyPassphraseException", new TestExceptionOperation() { - @Override - public void operation() - throws Exception - { - api.createDetachedSignature() - .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) - .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); - } - })); + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); } private void wrongPassphraseThrows(OpenPGPApi api) @@ -128,16 +138,16 @@ private void wrongPassphraseThrows(OpenPGPApi api) "KeyPassphraseException", new TestExceptionOperation() { - @Override - public void operation() - throws Exception - { - api.createDetachedSignature() - .addKeyPassphrase("thisIsWrong".toCharArray()) - .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) - .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); - } - })); + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addKeyPassphrase("thisIsWrong".toCharArray()) + .addSigningKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY_LOCKED)) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); } private void keyPassphrasesArePairedUpProperly_keyAddedFirst(OpenPGPApi api) @@ -183,6 +193,49 @@ private void keyPassphrasesArePairedUpProperly_passphraseAddedFirst(OpenPGPApi a isEquals(1, signatures.size()); } + private void noSigningSubkeyFails(OpenPGPApi api) + throws PGPException + { + OpenPGPKey noSigningKey = api.generateKey() + .withPrimaryKey( + new KeyPairGeneratorCallback() { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException { + return generator.generatePrimaryKey(); + } + }, + SignatureParameters.Callback.modifyHashedSubpackets( + new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + // No SIGN_DATA key flag + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER); + return subpackets; + } + } + ) + ).build(); + + isNotNull(testException( + "Key " + noSigningKey.getPrettyFingerprint() + " cannot sign.", + "InvalidSigningKeyException", + new TestExceptionOperation() + { + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addSigningKey(noSigningKey) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); + } + public static void main(String[] args) { runTest(new OpenPGPDetachedSignatureProcessorTest()); From b7708ce4f0da0943e7f51e04b9c96ce9edefbd5f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 13:02:24 +0100 Subject: [PATCH 085/154] Add test for detached signing with incapable explicit subkey --- ...OpenPGPDetachedSignatureProcessorTest.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java index 0906e2dca7..172bf507dc 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -56,7 +56,8 @@ private void performWith(OpenPGPApi api) missingPassphraseThrows(api); wrongPassphraseThrows(api); - noSigningSubkeyFails(api); + withoutSigningSubkeyFails(api); + nonSigningSubkeyFails(api); } private void createVerifyV4Signature(OpenPGPApi api) @@ -193,7 +194,7 @@ private void keyPassphrasesArePairedUpProperly_passphraseAddedFirst(OpenPGPApi a isEquals(1, signatures.size()); } - private void noSigningSubkeyFails(OpenPGPApi api) + private void withoutSigningSubkeyFails(OpenPGPApi api) throws PGPException { OpenPGPKey noSigningKey = api.generateKey() @@ -236,6 +237,49 @@ public void operation() })); } + private void nonSigningSubkeyFails(OpenPGPApi api) + throws PGPException + { + OpenPGPKey noSigningKey = api.generateKey() + .withPrimaryKey( + new KeyPairGeneratorCallback() { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException { + return generator.generatePrimaryKey(); + } + }, + SignatureParameters.Callback.modifyHashedSubpackets( + new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + // No SIGN_DATA key flag + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER); + return subpackets; + } + } + ) + ).build(); + + isNotNull(testException( + "Subkey cannot sign.", + "InvalidSigningKeyException", + new TestExceptionOperation() + { + @Override + public void operation() + throws Exception + { + api.createDetachedSignature() + .addSigningKey(noSigningKey.getPrimarySecretKey(), (char[])null, null) + .sign(new ByteArrayInputStream("Test Data".getBytes(StandardCharsets.UTF_8))); + } + })); + } + public static void main(String[] args) { runTest(new OpenPGPDetachedSignatureProcessorTest()); From 7b2740ea197d6372e7df89505391a96c5be2687c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 14:57:32 +0100 Subject: [PATCH 086/154] Move shared document signature generation logic to super class --- ...ractOpenPGPDocumentSignatureGenerator.java | 207 +++++++ .../AbstractOpenPGPKeySignatureGenerator.java | 13 - .../openpgp/api/KeyPairGeneratorCallback.java | 39 ++ ... => MissingMessagePassphraseCallback.java} | 4 +- .../bouncycastle/openpgp/api/OpenPGPApi.java | 5 + .../OpenPGPDetachedSignatureGenerator.java | 122 +--- .../api/OpenPGPEncryptionNegotiator.java | 2 +- .../openpgp/api/OpenPGPMessageGenerator.java | 572 ++++++------------ .../api/OpenPGPMessageOutputStream.java | 9 +- .../openpgp/api/OpenPGPMessageProcessor.java | 8 +- .../openpgp/api/OpenPGPV6KeyGenerator.java | 32 +- .../openpgp/api/SubkeySelector.java | 24 + .../api/test/OpenPGPMessageGeneratorTest.java | 28 +- .../api/test/OpenPGPMessageProcessorTest.java | 29 +- .../api/test/OpenPGPV6KeyGeneratorTest.java | 30 +- ...va => StackMessagePassphraseCallback.java} | 14 +- .../StaticV6OpenPGPMessageGeneratorTest.java | 23 +- 17 files changed, 531 insertions(+), 630 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java rename pg/src/main/java/org/bouncycastle/openpgp/api/{MissingPassphraseCallback.java => MissingMessagePassphraseCallback.java} (73%) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/SubkeySelector.java rename pg/src/test/java/org/bouncycastle/openpgp/api/test/{StackPassphraseCallback.java => StackMessagePassphraseCallback.java} (53%) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java new file mode 100644 index 0000000000..69e8c31681 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java @@ -0,0 +1,207 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class AbstractOpenPGPDocumentSignatureGenerator> +{ + + protected final OpenPGPImplementation implementation; + protected final OpenPGPPolicy policy; + + // Below lists all use the same indexing + protected final List signatureGenerators = new ArrayList<>(); + protected final List signingKeys = new ArrayList<>(); + protected final List signatureCallbacks = new ArrayList<>(); + protected final List signingKeyPassphraseProviders = new ArrayList<>(); + + protected final KeyPassphraseProvider.DefaultKeyPassphraseProvider defaultKeyPassphraseProvider = + new KeyPassphraseProvider.DefaultKeyPassphraseProvider(); + + protected SubkeySelector signingKeySelector = new SubkeySelector() + { + @Override + public List select(OpenPGPCertificate certificate, + OpenPGPPolicy policy) + { + return certificate.getSigningKeys() + .stream() + .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) + .collect(Collectors.toList()); + } + }; + + public AbstractOpenPGPDocumentSignatureGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) + { + this.implementation = implementation; + this.policy = policy; + } + + /** + * Replace the default signing key selector with a custom implementation. + * The signing key selector is responsible for selecting one or more signing subkeys from a signing key. + * + * @param signingKeySelector selector for signing (sub-)keys + * @return this + */ + public T setSigningKeySelector(SubkeySelector signingKeySelector) + { + this.signingKeySelector = Objects.requireNonNull(signingKeySelector); + return (T) this; + } + + + public T addKeyPassphrase(char[] passphrase) + { + defaultKeyPassphraseProvider.addPassphrase(passphrase); + return (T) this; + } + + public T addSigningKey( + OpenPGPKey key) + throws InvalidSigningKeyException + { + return addSigningKey(key, defaultKeyPassphraseProvider); + } + + /** + * Add an {@link OpenPGPKey} as signing key. + * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, + * this method will throw an {@link InvalidSigningKeyException}. + * Otherwise, all capable signing subkeys will be used to create detached signatures. + * + * @param key OpenPGP key + * @param passphraseProvider provides the passphrase to unlock the signing key + * @return this + * + * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey + * @throws PGPException if signing fails + */ + public T addSigningKey( + OpenPGPKey key, + KeyPassphraseProvider passphraseProvider) + throws InvalidSigningKeyException + { + List signingSubkeys = signingKeySelector.select(key, policy); + if (signingSubkeys.isEmpty()) + { + throw new InvalidSigningKeyException("Key " + key.getPrettyFingerprint() + " cannot sign."); + } + + for (OpenPGPCertificate.OpenPGPComponentKey subkey : signingSubkeys) + { + OpenPGPKey.OpenPGPSecretKey signingKey = key.getSecretKey(subkey); + addSigningKey(signingKey, passphraseProvider, null); + } + + return (T) this; + } + + public T addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + char[] passphrase, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException + { + return addSigningKey( + signingKey, + defaultKeyPassphraseProvider.addPassphrase(signingKey, passphrase), + signatureCallback); + } + + public T addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + KeyPassphraseProvider passphraseProvider, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException + { + if (!signingKey.isSigningKey()) + { + throw new InvalidSigningKeyException("Subkey cannot sign."); + } + + signingKeys.add(signingKey); + signingKeyPassphraseProviders.add(passphraseProvider); + signatureCallbacks.add(signatureCallback); + return (T) this; + } + + protected PGPSignatureGenerator initSignatureGenerator( + OpenPGPKey.OpenPGPSecretKey signingKey, + KeyPassphraseProvider passphraseProvider, + SignatureParameters.Callback signatureCallback) + throws PGPException + { + SignatureParameters parameters = SignatureParameters.dataSignature(policy) + .setSignatureHashAlgorithm(getPreferredHashAlgorithm(signingKey)); + + if (signatureCallback != null) + { + parameters = signatureCallback.apply(parameters); + } + + if (parameters == null) + { + throw new IllegalStateException("SignatureParameters Callback MUST NOT return null."); + } + + if (!signingKey.isSigningKey(parameters.getSignatureCreationTime())) + { + throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + + " is not capable of creating data signatures."); + } + + PGPSignatureGenerator sigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + signingKey.getPublicKey().getPGPPublicKey().getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + signingKey.getPGPPublicKey()); + + char[] passphrase = passphraseProvider.getKeyPassword(signingKey); + sigGen.init(parameters.getSignatureType(), signingKey.unlock(passphrase)); + + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, signingKey.getPGPPublicKey()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + sigGen.setHashedSubpackets(hashedSubpackets.generate()); + + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + return sigGen; + } + + private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) + { + PreferredAlgorithms hashPreferences = key.getHashAlgorithmPreferences(); + if (hashPreferences != null) + { + int[] pref = Arrays.stream(hashPreferences.getPreferences()) + .filter(it -> policy.isAcceptableDocumentSignatureHashAlgorithm(it, new Date())) + .toArray(); + if (pref.length != 0) + { + return pref[0]; + } + } + + return policy.getDefaultDocumentSignatureHashAlgorithm(); + } + + public T setMissingKeyPassphraseCallback(KeyPassphraseProvider callback) + { + defaultKeyPassphraseProvider.setMissingPassphraseCallback(callback); + return (T) this; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java index d575114ce2..927131bce9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPKeySignatureGenerator.java @@ -8,10 +8,7 @@ import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; public abstract class AbstractOpenPGPKeySignatureGenerator { @@ -181,14 +178,4 @@ public void setEncryptionSubkeySubpackets(SignatureSubpacketsFunction encryption { this.encryptionSubkeySubpackets = encryptionSubkeySubpackets; } - - protected KeyPairGeneratorCallback generatePrimaryKey = new KeyPairGeneratorCallback() - { - @Override - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generatePrimaryKey(); - } - }; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java index e30bb22cc2..c07966e7f2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java @@ -19,4 +19,43 @@ public interface KeyPairGeneratorCallback */ PGPKeyPair generateFrom(PGPKeyPairGenerator generator) throws PGPException; + + static KeyPairGeneratorCallback primaryKey() + { + return new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generatePrimaryKey(); + } + }; + } + + static KeyPairGeneratorCallback encryptionKey() + { + return new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateEncryptionSubkey(); + } + }; + } + + static KeyPairGeneratorCallback signingKey() + { + return new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateSigningSubkey(); + } + }; + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java b/pg/src/main/java/org/bouncycastle/openpgp/api/MissingMessagePassphraseCallback.java similarity index 73% rename from pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/MissingMessagePassphraseCallback.java index 2e547018a0..b3ff38af90 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/MissingPassphraseCallback.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/MissingMessagePassphraseCallback.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.api; -public interface MissingPassphraseCallback +public interface MissingMessagePassphraseCallback { /** * Return a passphrase for message decryption. @@ -8,6 +8,6 @@ public interface MissingPassphraseCallback * * @return passphrase */ - char[] getPassphrase(); + char[] getMessagePassphrase(); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java index 46333ab0c7..bf4c54e24f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -59,4 +59,9 @@ public OpenPGPKeyEditor editKey(OpenPGPKey key) { return new OpenPGPKeyEditor(key, implementation, policy); } + + public OpenPGPImplementation getImplementation() + { + return implementation; + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index 423e0d701b..37d311c811 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -1,17 +1,13 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; import java.util.List; /** @@ -29,19 +25,8 @@ * {@link #sign(InputStream)}, passing in an {@link InputStream} containing the data you want to sign. */ public class OpenPGPDetachedSignatureGenerator + extends AbstractOpenPGPDocumentSignatureGenerator { - private final OpenPGPImplementation implementation; - private final OpenPGPPolicy policy; - - // Below lists all use the same indexing - private final List signatureGenerators = new ArrayList<>(); - private final List signingKeys = new ArrayList<>(); - private final List signatureCallbacks = new ArrayList<>(); - private final List signingKeyPassphraseProviders = new ArrayList<>(); - - private final KeyPassphraseProvider.DefaultKeyPassphraseProvider defaultKeyPassphraseProvider = - new KeyPassphraseProvider.DefaultKeyPassphraseProvider(); - /** * Instantiate a signature generator using the default {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. */ @@ -68,21 +53,19 @@ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation) */ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) { - this.implementation = implementation; - this.policy = policy; + super(implementation, policy); } public OpenPGPDetachedSignatureGenerator addKeyPassphrase(char[] passphrase) { - defaultKeyPassphraseProvider.addPassphrase(passphrase); - return this; + return super.addKeyPassphrase(passphrase); } public OpenPGPDetachedSignatureGenerator addSigningKey( OpenPGPKey key) - throws PGPException + throws InvalidSigningKeyException { - return addSigningKey(key, defaultKeyPassphraseProvider); + return super.addSigningKey(key); } /** @@ -96,113 +79,32 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( * @return this * * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey - * @throws PGPException if signing fails + * @throws InvalidSigningKeyException if the key cannot sign */ public OpenPGPDetachedSignatureGenerator addSigningKey( OpenPGPKey key, KeyPassphraseProvider passphraseProvider) - throws PGPException + throws InvalidSigningKeyException { - List signingSubkeys = key.getSigningKeys(); - if (signingSubkeys.isEmpty()) - { - throw new InvalidSigningKeyException("Key " + key.getPrettyFingerprint() + " cannot sign."); - } - OpenPGPKey.OpenPGPSecretKey signingKey = key.getSecretKey(signingSubkeys.get(0)); - - return addSigningKey(signingKey, passphraseProvider, null); + return super.addSigningKey(key, passphraseProvider); } public OpenPGPDetachedSignatureGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, char[] passphrase, SignatureParameters.Callback signatureCallback) - throws PGPException + throws InvalidSigningKeyException { - return addSigningKey( - signingKey, - defaultKeyPassphraseProvider.addPassphrase(signingKey, passphrase), - signatureCallback); + return super.addSigningKey(signingKey, passphrase, signatureCallback); } public OpenPGPDetachedSignatureGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, KeyPassphraseProvider passphraseProvider, SignatureParameters.Callback signatureCallback) - throws PGPException - { - if (!signingKey.isSigningKey()) - { - throw new InvalidSigningKeyException("Subkey cannot sign."); - } - - signingKeys.add(signingKey); - signingKeyPassphraseProviders.add(passphraseProvider); - signatureCallbacks.add(signatureCallback); - return this; - } - - private PGPSignatureGenerator initSignatureGenerator( - OpenPGPKey.OpenPGPSecretKey signingKey, - KeyPassphraseProvider passphraseProvider, - SignatureParameters.Callback signatureCallback) - throws PGPException - { - SignatureParameters parameters = SignatureParameters.dataSignature(policy) - .setSignatureHashAlgorithm(getPreferredHashAlgorithm(signingKey)); - - if (signatureCallback != null) - { - parameters = signatureCallback.apply(parameters); - } - - if (parameters == null) - { - throw new IllegalStateException("SignatureParameters Callback MUST NOT return null."); - } - - if (!signingKey.isSigningKey(parameters.getSignatureCreationTime())) - { - throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + - " is not capable of creating data signatures."); - } - - PGPSignatureGenerator sigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - signingKey.getPublicKey().getPGPPublicKey().getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - signingKey.getPGPPublicKey()); - - char[] passphrase = passphraseProvider.getKeyPassword(signingKey); - sigGen.init(parameters.getSignatureType(), signingKey.unlock(passphrase)); - - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets.setIssuerFingerprint(true, signingKey.getPGPPublicKey()); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - sigGen.setHashedSubpackets(hashedSubpackets.generate()); - - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - return sigGen; - } - - private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) + throws InvalidSigningKeyException { - PreferredAlgorithms hashPreferences = key.getHashAlgorithmPreferences(); - if (hashPreferences != null) - { - int[] pref = Arrays.stream(hashPreferences.getPreferences()) - .filter(it -> policy.isAcceptableDocumentSignatureHashAlgorithm(it, new Date())) - .toArray(); - if (pref.length != 0) - { - return pref[0]; - } - } - - return policy.getDefaultDocumentSignatureHashAlgorithm(); + return super.addSigningKey(signingKey, passphraseProvider, signatureCallback); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java index 66422c4043..87ac8bcee4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPEncryptionNegotiator.java @@ -20,7 +20,7 @@ public interface OpenPGPEncryptionNegotiator * @param configuration message generator configuration * @return negotiated encryption mode and algorithms */ - MessageEncryptionMechanism negotiateEncryption(OpenPGPMessageGenerator.Configuration configuration); + MessageEncryptionMechanism negotiateEncryption(OpenPGPMessageGenerator configuration); static PreferredAEADCiphersuites negotiateAEADCiphersuite(List certificates, OpenPGPPolicy policy) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 8c51fa78e9..4994dd71a8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -3,23 +3,18 @@ import org.bouncycastle.bcpg.AEADAlgorithmTags; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; -import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPLiteralDataGenerator; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPPadding; -import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.InvalidEncryptionKeyException; import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; @@ -30,12 +25,10 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.Stack; import java.util.stream.Collectors; /** @@ -50,15 +43,18 @@ * The encryption mechanism is automatically decided, based on the provided recipient certificates, aiming to maximize * interoperability. * If the user provides one or more signing keys by calling {@link #addSigningKey(OpenPGPKey)} or - * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, SecretKeyPassphraseProvider, SignatureParameters.Callback)}, + * {@link #addSigningKey(OpenPGPKey.OpenPGPSecretKey, KeyPassphraseProvider, SignatureParameters.Callback)}, * the message will be signed. */ public class OpenPGPMessageGenerator + extends AbstractOpenPGPDocumentSignatureGenerator { public static final int BUFFER_SIZE = 1024; - private final OpenPGPImplementation implementation; - private final Configuration config; + private boolean isArmored = true; + public boolean isAllowPadding = true; + private final List encryptionKeys = new ArrayList<>(); + private final List messagePassphrases = new ArrayList<>(); // Literal Data metadata private Date fileModificationDate = null; @@ -78,14 +74,13 @@ public OpenPGPMessageGenerator(OpenPGPImplementation implementation) public OpenPGPMessageGenerator(OpenPGPImplementation implementation, OpenPGPPolicy policy) { - this.implementation = Objects.requireNonNull(implementation); - this.config = new Configuration(policy); + super(implementation, policy); } /** * Add a recipients certificate to the set of encryption keys. * Subkeys will be selected using the default {@link SubkeySelector}, which can be replaced by calling - * {@link Configuration#setEncryptionKeySelector(SubkeySelector)}. + * {@link #setEncryptionKeySelector(SubkeySelector)}. * The recipient will be able to decrypt the message using their corresponding secret key. * * @param recipientCertificate recipient certificate (public key) @@ -94,7 +89,7 @@ public OpenPGPMessageGenerator(OpenPGPImplementation implementation, OpenPGPPoli public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate) throws InvalidEncryptionKeyException { - return addEncryptionCertificate(recipientCertificate, config.encryptionKeySelector); + return addEncryptionCertificate(recipientCertificate, encryptionKeySelector); } /** @@ -111,14 +106,14 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip SubkeySelector subkeySelector) throws InvalidEncryptionKeyException { - List encryptionKeys = - subkeySelector.select(recipientCertificate, config.policy); - if (encryptionKeys.isEmpty()) + List subkeys = + subkeySelector.select(recipientCertificate, policy); + if (subkeys.isEmpty()) { throw new InvalidEncryptionKeyException("Certificate " + recipientCertificate.getKeyIdentifier() + " does not have valid encryption subkeys."); } - config.encryptionKeys.addAll(encryptionKeys); + this.encryptionKeys.addAll(subkeys); return this; } @@ -138,7 +133,7 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenP throw new InvalidEncryptionKeyException("Provided subkey " + encryptionKey.getKeyIdentifier() + " is not a valid encryption key."); } - config.encryptionKeys.add(encryptionKey); + encryptionKeys.add(encryptionKey); return this; } @@ -151,14 +146,14 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenP */ public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) { - config.messagePassphrases.add(passphrase); + messagePassphrases.add(passphrase); return this; } /** * Sign the message using a secret signing key. * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by - * calling {@link Configuration#setSigningKeySelector(SubkeySelector)}. + * calling {@link #setSigningKeySelector(SubkeySelector)}. * * @param signingKey OpenPGP key * @return this @@ -167,13 +162,13 @@ public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) throws InvalidSigningKeyException { - return addSigningKey(signingKey, key -> null); + return super.addSigningKey(signingKey); } /** * Sign the message using a secret signing key. * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by - * calling {@link Configuration#setSigningKeySelector(SubkeySelector)}. + * calling {@link #setSigningKeySelector(SubkeySelector)}. * * @param signingKey OpenPGP key * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. @@ -182,40 +177,24 @@ public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) */ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey signingKey, - SecretKeyPassphraseProvider signingKeyDecryptorProvider) + KeyPassphraseProvider signingKeyDecryptorProvider) throws InvalidSigningKeyException { - List publicSigningKeys = - config.signingKeySelector.select(signingKey, implementation.policy()); - - List signingKeys = new ArrayList<>(); - for (OpenPGPCertificate.OpenPGPComponentKey publicKey : publicSigningKeys) - { - OpenPGPKey.OpenPGPSecretKey secretKey = signingKey.getSecretKey(publicKey); - if (secretKey == null) - { - throw new InvalidSigningKeyException("Secret key " + publicKey.getKeyIdentifier() + - " is missing from the OpenPGP key."); - } - signingKeys.add(secretKey); - } - - if (signingKeys.isEmpty()) - { - throw new InvalidSigningKeyException("OpenPGP key " + signingKey.getKeyIdentifier() + - " does not have any valid signing subkeys."); - } + return super.addSigningKey(signingKey, signingKeyDecryptorProvider); + } - for (OpenPGPKey.OpenPGPSecretKey subkey : signingKeys) - { - config.signingKeys.add(new Signer(subkey, signingKeyDecryptorProvider, null)); - } - return this; + public OpenPGPMessageGenerator addSigningKey( + OpenPGPKey.OpenPGPSecretKey signingKey, + char[] passphrase, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException + { + return super.addSigningKey(signingKey, passphrase, signatureCallback); } /** * Sign the message using a signing-capable (sub-)key. - * If the signing key is protected with a passphrase, the given {@link SecretKeyPassphraseProvider} can be + * If the signing key is protected with a passphrase, the given {@link KeyPassphraseProvider} can be * used to unlock the key. * The signature can be customized by providing a {@link SignatureParameters.Callback}, which can change * the signature type, creation time and signature subpackets. @@ -228,18 +207,11 @@ public OpenPGPMessageGenerator addSigningKey( */ public OpenPGPMessageGenerator addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, - SecretKeyPassphraseProvider signingKeyPassphraseProvider, + KeyPassphraseProvider signingKeyPassphraseProvider, SignatureParameters.Callback signatureParameterCallback) throws InvalidSigningKeyException { - if (!signingKey.isSigningKey()) - { - throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + - " is not a valid signing key."); - } - - config.signingKeys.add(new Signer(signingKey, signingKeyPassphraseProvider, signatureParameterCallback)); - return this; + return super.addSigningKey(signingKey, signingKeyPassphraseProvider, signatureParameterCallback); } /** @@ -250,7 +222,13 @@ public OpenPGPMessageGenerator addSigningKey( */ public OpenPGPMessageGenerator setArmored(boolean armored) { - this.config.setArmored(armored); + this.isArmored = armored; + return this; + } + + public OpenPGPMessageGenerator setAllowPadding(boolean allowPadding) + { + this.isAllowPadding = allowPadding; return this; } @@ -306,15 +284,15 @@ public OpenPGPMessageOutputStream open(OutputStream out) * The output will only be wrapped in ASCII armor, if {@link #setArmored(boolean)} is set * to true (is true by default). * The {@link ArmoredOutputStream} will be instantiated using the {@link ArmoredOutputStreamFactory} - * which can be replaced using {@link Configuration#setArmorStreamFactory(ArmoredOutputStreamFactory)}. + * which can be replaced using {@link #setArmorStreamFactory(ArmoredOutputStreamFactory)}. * * @param builder OpenPGP message output stream builder */ private void applyOptionalAsciiArmor(OpenPGPMessageOutputStream.Builder builder) { - if (config.isArmored) + if (isArmored) { - builder.armor(config.armorStreamFactory); + builder.armor(armorStreamFactory); } } @@ -331,7 +309,7 @@ private void applyOptionalEncryption( OpenPGPMessageOutputStream.Builder builder, PGPEncryptedDataGenerator.SessionKeyExtractionCallback sessionKeyExtractionCallback) { - MessageEncryptionMechanism encryption = config.negotiateEncryption(); + MessageEncryptionMechanism encryption = encryptionNegotiator.negotiateEncryption(this); if (!encryption.isEncrypted()) { return; // No encryption @@ -365,7 +343,7 @@ private void applyOptionalEncryption( encGen.setSessionKeyExtractionCallback(sessionKeyExtractionCallback); // Setup asymmetric message encryption - for (OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey : config.encryptionKeys) + for (OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey : encryptionKeys) { PublicKeyKeyEncryptionMethodGenerator method = implementation.publicKeyKeyEncryptionMethodGenerator( encryptionSubkey.getPGPPublicKey()); @@ -373,7 +351,7 @@ private void applyOptionalEncryption( } // Setup symmetric (password-based) message encryption - for (char[] passphrase : config.messagePassphrases) + for (char[] passphrase : messagePassphrases) { PBEKeyEncryptionMethodGenerator skeskGen; switch (encryption.getMode()) @@ -410,7 +388,7 @@ private void applyOptionalEncryption( }); // Optionally, append a padding packet as the last packet inside the SEIPDv2 packet. - if (encryption.getMode() == EncryptedDataPacketType.SEIPDv2 && config.isPadded) + if (encryption.getMode() == EncryptedDataPacketType.SEIPDv2 && isAllowPadding) { builder.padding(o -> new OpenPGPMessageOutputStream.PaddingPacketAppenderOutputStream(o, PGPPadding::new)); } @@ -425,45 +403,13 @@ private void applySignatures(OpenPGPMessageOutputStream.Builder builder) { builder.sign(o -> { - Stack signatureGenerators = new Stack<>(); - for (Signer s : config.signingKeys) + for (int i = 0; i < signingKeys.size(); i++) { - OpenPGPKey.OpenPGPSecretKey signingSubkey = s.signingKey; - - SignatureParameters parameters = SignatureParameters.dataSignature(config.policy) - .setSignatureCreationTime(new Date()) - .setSignatureHashAlgorithm( - config.negotiateHashAlgorithm(signingSubkey.getOpenPGPKey(), signingSubkey)); - if (s.signatureParameters != null) - { - parameters = s.signatureParameters.apply(parameters); - } - - if (parameters == null) - { - throw new IllegalStateException("SignatureParameters callback MUST NOT return null."); - } - - PGPSignatureGenerator sigGen = new PGPSignatureGenerator( - implementation.pgpContentSignerBuilder( - signingSubkey.getPGPSecretKey().getPublicKey().getAlgorithm(), - parameters.getSignatureHashAlgorithmId()), - signingSubkey.getPGPSecretKey().getPublicKey()); - char[] passphrase = signingSubkey.isLocked() ? - s.passphraseProvider.providePassphrase(signingSubkey) : null; - PGPPrivateKey privateKey = signingSubkey.unlock(passphrase); - - sigGen.init(parameters.getSignatureType(), privateKey); - PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); - hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - hashedSubpackets.setIssuerFingerprint(true, signingSubkey.getPGPSecretKey().getPublicKey()); - sigGen.setHashedSubpackets(hashedSubpackets.generate()); - - PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); - unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - sigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); - - signatureGenerators.push(sigGen); + OpenPGPKey.OpenPGPSecretKey signingKey = signingKeys.get(i); + KeyPassphraseProvider keyPassphraseProvider = signingKeyPassphraseProviders.get(i); + SignatureParameters.Callback signatureCallback = signatureCallbacks.get(i); + PGPSignatureGenerator sigGen = initSignatureGenerator(signingKey, keyPassphraseProvider, signatureCallback); + super.signatureGenerators.add(sigGen); } // One-Pass-Signatures @@ -481,7 +427,7 @@ private void applySignatures(OpenPGPMessageOutputStream.Builder builder) private void applyOptionalCompression(OpenPGPMessageOutputStream.Builder builder) { - int compressionAlgorithm = config.negotiateCompression(); + int compressionAlgorithm = compressionNegotiator.negotiateCompression(this, policy); if (compressionAlgorithm == CompressionAlgorithmTags.UNCOMPRESSED) { return; // Uncompressed @@ -527,324 +473,168 @@ private void applyLiteralDataWrap(OpenPGPMessageOutputStream.Builder builder) }); } - public OpenPGPMessageGenerator setIsPadded(boolean isPadded) - { - config.setPadded(isPadded); - return this; - } - - public Configuration getConfiguration() - { - return config; - } + // Factory for creating ASCII armor + private ArmoredOutputStreamFactory armorStreamFactory = + outputStream -> ArmoredOutputStream.builder() + .clearHeaders() // Hide version + .enableCRC(false) // Disable CRC sum + .build(outputStream); - public interface ArmoredOutputStreamFactory - extends OpenPGPMessageOutputStream.OutputStreamFactory - { - ArmoredOutputStream get(OutputStream out); - } - - public interface CompressionNegotiator + private SubkeySelector encryptionKeySelector = new SubkeySelector() { - /** - * Negotiate a compression algorithm. - * Returning {@link org.bouncycastle.bcpg.CompressionAlgorithmTags#UNCOMPRESSED} will result in no compression. - * - * @param configuration message generator configuration - * @return negotiated compression algorithm ID - */ - int negotiateCompression(Configuration configuration, OpenPGPPolicy policy); - } - - public interface HashAlgorithmNegotiator - { - int negotiateHashAlgorithm(OpenPGPKey key, - OpenPGPCertificate.OpenPGPComponentKey subkey, - OpenPGPPolicy policy); - } - - public static class Configuration - { - private boolean isArmored = true; - public boolean isPadded = true; - private final List encryptionKeys = new ArrayList<>(); - private final List signingKeys = new ArrayList<>(); - private final List messagePassphrases = new ArrayList<>(); - private final OpenPGPPolicy policy; - - public Configuration(OpenPGPPolicy policy) - { - this.policy = policy; - } - - // Factory for creating ASCII armor - private ArmoredOutputStreamFactory armorStreamFactory = - outputStream -> ArmoredOutputStream.builder() - .clearHeaders() // Hide version - .enableCRC(false) // Disable CRC sum - .build(outputStream); - - private SubkeySelector encryptionKeySelector = new SubkeySelector() + @Override + public List select(OpenPGPCertificate certificate, + OpenPGPPolicy policy) { - @Override - public List select(OpenPGPCertificate certificate, - OpenPGPPolicy policy) - { - return certificate.getEncryptionKeys() - .stream() - .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) - .collect(Collectors.toList()); - } - }; - - private SubkeySelector signingKeySelector = new SubkeySelector() - { - @Override - public List select(OpenPGPCertificate certificate, - OpenPGPPolicy policy) - { - return certificate.getSigningKeys() - .stream() - .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) - .collect(Collectors.toList()); - } - }; - - // Encryption method negotiator for when only password-based encryption is requested - private OpenPGPEncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> - MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB); - - // Encryption method negotiator for when public-key encryption is requested - private OpenPGPEncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> - { - List certificates = encryptionKeys.stream() - .map(OpenPGPCertificate.OpenPGPCertificateComponent::getCertificate) - .distinct() + return certificate.getEncryptionKeys() + .stream() + .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) .collect(Collectors.toList()); - - // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. - if (OpenPGPEncryptionNegotiator.allRecipientsSupportSeipd2(certificates)) - { - PreferredAEADCiphersuites commonDenominator = - OpenPGPEncryptionNegotiator.negotiateAEADCiphersuite(certificates, configuration.policy); - return MessageEncryptionMechanism.aead(commonDenominator.getAlgorithms()[0]); - } - else if (OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificates)) - { - return MessageEncryptionMechanism.librePgp( - OpenPGPEncryptionNegotiator.bestOEDEncryptionModeByWeight(certificates, configuration.policy)); - } - else - { - return MessageEncryptionMechanism.integrityProtected( - OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight( - certificates, configuration.policy)); - } - }; - - // Primary encryption method negotiator - private final OpenPGPEncryptionNegotiator encryptionNegotiator = - configuration -> - { - // No encryption methods provided -> Unencrypted message - if (configuration.encryptionKeys.isEmpty() && configuration.messagePassphrases.isEmpty()) - { - return MessageEncryptionMechanism.unencrypted(); - } - - // No public-key encryption requested -> password-based encryption - else if (configuration.encryptionKeys.isEmpty()) - { - // delegate negotiation to pbe negotiator - return passwordBasedEncryptionNegotiator.negotiateEncryption(configuration); - } - else - { - // delegate negotiation to pkbe negotiator - return publicKeyBasedEncryptionNegotiator.negotiateEncryption(configuration); - } - }; - - // TODO: Implement properly, taking encryption into account (sign-only should not compress) - private CompressionNegotiator compressionNegotiator = - (configuration, policy) -> CompressionAlgorithmTags.UNCOMPRESSED; - - private HashAlgorithmNegotiator hashAlgorithmNegotiator = - (key, subkey, policy) -> - { - // TODO: Take into consideration hash preferences of recipients, not the sender - PreferredAlgorithms hashPreferences = subkey.getHashAlgorithmPreferences(); - if (hashPreferences != null) - { - int[] pref = Arrays.stream(hashPreferences.getPreferences()) - .filter(it -> policy.isAcceptableDocumentSignatureHashAlgorithm(it, new Date())) - .toArray(); - if (pref.length != 0) - { - return pref[0]; - } - } - - return policy.getDefaultDocumentSignatureHashAlgorithm(); - }; - - /** - * Replace the default {@link OpenPGPEncryptionNegotiator} that gets to decide, which - * {@link MessageEncryptionMechanism} mode to use if only password-based encryption is used. - * - * @param pbeNegotiator custom PBE negotiator. - * @return this - */ - public Configuration setPasswordBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pbeNegotiator) - { - this.passwordBasedEncryptionNegotiator = Objects.requireNonNull(pbeNegotiator); - return this; - } - - /** - * Replace the default {@link OpenPGPEncryptionNegotiator} that decides, which - * {@link MessageEncryptionMechanism} mode to use if public-key encryption is used. - * - * @param pkbeNegotiator custom encryption negotiator that gets to decide if PK-based encryption is used - * @return this - */ - public Configuration setPublicKeyBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pkbeNegotiator) - { - this.publicKeyBasedEncryptionNegotiator = Objects.requireNonNull(pkbeNegotiator); - return this; } + }; - /** - * Replace the default encryption key selector with a custom implementation. - * The encryption key selector is responsible for selecting one or more encryption subkeys from a - * recipient certificate. - * - * @param encryptionKeySelector selector for encryption (sub-)keys - * @return this - */ - public Configuration setEncryptionKeySelector(SubkeySelector encryptionKeySelector) - { - this.encryptionKeySelector = Objects.requireNonNull(encryptionKeySelector); - return this; - } + // Encryption method negotiator for when only password-based encryption is requested + private OpenPGPEncryptionNegotiator passwordBasedEncryptionNegotiator = configuration -> + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB); - /** - * Replace the default signing key selector with a custom implementation. - * The signing key selector is responsible for selecting one or more signing subkeys from a signing key. - * - * @param signingKeySelector selector for signing (sub-)keys - * @return this - */ - public Configuration setSigningKeySelector(SubkeySelector signingKeySelector) - { - this.signingKeySelector = Objects.requireNonNull(signingKeySelector); - return this; - } + // Encryption method negotiator for when public-key encryption is requested + private OpenPGPEncryptionNegotiator publicKeyBasedEncryptionNegotiator = configuration -> + { + List certificates = encryptionKeys.stream() + .map(OpenPGPCertificate.OpenPGPCertificateComponent::getCertificate) + .distinct() + .collect(Collectors.toList()); - /** - * Replace the default {@link CompressionNegotiator} with a custom implementation. - * The {@link CompressionNegotiator} is used to negotiate, whether and how to compress the literal data packet. - * - * @param compressionNegotiator negotiator - * @return this - */ - public Configuration setCompressionNegotiator(CompressionNegotiator compressionNegotiator) + // Decide, if SEIPDv2 (OpenPGP v6-style AEAD) is supported by all recipients. + if (OpenPGPEncryptionNegotiator.allRecipientsSupportSeipd2(certificates)) { - this.compressionNegotiator = Objects.requireNonNull(compressionNegotiator); - return this; + PreferredAEADCiphersuites commonDenominator = + OpenPGPEncryptionNegotiator.negotiateAEADCiphersuite(certificates, policy); + return MessageEncryptionMechanism.aead(commonDenominator.getAlgorithms()[0]); } - - /** - * Replace the default {@link HashAlgorithmNegotiator} with a custom implementation. - * - * @param hashAlgorithmNegotiator custom hash algorithm negotiator - * @return this - */ - public Configuration setHashAlgorithmNegotiator(HashAlgorithmNegotiator hashAlgorithmNegotiator) + else if (OpenPGPEncryptionNegotiator.allRecipientsSupportLibrePGPOED(certificates)) { - this.hashAlgorithmNegotiator = Objects.requireNonNull(hashAlgorithmNegotiator); - return this; + return MessageEncryptionMechanism.librePgp( + OpenPGPEncryptionNegotiator.bestOEDEncryptionModeByWeight(certificates, policy)); } - - /** - * Replace the {@link ArmoredOutputStreamFactory} with a custom implementation. - * - * @param factory factory for {@link ArmoredOutputStream} instances - * @return this - */ - public Configuration setArmorStreamFactory(ArmoredOutputStreamFactory factory) + else { - this.armorStreamFactory = Objects.requireNonNull(factory); - return this; + return MessageEncryptionMechanism.integrityProtected( + OpenPGPEncryptionNegotiator.bestSymmetricKeyAlgorithmByWeight( + certificates, policy)); } + }; - public Configuration setArmored(boolean isArmored) - { - this.isArmored = isArmored; - return this; - } + // Primary encryption method negotiator + private final OpenPGPEncryptionNegotiator encryptionNegotiator = + configuration -> + { + // No encryption methods provided -> Unencrypted message + if (encryptionKeys.isEmpty() && messagePassphrases.isEmpty()) + { + return MessageEncryptionMechanism.unencrypted(); + } - public Configuration setPadded(boolean isPadded) - { - this.isPadded = isPadded; - return this; - } + // No public-key encryption requested -> password-based encryption + else if (encryptionKeys.isEmpty()) + { + // delegate negotiation to pbe negotiator + return passwordBasedEncryptionNegotiator.negotiateEncryption(configuration); + } + else + { + // delegate negotiation to pkbe negotiator + return publicKeyBasedEncryptionNegotiator.negotiateEncryption(configuration); + } + }; - public int negotiateCompression() - { - return compressionNegotiator.negotiateCompression(this, policy); - } + // TODO: Implement properly, taking encryption into account (sign-only should not compress) + private CompressionNegotiator compressionNegotiator = + (configuration, policy) -> CompressionAlgorithmTags.UNCOMPRESSED; - public int negotiateHashAlgorithm(OpenPGPKey signingKey, OpenPGPKey.OpenPGPSecretKey signingSubkey) - { - return hashAlgorithmNegotiator.negotiateHashAlgorithm(signingKey, signingSubkey, policy); - } + /** + * Replace the default {@link OpenPGPEncryptionNegotiator} that gets to decide, which + * {@link MessageEncryptionMechanism} mode to use if only password-based encryption is used. + * + * @param pbeNegotiator custom PBE negotiator. + * @return this + */ + public OpenPGPMessageGenerator setPasswordBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pbeNegotiator) + { + this.passwordBasedEncryptionNegotiator = Objects.requireNonNull(pbeNegotiator); + return this; + } - public MessageEncryptionMechanism negotiateEncryption() - { - return encryptionNegotiator.negotiateEncryption(this); - } + /** + * Replace the default {@link OpenPGPEncryptionNegotiator} that decides, which + * {@link MessageEncryptionMechanism} mode to use if public-key encryption is used. + * + * @param pkbeNegotiator custom encryption negotiator that gets to decide if PK-based encryption is used + * @return this + */ + public OpenPGPMessageGenerator setPublicKeyBasedEncryptionNegotiator(OpenPGPEncryptionNegotiator pkbeNegotiator) + { + this.publicKeyBasedEncryptionNegotiator = Objects.requireNonNull(pkbeNegotiator); + return this; } /** - * Tuple representing an OpenPGP key used for signing. + * Replace the default encryption key selector with a custom implementation. + * The encryption key selector is responsible for selecting one or more encryption subkeys from a + * recipient certificate. + * + * @param encryptionKeySelector selector for encryption (sub-)keys + * @return this */ - static class Signer + public OpenPGPMessageGenerator setEncryptionKeySelector(SubkeySelector encryptionKeySelector) { - private final OpenPGPKey.OpenPGPSecretKey signingKey; - private final SecretKeyPassphraseProvider passphraseProvider; - private final SignatureParameters.Callback signatureParameters; + this.encryptionKeySelector = Objects.requireNonNull(encryptionKeySelector); + return this; + } - public Signer(OpenPGPKey.OpenPGPSecretKey signingKey, - SecretKeyPassphraseProvider passphraseProvider, - SignatureParameters.Callback signatureParameters) - { - this.signingKey = signingKey; - this.passphraseProvider = passphraseProvider; - this.signatureParameters = signatureParameters; - } + + /** + * Replace the default {@link CompressionNegotiator} with a custom implementation. + * The {@link CompressionNegotiator} is used to negotiate, whether and how to compress the literal data packet. + * + * @param compressionNegotiator negotiator + * @return this + */ + public OpenPGPMessageGenerator setCompressionNegotiator(CompressionNegotiator compressionNegotiator) + { + this.compressionNegotiator = Objects.requireNonNull(compressionNegotiator); + return this; } /** - * Interface for selecting a subset of keys from a {@link PGPKeyRing}. - * This is useful e.g. for selecting a signing key from an OpenPGP key, or a for selecting all - * encryption capable subkeys of a certificate. + * Replace the {@link ArmoredOutputStreamFactory} with a custom implementation. + * + * @param factory factory for {@link ArmoredOutputStream} instances + * @return this */ - public interface SubkeySelector + public OpenPGPMessageGenerator setArmorStreamFactory(ArmoredOutputStreamFactory factory) + { + this.armorStreamFactory = Objects.requireNonNull(factory); + return this; + } + + + public interface ArmoredOutputStreamFactory + extends OpenPGPMessageOutputStream.OutputStreamFactory + { + ArmoredOutputStream get(OutputStream out); + } + + public interface CompressionNegotiator { /** - * Given a {@link PGPKeyRing}, select a subset of the key rings (sub-)keys and return their - * {@link KeyIdentifier KeyIdentifiers}. + * Negotiate a compression algorithm. + * Returning {@link org.bouncycastle.bcpg.CompressionAlgorithmTags#UNCOMPRESSED} will result in no compression. * - * @param certificate OpenPGP key or certificate - * @param policy OpenPGP algorithm policy - * @return non-null list of identifiers + * @param messageGenerator message generator + * @return negotiated compression algorithm ID */ - List select(OpenPGPCertificate certificate, OpenPGPPolicy policy); + int negotiateCompression(OpenPGPMessageGenerator messageGenerator, OpenPGPPolicy policy); } - public interface SecretKeyPassphraseProvider - { - char[] providePassphrase(OpenPGPKey.OpenPGPSecretKey key); - } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java index d52db76900..273d247580 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageOutputStream.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.List; import java.util.Stack; /** @@ -355,9 +356,9 @@ static class SignatureGeneratorOutputStream { private final OutputStream out; - private final Stack signatureGenerators; + private final List signatureGenerators; - public SignatureGeneratorOutputStream(OutputStream out, Stack signatureGenerators) + public SignatureGeneratorOutputStream(OutputStream out, List signatureGenerators) { this.out = out; this.signatureGenerators = signatureGenerators; @@ -397,9 +398,9 @@ public void write(byte[] b, int off, int len) public void close() throws IOException { - while (!signatureGenerators.isEmpty()) + for (int i = signatureGenerators.size() - 1; i >= 0; i--) { - PGPSignatureGenerator gen = signatureGenerators.pop(); + PGPSignatureGenerator gen = signatureGenerators.get(i); PGPSignature sig = null; try { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index 4f97469c20..c815148c5d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -186,14 +186,14 @@ public OpenPGPMessageProcessor addMessagePassphrase(char[] messagePassphrase) } /** - * Set a {@link MissingPassphraseCallback} which will be invoked if the message is encrypted using a passphrase, + * Set a {@link MissingMessagePassphraseCallback} which will be invoked if the message is encrypted using a passphrase, * but no working passphrase was provided. * * @param callback callback * @return this */ public OpenPGPMessageProcessor setMissingMessagePassphraseCallback( - MissingPassphraseCallback callback) + MissingMessagePassphraseCallback callback) { this.configuration.missingMessagePassphraseCallback = callback; return this; @@ -383,7 +383,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) if (!skesks.isEmpty() && configuration.missingMessagePassphraseCallback != null) { char[] passphrase; - while ((passphrase = configuration.missingMessagePassphraseCallback.getPassphrase()) != null) + while ((passphrase = configuration.missingMessagePassphraseCallback.getMessagePassphrase()) != null) { for (PGPPBEEncryptedData skesk : skesks) { @@ -491,7 +491,7 @@ public static class Configuration private final OpenPGPKeyMaterialPool.OpenPGPKeyPool keyPool; private final KeyPassphraseProvider.DefaultKeyPassphraseProvider keyPassphraseProvider; public final List messagePassphrases = new ArrayList<>(); - private MissingPassphraseCallback missingMessagePassphraseCallback; + private MissingMessagePassphraseCallback missingMessagePassphraseCallback; private PGPExceptionCallback exceptionCallback = null; private PGPSessionKey sessionKey; private Date verifyNotAfter = new Date(); // now diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index d3929a7f0d..495179b7a6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -211,7 +211,7 @@ public WithPrimaryKey signOnlyKey() throws PGPException { return withPrimaryKey( - generatePrimaryKey, + KeyPairGeneratorCallback.primaryKey(), SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() { @Override @@ -234,17 +234,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa public WithPrimaryKey withPrimaryKey() throws PGPException { - return withPrimaryKey( - new KeyPairGeneratorCallback() - { - @Override - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generatePrimaryKey(); - } - } - ); + return withPrimaryKey(KeyPairGeneratorCallback.primaryKey()); } /** @@ -432,14 +422,7 @@ public WithPrimaryKey addUserId( public WithPrimaryKey addEncryptionSubkey() throws PGPException { - return addEncryptionSubkey(new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateEncryptionSubkey(); - } - }); + return addEncryptionSubkey(KeyPairGeneratorCallback.encryptionKey()); } /** @@ -559,14 +542,7 @@ public WithPrimaryKey addEncryptionSubkey( public WithPrimaryKey addSigningSubkey() throws PGPException { - return addSigningSubkey(new KeyPairGeneratorCallback() - { - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateSigningSubkey(); - } - }); + return addSigningSubkey(KeyPairGeneratorCallback.signingKey()); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SubkeySelector.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SubkeySelector.java new file mode 100644 index 0000000000..5369b4a2f4 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SubkeySelector.java @@ -0,0 +1,24 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.KeyIdentifier; +import org.bouncycastle.openpgp.PGPKeyRing; + +import java.util.List; + +/** + * Interface for selecting a subset of keys from a {@link PGPKeyRing}. + * This is useful e.g. for selecting a signing key from an OpenPGP key, or a for selecting all + * encryption capable subkeys of a certificate. + */ +public interface SubkeySelector +{ + /** + * Given a {@link PGPKeyRing}, select a subset of the key rings (sub-)keys and return their + * {@link KeyIdentifier KeyIdentifiers}. + * + * @param certificate OpenPGP key or certificate + * @param policy OpenPGP algorithm policy + * @return non-null list of identifiers + */ + List select(OpenPGPCertificate certificate, OpenPGPPolicy policy); +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java index 7580772547..bdca1ea999 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -55,8 +55,8 @@ private void performTestsWith(OpenPGPApi api) private void armoredLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.setIsPadded(false); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() + .setAllowPadding(false); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -76,9 +76,9 @@ private void armoredLiteralDataPacket(OpenPGPApi api) private void unarmoredLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.setArmored(false); // disable ASCII armor - gen.setIsPadded(false); // disable padding + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() + .setArmored(false) // disable ASCII armor + .setAllowPadding(false); // disable padding ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -94,10 +94,9 @@ private void unarmoredLiteralDataPacket(OpenPGPApi api) private void armoredCompressedLiteralDataPacket(OpenPGPApi api) throws PGPException, IOException { - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.setIsPadded(false); - OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); - configuration.setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() + .setAllowPadding(false) + .setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -117,11 +116,10 @@ private void armoredCompressedLiteralDataPacket(OpenPGPApi api) private void unarmoredCompressedLiteralDataPacket(OpenPGPApi api) throws IOException, PGPException { - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.setArmored(false); // no armor - gen.setIsPadded(false); - OpenPGPMessageGenerator.Configuration configuration = gen.getConfiguration(); - configuration.setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); + OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() + .setArmored(false) // no armor + .setAllowPadding(false) + .setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OpenPGPMessageOutputStream msgOut = gen.open(bOut); @@ -172,7 +170,7 @@ private void seipd2EncryptedSignedMessage(OpenPGPApi api) OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() - .setIsPadded(true) + .setAllowPadding(true) .setArmored(true) .addSigningKey(key) .addEncryptionCertificate(key); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 3081348865..899e2801c4 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -88,10 +88,8 @@ private void roundtripUnarmoredPlaintextMessage(OpenPGPApi api) { OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(false) - .setIsPadded(false); - - gen.getConfiguration().setCompressionNegotiator( - (conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); + .setAllowPadding(false) + .setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -114,9 +112,8 @@ private void roundtripArmoredPlaintextMessage(OpenPGPApi api) { OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) - .setIsPadded(false); - gen.getConfiguration().setCompressionNegotiator( - (conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); + .setAllowPadding(false) + .setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.UNCOMPRESSED); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -140,9 +137,8 @@ private void roundTripCompressedMessage(OpenPGPApi api) { OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) - .setIsPadded(false); - gen.getConfiguration().setCompressionNegotiator( - (conf, neg) -> CompressionAlgorithmTags.ZIP); + .setAllowPadding(false) + .setCompressionNegotiator((conf, neg) -> CompressionAlgorithmTags.ZIP); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); @@ -168,8 +164,7 @@ private void roundTripCompressedSymEncMessageMessage(OpenPGPApi api) .setSessionKeyExtractionCallback( sk -> this.encryptionSessionKey = sk ) - .setIsPadded(false); - gen.getConfiguration() + .setAllowPadding(false) .setPasswordBasedEncryptionNegotiator(conf -> MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256)) .setCompressionNegotiator( @@ -203,9 +198,9 @@ private void roundTripSymEncMessageWithMultiplePassphrases(OpenPGPApi api) OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .addEncryptionPassphrase("orange".toCharArray()) .addEncryptionPassphrase("violet".toCharArray()) - .setSessionKeyExtractionCallback(sk -> this.encryptionSessionKey = sk); - gen.getConfiguration().setPasswordBasedEncryptionNegotiator(configuration -> - MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); + .setSessionKeyExtractionCallback(sk -> this.encryptionSessionKey = sk) + .setPasswordBasedEncryptionNegotiator(configuration -> + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); OutputStream encOut = gen.open(bOut); encOut.write(PLAINTEXT); @@ -232,7 +227,7 @@ private void roundTripSymEncMessageWithMultiplePassphrases(OpenPGPApi api) bOut = new ByteArrayOutputStream(); bIn = new ByteArrayInputStream(ciphertext); processor = api.decryptAndOrVerifyMessage(); - decIn = processor.setMissingMessagePassphraseCallback(new StackPassphraseCallback("orange".toCharArray())) + decIn = processor.setMissingMessagePassphraseCallback(new StackMessagePassphraseCallback("orange".toCharArray())) // wrong passphrase, so missing callback is invoked .addMessagePassphrase("yellow".toCharArray()) .process(bIn); @@ -330,7 +325,7 @@ private void roundTripV6KeyEncryptedMessage(OpenPGPApi api) OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage() .setArmored(true) .addEncryptionCertificate(key) - .setIsPadded(false); + .setAllowPadding(false); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); OutputStream msgOut = gen.open(bOut); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 29e60b11b8..e980da3293 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -586,15 +586,7 @@ private void testGenerateKeyWithoutSignatures(OpenPGPApi api) { OpenPGPKey key = api.generateKey() .withPrimaryKey( - new KeyPairGeneratorCallback() - { - @Override - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generatePrimaryKey(); - } - }, + KeyPairGeneratorCallback.primaryKey(), // No direct-key sig new SignatureParameters.Callback() { @@ -604,15 +596,7 @@ public SignatureParameters apply(SignatureParameters parameters) { } }) .addSigningSubkey( - new KeyPairGeneratorCallback() - { - @Override - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateSigningSubkey(); - } - }, + KeyPairGeneratorCallback.signingKey(), // No subkey binding sig new SignatureParameters.Callback() { @@ -632,15 +616,7 @@ public SignatureParameters apply(SignatureParameters parameters) } }) .addEncryptionSubkey( - new KeyPairGeneratorCallback() - { - @Override - public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) - throws PGPException - { - return generator.generateEncryptionSubkey(); - } - }, + KeyPairGeneratorCallback.encryptionKey(), // No subkey binding sig new SignatureParameters.Callback() { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackMessagePassphraseCallback.java similarity index 53% rename from pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java rename to pg/src/test/java/org/bouncycastle/openpgp/api/test/StackMessagePassphraseCallback.java index 33c8463ba8..87d43cf551 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackPassphraseCallback.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StackMessagePassphraseCallback.java @@ -1,33 +1,33 @@ package org.bouncycastle.openpgp.api.test; -import org.bouncycastle.openpgp.api.MissingPassphraseCallback; +import org.bouncycastle.openpgp.api.MissingMessagePassphraseCallback; import java.util.Collection; import java.util.Collections; import java.util.Stack; /** - * Test implementation of {@link MissingPassphraseCallback} which provides passphrases by popping + * Test implementation of {@link MissingMessagePassphraseCallback} which provides passphrases by popping * them from a provided {@link Stack}. */ -public class StackPassphraseCallback - implements MissingPassphraseCallback +public class StackMessagePassphraseCallback + implements MissingMessagePassphraseCallback { private final Stack passphases; - public StackPassphraseCallback(char[] passphrase) + public StackMessagePassphraseCallback(char[] passphrase) { this(Collections.singleton(passphrase)); } - public StackPassphraseCallback(Collection passphrases) + public StackMessagePassphraseCallback(Collection passphrases) { this.passphases = new Stack<>(); this.passphases.addAll(passphrases); } @Override - public char[] getPassphrase() + public char[] getMessagePassphrase() { if (passphases.isEmpty()) { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java index 92375a46bf..b64fa3b60c 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/StaticV6OpenPGPMessageGeneratorTest.java @@ -10,6 +10,7 @@ import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; import org.bouncycastle.openpgp.api.OpenPGPPolicy; +import org.bouncycastle.openpgp.api.SubkeySelector; import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; @@ -82,22 +83,22 @@ private void staticSignedMessage() */ public OpenPGPMessageGenerator getStaticGenerator() { - OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator(); - - gen.getConfiguration() + OpenPGPMessageGenerator gen = new OpenPGPMessageGenerator() + .setSigningKeySelector(new SubkeySelector() + { + @Override + public List select( + OpenPGPCertificate certificate, OpenPGPPolicy policy) + { + return Collections.singletonList(certificate.getKey(signingKeyIdentifier)); + } + }) .setEncryptionKeySelector( - new OpenPGPMessageGenerator.SubkeySelector() { + new SubkeySelector() { @Override public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { return Collections.singletonList(certificate.getKey(encryptionKeyIdentifier)); } - }) - .setSigningKeySelector( - new OpenPGPMessageGenerator.SubkeySelector() { - @Override - public List select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { - return Collections.singletonList(certificate.getKey(signingKeyIdentifier)); - } }); return gen; From 6a1ad0d6454f355a3dce82a2483c4e6386e82041 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 14:57:55 +0100 Subject: [PATCH 087/154] OpenPGPDefaultPolicy: Reject ElGamal, DSA keys --- .../org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java index db07b189e6..de580d1c1d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDefaultPolicy.java @@ -72,13 +72,8 @@ public OpenPGPDefaultPolicy() acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.RSA_ENCRYPT, 2000); acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.RSA_SIGN, 2000); - acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, 2000); - acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ELGAMAL_GENERAL, 2000); - - acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.DSA, 2000); acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ECDSA, 250); acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.EDDSA_LEGACY, 250); - acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.DIFFIE_HELLMAN, 2000); acceptPublicKeyAlgorithmWithMinimalStrength(PublicKeyAlgorithmTags.ECDH, 250); acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.X25519); From b5583fde5ca38ba8019596faaf1171dc63698041 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 16 Jan 2025 15:19:42 +0100 Subject: [PATCH 088/154] Sanitize key protection methods on unlock() --- .../openpgp/api/OpenPGPCertificate.java | 5 +++ .../bouncycastle/openpgp/api/OpenPGPKey.java | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 72f74a3f28..8fb1438fd9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1059,6 +1059,11 @@ public KeyIdentifier getKeyIdentifier() return rawPubkey.getKeyIdentifier(); } + public int getVersion() + { + return getPGPPublicKey().getVersion(); + } + /** * Return the creation time of this key. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 7b01bfbf12..bf89d4074b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -4,8 +4,11 @@ import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyValidationException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -259,6 +262,7 @@ public boolean isLocked() public PGPPrivateKey unlock(char[] passphrase) throws PGPException { + sanitizeProtectionMode(); PBESecretKeyDecryptor decryptor = null; try { @@ -274,6 +278,39 @@ public PGPPrivateKey unlock(char[] passphrase) } } + private void sanitizeProtectionMode() + throws PGPException + { + if (!isLocked()) + { + return; + } + + PGPSecretKey secretKey = getPGPSecretKey(); + S2K s2k = secretKey.getS2K(); + if (s2k == null) + { + throw new PGPKeyValidationException("Legacy CFB using MD5 is not allowed."); + } + + if (s2k.getType() == S2K.ARGON_2 && secretKey.getS2KUsage() != SecretKeyPacket.USAGE_AEAD) + { + throw new PGPKeyValidationException("Argon2 without AEAD is not allowed."); + } + + if (getVersion() == PublicKeyPacket.VERSION_6) + { + if (secretKey.getS2KUsage() == SecretKeyPacket.USAGE_CHECKSUM) + { + throw new PGPKeyValidationException("Version 6 keys MUST NOT use malleable CFB."); + } + if (s2k.getType() == S2K.SIMPLE) + { + throw new PGPKeyValidationException("Version 6 keys MUST NOT use SIMPLE S2K."); + } + } + } + public boolean isPassphraseCorrect(char[] passphrase) { try From 79f0a086f7df53ffc6363a2154d5eb2480cd8bf3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 14:34:37 +0100 Subject: [PATCH 089/154] Fix verification of embedded primary-key binding signatures --- .../openpgp/api/OpenPGPCertificate.java | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 8fb1438fd9..62ce201b5d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -28,6 +28,7 @@ import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.api.exception.IncorrectPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; @@ -898,7 +899,7 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi sanitize(issuer, policy); // Direct-Key signature - if (target == issuer) + if (signature.getSignatureType() == PGPSignature.DIRECT_KEY) { verifyKeySignature( issuer, @@ -907,12 +908,15 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi } // Subkey binding signature - else if (target instanceof OpenPGPSubkey) + else if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) { verifyKeySignature( issuer, (OpenPGPSubkey) target, contentVerifierBuilderProvider); + + // For signing-capable subkeys, check the embedded primary key binding signature + verifyEmbeddedPrimaryKeyBinding(contentVerifierBuilderProvider, policy); } // User-ID binding @@ -939,6 +943,45 @@ else if (target instanceof OpenPGPUserAttribute) } } + private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, + OpenPGPPolicy policy) + throws PGPSignatureException + { + int keyFlags = signature.getHashedSubPackets().getKeyFlags(); + if ((keyFlags & KeyFlags.SIGN_DATA) != KeyFlags.SIGN_DATA) + { + // Non-signing key - no embedded primary key binding sig required + return; + } + + OpenPGPComponentKey subkey = getTargetKeyComponent(); + // Signing subkey needs embedded primary key binding signature + PGPSignatureList embeddedSignatures; + try + { + embeddedSignatures = signature.getHashedSubPackets().getEmbeddedSignatures(); + } + catch (PGPException e) + { + throw new PGPSignatureException("Cannot extract embedded signature.", e); + } + + if (embeddedSignatures.isEmpty()) + { + throw new MalformedPGPSignatureException( + "Signing key SubkeyBindingSignature MUST contain embedded PrimaryKeyBindingSignature."); + } + PGPSignature primaryKeyBinding = embeddedSignatures.get(0); + OpenPGPCertificate.OpenPGPComponentSignature backSig = + new OpenPGPCertificate.OpenPGPComponentSignature( + primaryKeyBinding, + subkey, + issuer); + + backSig.sanitize(subkey, policy); + backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); + } + public void verifyKeySignature(OpenPGPComponentKey issuer, OpenPGPComponentKey target, PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) @@ -953,6 +996,10 @@ public void verifyKeySignature(OpenPGPComponentKey issuer, // Direct-Key Signature isCorrect = signature.verifyCertification(target.getPGPPublicKey()); } + else if (signature.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) + { + isCorrect = signature.verifyCertification(target.getPGPPublicKey(), issuer.getPGPPublicKey()); + } else { // Subkey Binding Signature From 38a231a1dc36db4f6e366e3173f43fafd09d8be2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 14:34:53 +0100 Subject: [PATCH 090/154] Remove test using ElGamal key --- .../api/test/OpenPGPMessageProcessorTest.java | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 899e2801c4..770f3d5f78 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -65,7 +65,6 @@ private void performTestsWith(OpenPGPApi api) roundTripV4KeyEncryptedMessageAlice(api); roundTripV4KeyEncryptedMessageBob(api); - roundTripV4KeyEncryptedMessageCarol(api); roundTripV6KeyEncryptedMessage(api); encryptWithV4V6KeyDecryptWithV4(api); @@ -291,32 +290,6 @@ private void roundTripV4KeyEncryptedMessageBob(OpenPGPApi api) isEncodingEqual(bOut.toByteArray(), PLAINTEXT); } - private void roundTripV4KeyEncryptedMessageCarol(OpenPGPApi api) - throws IOException, PGPException - { - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - gen.addEncryptionCertificate(api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.CAROL_CERT)); - - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OutputStream enc = gen.open(bOut); - enc.write(PLAINTEXT); - enc.close(); - - ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage(); - processor.addDecryptionKey(api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.CAROL_KEY)); - - OpenPGPMessageInputStream decIn = processor.process(bIn); - - bOut = new ByteArrayOutputStream(); - Streams.pipeAll(decIn, bOut); - decIn.close(); - OpenPGPMessageInputStream.Result result = decIn.getResult(); - isEquals(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithmTags.AES_256), - result.getEncryptionMethod()); - isEncodingEqual(bOut.toByteArray(), PLAINTEXT); - } - private void roundTripV6KeyEncryptedMessage(OpenPGPApi api) throws IOException, PGPException { From 7c96f6903cc677a89cc84f34f451d612f322f7d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 14:35:12 +0100 Subject: [PATCH 091/154] More fine-grained addSigningKey() methods --- ...ractOpenPGPDocumentSignatureGenerator.java | 19 ++++++++++++++++++- .../OpenPGPDetachedSignatureGenerator.java | 8 ++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java index 69e8c31681..60d0fa49f4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java @@ -91,6 +91,23 @@ public T addSigningKey( OpenPGPKey key, KeyPassphraseProvider passphraseProvider) throws InvalidSigningKeyException + { + return addSigningKey(key, passphraseProvider, null); + } + + public T addSigningKey( + OpenPGPKey key, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException + { + return addSigningKey(key, defaultKeyPassphraseProvider, signatureCallback); + } + + public T addSigningKey( + OpenPGPKey key, + KeyPassphraseProvider passphraseProvider, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException { List signingSubkeys = signingKeySelector.select(key, policy); if (signingSubkeys.isEmpty()) @@ -101,7 +118,7 @@ public T addSigningKey( for (OpenPGPCertificate.OpenPGPComponentKey subkey : signingSubkeys) { OpenPGPKey.OpenPGPSecretKey signingKey = key.getSecretKey(subkey); - addSigningKey(signingKey, passphraseProvider, null); + addSigningKey(signingKey, passphraseProvider, signatureCallback); } return (T) this; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index 37d311c811..759d4babd6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -107,6 +107,14 @@ public OpenPGPDetachedSignatureGenerator addSigningKey( return super.addSigningKey(signingKey, passphraseProvider, signatureCallback); } + public OpenPGPDetachedSignatureGenerator addSigningKey( + OpenPGPKey signingKey, + SignatureParameters.Callback signatureCallback) + throws InvalidSigningKeyException + { + return super.addSigningKey(signingKey, defaultKeyPassphraseProvider, signatureCallback); + } + /** * Pass in an {@link InputStream} containing the data that shall be signed and return a list of detached * signatures. From 536916ce7c7f9c8b93d97c84aa5cd973983e59e6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 14:44:53 +0100 Subject: [PATCH 092/154] When parsing keys and certs: Ignore marker packets --- .../org/bouncycastle/openpgp/api/OpenPGPKeyReader.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java index 0be9039f80..4a731aef4e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.openpgp.PGPMarker; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -90,7 +91,10 @@ public OpenPGPCertificate parseCertificateOrKey(byte[] bytes) // TODO: Is it dangerous, if we don't explicitly fail upon encountering secret key material here? // Could it lead to a situation where we need to be cautious with the certificate API design to // prevent the user from doing dangerous things like accidentally publishing their private key? - + while (object instanceof PGPMarker) + { + object = objectFactory.nextObject(); + } if (object instanceof PGPSecretKeyRing) { return new OpenPGPKey((PGPSecretKeyRing) object, implementation, policy); @@ -126,6 +130,10 @@ public OpenPGPKey parseKey(byte[] bytes) PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); Object object = objectFactory.nextObject(); + while (object instanceof PGPMarker) + { + object = objectFactory.nextObject(); + } if (!(object instanceof PGPSecretKeyRing)) { throw new IOException("Not a secret key."); From 9f94b235fd1554fe580b99d3d6463dc9021892fe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 15:41:13 +0100 Subject: [PATCH 093/154] OpenPGPCertificate: Add javadoc --- .../openpgp/api/OpenPGPCertificate.java | 180 ++++++++++++++++-- 1 file changed, 168 insertions(+), 12 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 62ce201b5d..5908f282e0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -7,6 +7,7 @@ import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; @@ -232,6 +233,11 @@ public PGPKeyRing getPGPKeyRing() return keyRing; } + /** + * Return the underlying {@link PGPPublicKeyRing}. + * + * @return public keys + */ public PGPPublicKeyRing getPGPPublicKeyRing() { if (keyRing instanceof PGPPublicKeyRing) @@ -247,6 +253,11 @@ public PGPPublicKeyRing getPGPPublicKeyRing() return new PGPPublicKeyRing(list); } + /** + * Return the {@link KeyIdentifier} of the certificates primary key. + * + * @return primary key identifier + */ public KeyIdentifier getKeyIdentifier() { return primaryKey.getKeyIdentifier(); @@ -294,7 +305,7 @@ public static OpenPGPCertificate join(OpenPGPCertificate certificate, String arm else if (next instanceof PGPSecretKeyRing) { - + throw new IllegalArgumentException("Joining with a secret key is not supported."); } else if (next instanceof PGPSignatureList) @@ -324,16 +335,32 @@ public static OpenPGPCertificate join(OpenPGPCertificate certificate, OpenPGPCer return new OpenPGPCertificate(joined, certificate.implementation); } + /** + * Return the primary keys fingerprint in binary format. + * + * @return primary key fingerprint + */ public byte[] getFingerprint() { return primaryKey.getPGPPublicKey().getFingerprint(); } + /** + * Return the primary keys fingerprint as a pretty-printed {@link String}. + * + * @return pretty-printed primary key fingerprint + */ public String getPrettyFingerprint() { return FingerprintUtil.prettifyFingerprint(getFingerprint()); } + /** + * Return an ASCII armored {@link String} containing the certificate. + * + * @return armored certificate + * @throws IOException if the cert cannot be encoded + */ public String toAsciiArmoredString() throws IOException { @@ -637,21 +664,43 @@ private OpenPGPSignatureChain getPreferenceSignature(Date evaluationTime) return uidBindings.isEmpty() ? null : uidBindings.get(0); } + /** + * Return all identities ({@link OpenPGPUserId User IDs}, {@link OpenPGPUserAttribute User Attributes} + * of the certificate. + * + * @return identities + */ public List getIdentities() { return new ArrayList<>(primaryKey.identityComponents); } + /** + * Return the current primary {@link OpenPGPUserId} of the certificate. + * + * @return primary user id + */ public OpenPGPUserId getPrimaryUserId() { return getPrimaryUserId(new Date()); } + /** + * Return the {@link OpenPGPUserId} that is considered primary at the given evaluation time. + * + * @param evaluationTime evaluation time + * @return primary user-id at evaluation time + */ public OpenPGPUserId getPrimaryUserId(Date evaluationTime) { return primaryKey.getExplicitOrImplicitPrimaryUserId(evaluationTime); } + /** + * Return the {@link OpenPGPUserId} object matching the given user-id {@link String}. + * @param userId user-id + * @return user-id + */ public OpenPGPUserId getUserId(String userId) { for (OpenPGPUserId uid : primaryKey.getUserIDs()) @@ -694,6 +743,11 @@ public OpenPGPCertificate getCertificate() */ public abstract String toDetailString(); + /** + * Return true, if the component is currently validly bound to the certificate. + * + * @return true if bound + */ public boolean isBound() { return isBoundAt(new Date()); @@ -982,7 +1036,7 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); } - public void verifyKeySignature(OpenPGPComponentKey issuer, + protected void verifyKeySignature(OpenPGPComponentKey issuer, OpenPGPComponentKey target, PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) throws PGPSignatureException @@ -1018,7 +1072,7 @@ else if (signature.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) } } - public void verifyUserIdSignature(OpenPGPComponentKey issuer, + protected void verifyUserIdSignature(OpenPGPComponentKey issuer, OpenPGPUserId target, PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) throws PGPSignatureException @@ -1040,7 +1094,7 @@ public void verifyUserIdSignature(OpenPGPComponentKey issuer, } } - public void verifyUserAttributeSignature(OpenPGPComponentKey issuer, + protected void verifyUserAttributeSignature(OpenPGPComponentKey issuer, OpenPGPUserAttribute target, PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) throws PGPSignatureException @@ -1091,6 +1145,11 @@ public OpenPGPComponentKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificat this.rawPubkey = rawPubkey; } + /** + * Return the underlying {@link PGPPublicKey} of this {@link OpenPGPComponentKey}. + * + * @return public key + */ public PGPPublicKey getPGPPublicKey() { return rawPubkey; @@ -1106,6 +1165,11 @@ public KeyIdentifier getKeyIdentifier() return rawPubkey.getKeyIdentifier(); } + /** + * Return the public key version. + * + * @return key version + */ public int getVersion() { return getPGPPublicKey().getVersion(); @@ -1175,14 +1239,7 @@ public boolean isSigningKey() public boolean isSigningKey(Date evaluationTime) { // TODO: Replace with https://github.com/bcgit/bc-java/pull/1857/files#diff-36f593d586240aec2546daad96d16b5debd3463202a3d5d82c0b2694572c8426R14-R30 - int alg = rawPubkey.getAlgorithm(); - if (alg != PublicKeyAlgorithmTags.RSA_GENERAL && - alg != PublicKeyAlgorithmTags.RSA_SIGN && - alg != PublicKeyAlgorithmTags.DSA && - alg != PublicKeyAlgorithmTags.ECDSA && - alg != PublicKeyAlgorithmTags.EDDSA_LEGACY && - alg != PublicKeyAlgorithmTags.Ed25519 && - alg != PublicKeyAlgorithmTags.Ed448) + if (!PublicKeyUtils.isSigningAlgorithm(rawPubkey.getAlgorithm())) { // Key is not signing-capable by algorithm return false; @@ -1191,9 +1248,11 @@ public boolean isSigningKey(Date evaluationTime) KeyFlags keyFlags = getKeyFlags(evaluationTime); if (keyFlags == null) { + // Key has no applicable key-flags return false; } + // Check if key is marked as signing-capable by key-flags int flags = keyFlags.getFlags(); return (flags & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA; } @@ -1290,11 +1349,24 @@ public Features getFeatures(Date evaluationTime) return null; } + /** + * Return the {@link PreferredAEADCiphersuites} that apply to this (sub-)key. + * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. + * + * @return AEAD algorithm preferences + */ public PreferredAEADCiphersuites getAEADCipherSuitePreferences() { return getAEADCipherSuitePreferences(new Date()); } + /** + * Return the {@link PreferredAEADCiphersuites} that - at evaluation time - apply to this (sub-)key. + * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. + * + * @param evaluationTime evaluation time + * @return AEAD algorithm preferences at evaluation time + */ public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTime) { OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, @@ -1306,11 +1378,22 @@ public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTi return null; } + /** + * Return the current symmetric encryption algorithm preferences of this (sub-)key. + * + * @return current preferred symmetric-key algorithm preferences + */ public PreferredAlgorithms getSymmetricCipherPreferences() { return getSymmetricCipherPreferences(new Date()); } + /** + * Return the symmetric encryption algorithm preferences of this (sub-)key at evaluation time. + * + * @param evaluationTime evaluation time + * @return current preferred symmetric-key algorithm preferences + */ public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) { OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); @@ -1321,11 +1404,22 @@ public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) return null; } + /** + * Return the current signature hash algorithm preferences of this (sub-)key. + * + * @return hash algorithm preferences + */ public PreferredAlgorithms getHashAlgorithmPreferences() { return getHashAlgorithmPreferences(new Date()); } + /** + * Return the signature hash algorithm preferences of this (sub-)key at evaluation time. + * + * @param evaluationTime evaluation time + * @return hash algorithm preferences + */ public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) { OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); @@ -1336,11 +1430,22 @@ public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) return null; } + /** + * Return the {@link Date}, at which the key expires. + * + * @return key expiration time + */ public Date getKeyExpirationDate() { return getKeyExpirationDateAt(new Date()); } + /** + * Return the {@link Date}, at which the key - at evaluation time - expires. + * + * @param evaluationTime evaluation time + * @return key expiration time + */ public Date getKeyExpirationDateAt(Date evaluationTime) { OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = @@ -1421,6 +1526,12 @@ public List getUserIDs() return userIds; } + /** + * Return the {@link OpenPGPUserId}, which is - at evaluation time - explicitly marked as primary. + * + * @param evaluationTime evaluation time + * @return explicit primary userid + */ public OpenPGPUserId getExplicitPrimaryUserId(Date evaluationTime) { // Return the latest, valid, explicitly marked as primary UserID @@ -1454,6 +1565,14 @@ public OpenPGPUserId getExplicitPrimaryUserId(Date evaluationTime) return latestUid; } + /** + * Return the {@link OpenPGPUserId}, which is - at evaluation time - considered primary, + * either because it is explicitly marked as primary userid, or because it is implicitly primary + * (e.g. because it is the sole user-id on the key). + * + * @param evaluationTime evaluation time + * @return primary user-id + */ public OpenPGPUserId getExplicitOrImplicitPrimaryUserId(Date evaluationTime) { OpenPGPUserId explicitPrimaryUserId = getExplicitPrimaryUserId(evaluationTime); @@ -1505,6 +1624,11 @@ public List getUserAttributes() return userAttributes; } + /** + * Return all direct-key and key-revocation signatures on the primary key. + * + * @return key signatures + */ protected List getKeySignatures() { Iterator iterator = rawPubkey.getSignatures(); @@ -1526,6 +1650,12 @@ protected List getKeySignatures() return list; } + /** + * Return all signatures on the given {@link OpenPGPUserId}. + * + * @param identity user-id + * @return list of user-id signatures + */ protected List getUserIdSignatures(OpenPGPUserId identity) { Iterator iterator = rawPubkey.getSignaturesForID(identity.getUserId()); @@ -1542,6 +1672,12 @@ protected List getUserIdSignatures(OpenPGPUserId iden return list; } + /** + * Return all signatures on the given {@link OpenPGPUserAttribute}. + * + * @param identity user-attribute + * @return list of user-attribute signatures + */ protected List getUserAttributeSignatures(OpenPGPUserAttribute identity) { Iterator iterator = rawPubkey.getSignaturesForUserAttribute(identity.getUserAttribute()); @@ -1582,6 +1718,11 @@ public String toDetailString() return "Subkey[" + getKeyIdentifier() + "] (" + UTCUtil.format(getCreationTime()) + ")"; } + /** + * Return all subkey-binding and -revocation signatures on the subkey. + * + * @return subkey signatures + */ protected List getKeySignatures() { Iterator iterator = rawPubkey.getSignatures(); @@ -1619,6 +1760,11 @@ public OpenPGPIdentityComponent(OpenPGPPrimaryKey primaryKey) this.primaryKey = primaryKey; } + /** + * Return the primary key, which this identity belongs to. + * + * @return primary key + */ public OpenPGPPrimaryKey getPrimaryKey() { return primaryKey; @@ -1645,6 +1791,11 @@ public OpenPGPUserId(String userId, OpenPGPPrimaryKey primaryKey) this.userId = userId; } + /** + * Return the {@link String} representation of the {@link OpenPGPUserId}. + * + * @return user-id + */ public String getUserId() { return userId; @@ -1697,6 +1848,11 @@ public OpenPGPUserAttribute(PGPUserAttributeSubpacketVector userAttribute, OpenP this.userAttribute = userAttribute; } + /** + * Return the underlying {@link PGPUserAttributeSubpacketVector} representing this {@link OpenPGPUserAttribute}. + * + * @return user attribute subpacket vector + */ public PGPUserAttributeSubpacketVector getUserAttribute() { return userAttribute; From 1b4fc5ad80e0cd2bd63a1639fb4047a0269f40d7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 15:49:11 +0100 Subject: [PATCH 094/154] Remove another test case using unusable ElGamal key --- .../api/test/OpenPGPMessageProcessorTest.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 770f3d5f78..03b3027904 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -75,7 +75,6 @@ private void performTestsWith(OpenPGPApi api) inlineSignWithV4KeyAlice(api); inlineSignWithV4KeyBob(api); - inlineSignWithV4KeyCarol(api); inlineSignWithV6Key(api); verifyMessageByRevokedKey(api); @@ -522,37 +521,6 @@ private void inlineSignWithV4KeyBob(OpenPGPApi api) isEncodingEqual(PLAINTEXT, bOut.toByteArray()); } - private void inlineSignWithV4KeyCarol(OpenPGPApi api) - throws PGPException, IOException - { - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage(); - OpenPGPKey carolKey = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.CAROL_KEY); - gen.addSigningKey(carolKey); - - OutputStream signOut = gen.open(bOut); - signOut.write(PLAINTEXT); - signOut.close(); - - ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); - bOut = new ByteArrayOutputStream(); - - OpenPGPCertificate carolCert = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.CAROL_CERT); - OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage() - .addVerificationCertificate(carolCert); - - OpenPGPMessageInputStream verifIn = processor.process(bIn); - Streams.pipeAll(verifIn, bOut); - verifIn.close(); - OpenPGPMessageInputStream.Result result = verifIn.getResult(); - List signatures = result.getSignatures(); - isEquals(1, signatures.size()); - OpenPGPSignature.OpenPGPDocumentSignature sig = signatures.get(0); - isEquals(carolCert, sig.getIssuerCertificate()); - - isEncodingEqual(PLAINTEXT, bOut.toByteArray()); - } - private void inlineSignWithV6Key(OpenPGPApi api) throws PGPException, IOException { From 2daf5dff5cd92edb03a79c1c662b4932a50007ac Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jan 2025 17:55:10 +0100 Subject: [PATCH 095/154] WIP: Open up OpenPGPKeyGenerator for non-v6 keys --- .../bouncycastle/openpgp/api/OpenPGPApi.java | 29 ++++++- ...enerator.java => OpenPGPKeyGenerator.java} | 41 +++++++--- .../api/SignatureSubpacketsFunction.java | 2 +- .../openpgp/api/bc/BcOpenPGPApi.java | 18 +++-- ...erator.java => BcOpenPGPKeyGenerator.java} | 24 +++--- .../openpgp/api/jcajce/JcaOpenPGPApi.java | 14 ++-- ...rator.java => JcaOpenPGPKeyGenerator.java} | 17 +++-- .../api/test/OpenPGPCertificateTest.java | 4 +- .../api/test/OpenPGPV4KeyGenerationTest.java | 75 +++++++++++++++++++ .../api/test/OpenPGPV6KeyGeneratorTest.java | 20 ++--- 10 files changed, 183 insertions(+), 61 deletions(-) rename pg/src/main/java/org/bouncycastle/openpgp/api/{OpenPGPV6KeyGenerator.java => OpenPGPKeyGenerator.java} (96%) rename pg/src/main/java/org/bouncycastle/openpgp/api/bc/{BcOpenPGPV6KeyGenerator.java => BcOpenPGPKeyGenerator.java} (55%) rename pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/{JcaOpenPGPV6KeyGenerator.java => JcaOpenPGPKeyGenerator.java} (54%) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java index bf4c54e24f..a63048c2ca 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.api; +import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.openpgp.PGPException; import java.util.Date; @@ -25,14 +26,34 @@ public OpenPGPKeyReader readKeyOrCertificate() return new OpenPGPKeyReader(implementation, policy); } - public abstract OpenPGPV6KeyGenerator generateKey() + public OpenPGPKeyGenerator generateKey() + throws PGPException + { + return generateKey(PublicKeyPacket.VERSION_6); + } + + public abstract OpenPGPKeyGenerator generateKey(int version) throws PGPException; - public abstract OpenPGPV6KeyGenerator generateKey(Date creationTime) + public OpenPGPKeyGenerator generateKey(Date creationTime) + throws PGPException + { + return generateKey(PublicKeyPacket.VERSION_6, creationTime); + } + + public abstract OpenPGPKeyGenerator generateKey(int version, + Date creationTime) throws PGPException; - public abstract OpenPGPV6KeyGenerator generateKey(Date creationTime, - boolean aeadProtection) + public OpenPGPKeyGenerator generateKey(Date creationTime, boolean aeadProtection) + throws PGPException + { + return generateKey(PublicKeyPacket.VERSION_6, creationTime, aeadProtection); + } + + public abstract OpenPGPKeyGenerator generateKey(int version, + Date creationTime, + boolean aeadProtection) throws PGPException; public OpenPGPMessageGenerator signAndOrEncryptMessage() diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java similarity index 96% rename from pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java index 495179b7a6..9e58de93bd 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java @@ -32,7 +32,7 @@ /** * High-level generator class for OpenPGP v6 keys. */ -public class OpenPGPV6KeyGenerator +public class OpenPGPKeyGenerator extends AbstractOpenPGPKeySignatureGenerator { // SECONDS @@ -41,17 +41,27 @@ public class OpenPGPV6KeyGenerator private static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; private static final long SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY; - + private final int keyVersion; private final OpenPGPImplementation implementationProvider; private final Configuration configuration; // contains BC or JCA/JCE implementations - public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, - boolean aead, - Date creationTime) + public OpenPGPKeyGenerator(OpenPGPImplementation implementation, + boolean aead, + Date creationTime) + throws PGPException + { + this(implementation, PublicKeyPacket.VERSION_6, aead, creationTime); + } + + public OpenPGPKeyGenerator(OpenPGPImplementation implementationProvider, + int version, + boolean aead, + Date creationTime) throws PGPException { this( implementationProvider, + version, implementationProvider.pgpKeyPairGeneratorProvider(), implementationProvider.pgpDigestCalculatorProvider(), implementationProvider.pbeSecretKeyEncryptorFactory(aead), @@ -69,15 +79,24 @@ public OpenPGPV6KeyGenerator(OpenPGPImplementation implementationProvider, * @param keyFingerPrintCalculator calculator for key fingerprints * @param creationTime key creation time */ - public OpenPGPV6KeyGenerator( + public OpenPGPKeyGenerator( OpenPGPImplementation implementationProvider, + int keyVersion, PGPKeyPairGeneratorProvider kpGenProvider, PGPDigestCalculatorProvider digestCalculatorProvider, PBESecretKeyEncryptorFactory keyEncryptionBuilderProvider, KeyFingerPrintCalculator keyFingerPrintCalculator, Date creationTime) { + if (keyVersion != PublicKeyPacket.VERSION_4 && + keyVersion != PublicKeyPacket.LIBREPGP_5 && + keyVersion != PublicKeyPacket.VERSION_6) + { + throw new IllegalArgumentException("Generating keys of version " + keyVersion + " is not supported."); + } + this.implementationProvider = implementationProvider; + this.keyVersion = keyVersion; this.configuration = new Configuration(creationTime, kpGenProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); } @@ -270,8 +289,8 @@ public WithPrimaryKey withPrimaryKey( SignatureParameters.Callback preferenceSignatureCallback) throws PGPException { - PGPKeyPair primaryKeyPair = keyGenCallback.generateFrom( - configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); + PGPKeyPair primaryKeyPair = keyGenCallback.generateFrom(configuration.kpGenProvider.get( + keyVersion, configuration.keyCreationTime)); if (primaryKeyPair.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket) { @@ -455,8 +474,7 @@ public WithPrimaryKey addEncryptionSubkey( throws PGPException { PGPKeyPairGenerator generator = configuration.kpGenProvider.get( - primaryKey.getPublicKey().getVersion(), - configuration.keyCreationTime + keyVersion, configuration.keyCreationTime ); PGPKeyPair subkey = generatorCallback.generateFrom(generator); subkey = subkey.asSubkey(implementation.keyFingerPrintCalculator()); @@ -586,7 +604,8 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, SignatureParameters.Callback backSignatureCallback) throws PGPException { - PGPKeyPair subkey = keyGenCallback.generateFrom(configuration.kpGenProvider.get(PublicKeyPacket.VERSION_6, configuration.keyCreationTime)); + PGPKeyPair subkey = keyGenCallback.generateFrom(configuration.kpGenProvider.get( + keyVersion, configuration.keyCreationTime)); subkey = subkey.asSubkey(configuration.keyFingerprintCalculator); return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java index 177954b692..9d7637e89a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java @@ -5,7 +5,7 @@ /** * Callback to modify the contents of a {@link PGPSignatureSubpacketGenerator}. - * The {@link OpenPGPV6KeyGenerator} already prepopulates the hashed subpacket areas of signatures during + * The {@link OpenPGPKeyGenerator} already prepopulates the hashed subpacket areas of signatures during * key generation. This callback is useful to apply custom changes to the hashed subpacket area during the * generation process. */ diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java index aab2565e2c..6fb16e298c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java @@ -3,7 +3,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPPolicy; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import java.util.Date; @@ -21,24 +21,26 @@ public BcOpenPGPApi(OpenPGPPolicy policy) } @Override - public OpenPGPV6KeyGenerator generateKey() + public OpenPGPKeyGenerator generateKey(int version) throws PGPException { - return new BcOpenPGPV6KeyGenerator(); + return new BcOpenPGPKeyGenerator(version); } @Override - public OpenPGPV6KeyGenerator generateKey(Date creationTime) + public OpenPGPKeyGenerator generateKey(int version, + Date creationTime) throws PGPException { - return new BcOpenPGPV6KeyGenerator(creationTime); + return new BcOpenPGPKeyGenerator(version, creationTime); } @Override - public OpenPGPV6KeyGenerator generateKey(Date creationTime, - boolean aeadProtection) + public OpenPGPKeyGenerator generateKey(int version, + Date creationTime, + boolean aeadProtection) throws PGPException { - return new BcOpenPGPV6KeyGenerator(creationTime, aeadProtection); + return new BcOpenPGPKeyGenerator(version, creationTime, aeadProtection); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPKeyGenerator.java similarity index 55% rename from pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPKeyGenerator.java index ea70edef41..218fb6c73d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPKeyGenerator.java @@ -1,47 +1,51 @@ package org.bouncycastle.openpgp.api.bc; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import java.util.Date; /** - * Bouncy Castle implementation of {@link OpenPGPV6KeyGenerator}. + * Bouncy Castle implementation of {@link OpenPGPKeyGenerator}. */ -public class BcOpenPGPV6KeyGenerator - extends OpenPGPV6KeyGenerator +public class BcOpenPGPKeyGenerator + extends OpenPGPKeyGenerator { /** * Create a new key generator for OpenPGP v6 keys. + * + * @param version key version */ - public BcOpenPGPV6KeyGenerator() + public BcOpenPGPKeyGenerator(int version) throws PGPException { - this(new Date()); + this(version, new Date()); } /** * Create a new key generator for OpenPGP v6 keys. * The key creation time will be set to {@code creationTime} * + * @param version key version * @param creationTime creation time of the generated OpenPGP key */ - public BcOpenPGPV6KeyGenerator(Date creationTime) + public BcOpenPGPKeyGenerator(int version, Date creationTime) throws PGPException { - this(creationTime, true); + this(version, creationTime, true); } /** * Create a new OpenPGP key generator for v6 keys. * + * @param version key version * @param creationTime creation time of the key and signatures * @param aeadProtection whether the key shall be protected using AEAD. If false, the key is protected using CFB. */ - public BcOpenPGPV6KeyGenerator(Date creationTime, boolean aeadProtection) + public BcOpenPGPKeyGenerator(int version, Date creationTime, boolean aeadProtection) throws PGPException { - super(new BcOpenPGPImplementation(), aeadProtection, creationTime); + super(new BcOpenPGPImplementation(), version, aeadProtection, creationTime); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java index 9cf43f0413..ba09751937 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java @@ -4,7 +4,7 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPPolicy; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import java.security.Provider; import java.security.SecureRandom; @@ -38,23 +38,23 @@ public JcaOpenPGPApi(Provider provider, SecureRandom random, OpenPGPPolicy polic } @Override - public OpenPGPV6KeyGenerator generateKey() + public OpenPGPKeyGenerator generateKey(int version) throws PGPException { - return new JcaOpenPGPV6KeyGenerator(provider); + return new JcaOpenPGPKeyGenerator(version, provider); } @Override - public OpenPGPV6KeyGenerator generateKey(Date creationTime) + public OpenPGPKeyGenerator generateKey(int version, Date creationTime) throws PGPException { - return new JcaOpenPGPV6KeyGenerator(creationTime, provider); + return new JcaOpenPGPKeyGenerator(version, creationTime, provider); } @Override - public OpenPGPV6KeyGenerator generateKey(Date creationTime, boolean aeadProtection) + public OpenPGPKeyGenerator generateKey(int version, Date creationTime, boolean aeadProtection) throws PGPException { - return new JcaOpenPGPV6KeyGenerator(creationTime, aeadProtection, provider); + return new JcaOpenPGPKeyGenerator(version, creationTime, aeadProtection, provider); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java similarity index 54% rename from pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java index 34eddc683e..f66facfe49 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java @@ -1,26 +1,26 @@ package org.bouncycastle.openpgp.api.jcajce; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import java.security.Provider; import java.security.SecureRandom; import java.util.Date; -public class JcaOpenPGPV6KeyGenerator - extends OpenPGPV6KeyGenerator +public class JcaOpenPGPKeyGenerator + extends OpenPGPKeyGenerator { - public JcaOpenPGPV6KeyGenerator(Provider provider) + public JcaOpenPGPKeyGenerator(int version, Provider provider) throws PGPException { - this(new Date(), provider); + this(version, new Date(), provider); } - public JcaOpenPGPV6KeyGenerator(Date creationTime, Provider provider) + public JcaOpenPGPKeyGenerator(int version, Date creationTime, Provider provider) throws PGPException { - this(creationTime, true, provider); + this(version, creationTime, true, provider); } /** @@ -28,11 +28,12 @@ public JcaOpenPGPV6KeyGenerator(Date creationTime, Provider provider) * * @param creationTime creation time of the key and signatures */ - public JcaOpenPGPV6KeyGenerator(Date creationTime, boolean aeadProtection, Provider provider) + public JcaOpenPGPKeyGenerator(int version, Date creationTime, boolean aeadProtection, Provider provider) throws PGPException { super( new JcaOpenPGPImplementation(provider, new SecureRandom()), + version, aeadProtection, creationTime); } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index 05be82f0f1..41838fdd53 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -17,7 +17,7 @@ import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; @@ -812,7 +812,7 @@ private void testGetPrimaryUserId(OpenPGPApi api) Date now = new Date((new Date().getTime() / 1000) * 1000); Date oneHourAgo = new Date(now.getTime() - 1000 * 60 * 60); - OpenPGPV6KeyGenerator gen = api.generateKey(oneHourAgo); + OpenPGPKeyGenerator gen = api.generateKey(oneHourAgo); OpenPGPKey key = gen.withPrimaryKey() .addUserId("Old non-primary ") .addUserId("New primary ", diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java new file mode 100644 index 0000000000..1f9bf07e40 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java @@ -0,0 +1,75 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.SignatureParameters; +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; + +public class OpenPGPV4KeyGenerationTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "OpenPGPV4KeyGenerationTest"; + } + + @Override + public void performTest() + throws Exception + { + performWith(new BcOpenPGPApi()); + performWith(new JcaOpenPGPApi(new BouncyCastleProvider())); + } + + private void performWith(OpenPGPApi api) + throws PGPException + { + generateRSAKey(api); + } + + private void generateRSAKey(OpenPGPApi api) + throws PGPException + { + OpenPGPKey key = api.generateKey(PublicKeyPacket.VERSION_4) + .withPrimaryKey(new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateRsaKeyPair(3072); + } + }, SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); + return subpackets; + } + })) + .addUserId("Alice ") + .build(); + + isEquals(PublicKeyPacket.VERSION_4, key.getPrimaryKey().getVersion()); + } + + public static void main(String[] args) + { + runTest(new OpenPGPV4KeyGenerationTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index e980da3293..fd2ccfc585 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -26,7 +26,7 @@ import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; @@ -81,7 +81,7 @@ private void performTestsWith(OpenPGPApi api) private void testGenerateSignOnlyKeyBaseCase(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = api.generateKey(); + OpenPGPKeyGenerator generator = api.generateKey(); OpenPGPKey key = generator.signOnlyKey().build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); @@ -109,7 +109,7 @@ private void testGenerateSignOnlyKeyBaseCase(OpenPGPApi api) private void testGenerateAEADProtectedSignOnlyKey(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = api.generateKey(new Date(), true); + OpenPGPKeyGenerator generator = api.generateKey(new Date(), true); OpenPGPKey key = generator.signOnlyKey().build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); @@ -127,7 +127,7 @@ private void testGenerateAEADProtectedSignOnlyKey(OpenPGPApi api) private void testGenerateCFBProtectedSignOnlyKey(OpenPGPApi api) throws PGPException { - OpenPGPV6KeyGenerator generator = api.generateKey(new Date(), false); + OpenPGPKeyGenerator generator = api.generateKey(new Date(), false); OpenPGPKey key = generator.signOnlyKey().build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); @@ -146,7 +146,7 @@ private void testGenerateClassicKeyBaseCase(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = api.generateKey(creationTime); + OpenPGPKeyGenerator generator = api.generateKey(creationTime); OpenPGPKey key = generator .classicKey("Alice ").build(); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); @@ -207,7 +207,7 @@ private void testGenerateProtectedTypicalKey(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = api.generateKey(creationTime); + OpenPGPKeyGenerator generator = api.generateKey(creationTime); OpenPGPKey key = generator .classicKey("Alice ").build("passphrase".toCharArray()); PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); @@ -243,7 +243,7 @@ private void testGenerateEd25519x25519Key(OpenPGPApi api) { Date currentTime = currentTimeRounded(); String userId = "Foo "; - OpenPGPV6KeyGenerator generator = api.generateKey(currentTime); + OpenPGPKeyGenerator generator = api.generateKey(currentTime); OpenPGPKey key = generator.ed25519x25519Key(userId).build(); PGPSecretKeyRing secretKey = key.getPGPKeyRing(); @@ -291,7 +291,7 @@ private void testGenerateEd448x448Key(OpenPGPApi api) { Date currentTime = currentTimeRounded(); String userId = "Foo "; - OpenPGPV6KeyGenerator generator = api.generateKey(currentTime); + OpenPGPKeyGenerator generator = api.generateKey(currentTime); OpenPGPKey key = generator.ed448x448Key(userId).build(); PGPSecretKeyRing secretKey = key.getPGPKeyRing(); @@ -338,7 +338,7 @@ private void testGenerateCustomKey(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = api.generateKey(creationTime, false); + OpenPGPKeyGenerator generator = api.generateKey(creationTime, false); OpenPGPKey key = generator .withPrimaryKey( @@ -480,7 +480,7 @@ private void testGenerateMinimalKey(OpenPGPApi api) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator gen = api.generateKey(creationTime, false); + OpenPGPKeyGenerator gen = api.generateKey(creationTime, false); OpenPGPKey key = gen.withPrimaryKey( PGPKeyPairGenerator::generateEd25519KeyPair, SignatureParameters.Callback.modifyHashedSubpackets(new SignatureSubpacketsFunction() From 1caffd2e8d0854ca635e8e9b109be7af1e55d1d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 22 Jan 2025 15:31:08 +0100 Subject: [PATCH 096/154] Test DoubleBufferedOutputStream --- .../api/DoubleBufferedInputStream.java | 175 ++++++++++++++++++ .../openpgp/api/RetainingInputStream.java | 107 ----------- .../test/DoubleBufferedInputStreamTest.java | 160 ++++++++++++++++ 3 files changed, 335 insertions(+), 107 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java new file mode 100644 index 0000000000..046e7f55bf --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java @@ -0,0 +1,175 @@ +package org.bouncycastle.openpgp.api; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Implementation of {@link InputStream} double-buffers data from an underlying input stream. + * Upon reaching the end of the underlying data stream, the underlying data stream is + * automatically closed. + * Any exceptions while reading from the underlying input stream cause the {@link DoubleBufferedInputStream} + * to withhold pending data. + * This is done in order to minimize the risk of emitting unauthenticated plaintext, while at the same + * time being somewhat resource-efficient. + * The minimum number of bytes to withhold can be configured ({@link #BUFFER_SIZE} by default). + */ +public class DoubleBufferedInputStream + extends InputStream +{ + private static final int BUFFER_SIZE = 1024 * 1024 * 32; // 32 MiB + + private byte[] buf1; + private byte[] buf2; + + private int b1Pos; + private int b1Max; + private int b2Max; + private final I in; + private boolean closed = false; + + public DoubleBufferedInputStream(I in) + { + this(in, BUFFER_SIZE); + } + + public DoubleBufferedInputStream(I in, int bufferSize) + { + if (bufferSize <= 0) + { + throw new IllegalArgumentException("Buffer size cannot be null nor negative."); + } + this.buf1 = new byte[bufferSize]; + this.buf2 = new byte[bufferSize]; + this.in = in; + b1Pos = -1; + } + + public I getInputStream() + { + return in; + } + + private void fill() + throws IOException + { + // init + if (b1Pos == -1) + { + // fill both buffers with data + b1Max = in.read(buf1); + b2Max = in.read(buf2); + + if (b2Max == -1) + { + // data fits into b1 -> close underlying stream + close(); + } + + b1Pos = 0; + return; + } + + // no data + if (b1Max <= 0) + { + return; + } + + // Reached end of buf1 + if (b1Pos == b1Max) + { + // swap buffers + byte[] t = buf1; + buf1 = buf2; + buf2 = t; + b1Max = b2Max; + + // reset reader pos + b1Pos = 0; + + // fill buf2 + try + { + b2Max = in.read(buf2); + // could not fill the buffer, or swallowed an IOException + if (b2Max != buf2.length) + { + // provoke the IOException otherwise swallowed by read(buf) + int i = in.read(); + // no exception was thrown, so either data became available, or EOF + if (i != -1) + { + // data became available, push to buf2 + buf2[b2Max++] = (byte) i; + } + } + } + catch (IOException e) + { + b1Max = -1; + b2Max = -1; + } + + // EOF + if (b2Max == -1) + { + close(); + } + } + } + + @Override + public void close() + throws IOException + { + if (!closed) + { + closed = true; + in.close(); + } + } + + @Override + public int read() + throws IOException + { + // fill the buffer(s) + fill(); + + // EOF + if (b1Max == -1) + { + close(); + return -1; + } + int i = buf1[b1Pos]; + b1Pos++; + return i; + } + + @Override + public int read(byte[] b, int off, int len) + throws IOException + { + // Fill the buffer(s) + fill(); + + // EOF + if (b1Max == -1) + { + close(); + return -1; + } + // available bytes in b1 + int avail = b1Max - b1Pos; + + // math.min(avail, len) + int ret = avail < len ? avail : len; + + // emit data + System.arraycopy(buf1, b1Pos, b, off, ret); + + b1Pos += ret; + return ret; + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java deleted file mode 100644 index 675b6932f4..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/RetainingInputStream.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.bouncycastle.openpgp.api; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Implementation of {@link InputStream} that withholds a number of bytes from the end of the original - * message until the message has been processed entirely. - * Furthermore, upon reaching the end of the underlying data stream, the underlying data stream is - * automatically closed. - * This is done in order to minimize the risk of emitting unauthenticated plaintext, while at the same - * time being somewhat resource-efficient. - * The number of bytes to withhold can be configured ({@link #CIRCULAR_BUFFER_SIZE} by default). - */ -public class RetainingInputStream - extends InputStream -{ - private static final int CIRCULAR_BUFFER_SIZE = 1024 * 1024 * 32; // 32 MiB - - private final byte[] circularBuffer; - private int lastWrittenPos = 0; - private int bufReadPos = 0; - private final I in; - private boolean closed = false; - - public RetainingInputStream(I in) - { - this(in, CIRCULAR_BUFFER_SIZE); - } - - public RetainingInputStream(I in, int bufferSize) - { - if (bufferSize <= 0) - { - throw new IllegalArgumentException("Buffer size cannot be null nor negative."); - } - this.circularBuffer = new byte[bufferSize]; - this.in = in; - } - - public I getInputStream() - { - return in; - } - - private void fill() - throws IOException - { - if (closed) - { - return; - } - - // readerPos - 1 % buf.len - int lastAvailPos = (circularBuffer.length + bufReadPos - 1) % circularBuffer.length; - int read; - if (lastWrittenPos < lastAvailPos) - { - read = in.read(circularBuffer, lastWrittenPos, lastAvailPos - lastWrittenPos); - } - else - { - read = in.read(circularBuffer, lastWrittenPos, circularBuffer.length - lastWrittenPos); - if (read >= 0) - { - lastWrittenPos += read; - } - read = in.read(circularBuffer, 0, lastAvailPos); - } - - if (read >= 0) - { - lastWrittenPos += read; - } - else - { - close(); - } - - lastWrittenPos %= circularBuffer.length; - } - - @Override - public void close() - throws IOException - { - if (!closed) - { - closed = true; - in.close(); - } - } - - @Override - public int read() - throws IOException - { - fill(); - if (bufReadPos == lastWrittenPos) - { - return -1; - } - int i = circularBuffer[bufReadPos++]; - bufReadPos %= circularBuffer.length; - return i; - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java new file mode 100644 index 0000000000..52f3f21512 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java @@ -0,0 +1,160 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.openpgp.api.DoubleBufferedInputStream; +import org.bouncycastle.util.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class DoubleBufferedInputStreamTest + extends AbstractPacketTest +{ + + @Override + public String getName() + { + return "RetainingInputStreamTest"; + } + + @Override + public void performTest() + throws Exception + { + throwWhileReadingNthBlock(); + successfullyReadSmallerThanBuffer(); + successfullyReadGreaterThanBuffer(); + + throwWhileReadingFirstBlock(); + throwWhileClosing(); + } + + private void successfullyReadSmallerThanBuffer() + throws IOException + { + byte[] bytes = getSequentialBytes(400); + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + DoubleBufferedInputStream retIn = new DoubleBufferedInputStream<>(bIn, 512); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + Streams.pipeAll(retIn, bOut); + isEncodingEqual(bytes, bOut.toByteArray()); + } + + private void successfullyReadGreaterThanBuffer() + throws IOException + { + byte[] bytes = getSequentialBytes(2000); + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + DoubleBufferedInputStream retIn = new DoubleBufferedInputStream<>(bIn, 512); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + Streams.pipeAll(retIn, bOut); + isEncodingEqual(bytes, bOut.toByteArray()); + } + + private void throwWhileReadingFirstBlock() + { + InputStream throwAfterNBytes = new InputStream() { + int throwAt = 314; + int r = 0; + @Override + public int read() throws IOException { + int i = r; + if (r == throwAt) + { + throw new IOException("Oopsie"); + } + r++; + return i; + } + }; + DoubleBufferedInputStream retIn = new DoubleBufferedInputStream<>(throwAfterNBytes, 512); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + try + { + Streams.pipeAll(retIn, bOut); + } + catch (IOException e) + { + isEquals("Oopsie", e.getMessage()); + } + isEquals("throwWhileReadingFirstBlock: expected no bytes emitted", 0, bOut.toByteArray().length); + } + + private void throwWhileReadingNthBlock() + { + InputStream throwAfterNBytes = new InputStream() + { + int throwAt = 10; + int r = 0; + @Override + public int read() + throws IOException + { + int i = r; + if (r == throwAt) + { + throw new IOException("Oopsie"); + } + r++; + return i; + } + }; + DoubleBufferedInputStream retIn = new DoubleBufferedInputStream<>(throwAfterNBytes, 4); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + try + { + Streams.pipeAll(retIn, bOut); + } + catch (IOException e) + { + isEquals("Oopsie", e.getMessage()); + } + byte[] got = bOut.toByteArray(); + isEquals("throwWhileReadingNthBlock: expected 4 bytes emitted. Got " + got.length, 4, got.length); + } + + private void throwWhileClosing() + { + byte[] bytes = getSequentialBytes(100); + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + FilterInputStream throwOnClose = new FilterInputStream(bIn) + { + @Override + public void close() + throws IOException + { + throw new IOException("Oopsie"); + } + }; + DoubleBufferedInputStream retIn = new DoubleBufferedInputStream<>(throwOnClose, 512); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + try + { + Streams.pipeAll(retIn, bOut); + } + catch (IOException e) + { + isEquals("Oopsie", e.getMessage()); + } + isEquals("throwWhileClosing: len mismatch", 0, bOut.toByteArray().length); + } + + private byte[] getSequentialBytes(int n) + { + byte[] bytes = new byte[n]; + for (int i = 0; i < bytes.length; i++) + { + bytes[i] = (byte) (i % 128); + } + return bytes; + } + + public static void main(String[] args) + { + runTest(new DoubleBufferedInputStreamTest()); + } +} From 99e9afe58778c0a19685a22cfda2b588f9704d0c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 16:18:12 +0100 Subject: [PATCH 097/154] More cleanup in DoubleBufferInputStream --- .../api/DoubleBufferedInputStream.java | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java index 046e7f55bf..929bf3da13 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/DoubleBufferedInputStream.java @@ -4,7 +4,7 @@ import java.io.InputStream; /** - * Implementation of {@link InputStream} double-buffers data from an underlying input stream. + * Implementation of an {@link InputStream} that double-buffers data from an underlying input stream. * Upon reaching the end of the underlying data stream, the underlying data stream is * automatically closed. * Any exceptions while reading from the underlying input stream cause the {@link DoubleBufferedInputStream} @@ -27,28 +27,49 @@ public class DoubleBufferedInputStream private final I in; private boolean closed = false; + /** + * Create a {@link DoubleBufferedInputStream}, which buffers twice 32MiB. + * + * @param in input stream + */ public DoubleBufferedInputStream(I in) { this(in, BUFFER_SIZE); } + /** + * Create a {@link DoubleBufferedInputStream}, which buffers twice the given buffer size in bytes. + * + * @param in input stream + * @param bufferSize buffer size + */ public DoubleBufferedInputStream(I in, int bufferSize) { if (bufferSize <= 0) { - throw new IllegalArgumentException("Buffer size cannot be null nor negative."); + throw new IllegalArgumentException("Buffer size cannot be zero nor negative."); } this.buf1 = new byte[bufferSize]; this.buf2 = new byte[bufferSize]; this.in = in; - b1Pos = -1; + b1Pos = -1; // indicate to fill() that we need to initialize } + /** + * Return the underlying {@link InputStream}. + * + * @return underlying input stream + */ public I getInputStream() { return in; } + /** + * Buffer some data from the underlying {@link InputStream}. + * + * @throws IOException re-throw exceptions from the underlying input stream + */ private void fill() throws IOException { @@ -106,8 +127,12 @@ private void fill() } catch (IOException e) { + // set buffer max's to -1 to indicate to stop emitting data immediately b1Max = -1; b2Max = -1; + close(); + + throw e; } // EOF @@ -122,6 +147,7 @@ private void fill() public void close() throws IOException { + // close the inner stream only once if (!closed) { closed = true; @@ -136,12 +162,14 @@ public int read() // fill the buffer(s) fill(); - // EOF + // EOF / exception? if (b1Max == -1) { close(); return -1; } + + // return byte from the buffer int i = buf1[b1Pos]; b1Pos++; return i; @@ -154,21 +182,19 @@ public int read(byte[] b, int off, int len) // Fill the buffer(s) fill(); - // EOF + // EOF / exception? if (b1Max == -1) { close(); return -1; } + // available bytes in b1 int avail = b1Max - b1Pos; - // math.min(avail, len) int ret = avail < len ? avail : len; - - // emit data + // emit data from the buffer System.arraycopy(buf1, b1Pos, b, off, ret); - b1Pos += ret; return ret; } From 7c6be1f7b4f5deb63073a157f190976e1a661645 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 16:37:41 +0100 Subject: [PATCH 098/154] Add javadoc to SignatureParameters --- .../openpgp/api/SignatureParameters.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index 9e22c9fd39..15c769e73b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -6,6 +6,9 @@ import java.util.Date; +/** + * Parameters for signature generation. + */ public class SignatureParameters { private int signatureType; @@ -21,6 +24,12 @@ private SignatureParameters(int... allowedSignatureTypes) this.allowedSignatureTypes = allowedSignatureTypes; } + /** + * Create default signature parameters object for a direct-key signature. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters directKeySignature(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.DIRECT_KEY) @@ -29,6 +38,15 @@ public static SignatureParameters directKeySignature(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create a default signature parameters object for a certification signature. + * The default signature type is {@link PGPSignature#POSITIVE_CERTIFICATION}, but can be changed to + * {@link PGPSignature#DEFAULT_CERTIFICATION}, {@link PGPSignature#NO_CERTIFICATION}, + * {@link PGPSignature#CASUAL_CERTIFICATION}. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters certification(OpenPGPPolicy policy) { return new SignatureParameters( @@ -41,6 +59,12 @@ public static SignatureParameters certification(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create a default signature parameters object for a subkey binding signature. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters subkeyBinding(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.SUBKEY_BINDING) @@ -49,6 +73,12 @@ public static SignatureParameters subkeyBinding(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create a default signature parameters object for a primary-key binding (back-sig) signature. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters primaryKeyBinding(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.PRIMARYKEY_BINDING) @@ -57,6 +87,12 @@ public static SignatureParameters primaryKeyBinding(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create a default signature parameters object for a certification-revocation signature. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters certificationRevocation(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.CERTIFICATION_REVOCATION) @@ -65,6 +101,14 @@ public static SignatureParameters certificationRevocation(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create a default signature parameters object for a data/document signature. + * The default signature type is {@link PGPSignature#BINARY_DOCUMENT}, but can be changed to + * {@link PGPSignature#CANONICAL_TEXT_DOCUMENT}. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters dataSignature(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.BINARY_DOCUMENT, PGPSignature.CANONICAL_TEXT_DOCUMENT) @@ -73,6 +117,16 @@ public static SignatureParameters dataSignature(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Change the signature type of the signature to-be-generated to the given type. + * Depending on which factory method was used to instantiate the signature parameters object, + * only certain signature types are allowed. Passing an illegal signature type causes an + * {@link IllegalArgumentException} to be thrown. + * + * @param signatureType signature type + * @return parameters + * @throws IllegalArgumentException if an illegal signature type is passed + */ public SignatureParameters setSignatureType(int signatureType) { if (!Arrays.contains(allowedSignatureTypes, signatureType)) @@ -84,39 +138,79 @@ public SignatureParameters setSignatureType(int signatureType) return this; } + /** + * Return the signature type for the signature to-be-generated. + * + * @return signature type + */ public int getSignatureType() { return signatureType; } + /** + * Change the creation time of the signature to-be-generated. + * + * @param signatureCreationTime signature creation time + * @return parameters + */ public SignatureParameters setSignatureCreationTime(Date signatureCreationTime) { this.signatureCreationTime = signatureCreationTime; return this; } + /** + * Return the creation time of the signature to-be-generated. + * + * @return signature creation time + */ public Date getSignatureCreationTime() { return signatureCreationTime; } + /** + * Change the hash algorithm for the signature to-be-generated. + * + * @param signatureHashAlgorithmId signature hash algorithm id + * @return parameters + */ public SignatureParameters setSignatureHashAlgorithm(int signatureHashAlgorithmId) { this.signatureHashAlgorithmId = signatureHashAlgorithmId; return this; } + /** + * Return the hash algorithm id of the signature to-be-generated. + * + * @return hash algorithm id + */ public int getSignatureHashAlgorithmId() { return signatureHashAlgorithmId; } + /** + * Set a function, which is applied to the hashed subpackets area of the signature to-be-generated. + * + * @param subpacketsFunction function to apply to the hashed signature subpackets + * @return parameters + */ public SignatureParameters setHashedSubpacketsFunction(SignatureSubpacketsFunction subpacketsFunction) { this.hashedSubpacketsFunction = subpacketsFunction; return this; } + /** + * Apply the hashed subpackets function set via {@link #setHashedSubpacketsFunction(SignatureSubpacketsFunction)} + * to the given hashed subpackets. + * + * @param hashedSubpackets hashed signature subpackets + * @return modified hashed subpackets + */ PGPSignatureSubpacketGenerator applyToHashedSubpackets(PGPSignatureSubpacketGenerator hashedSubpackets) { if (hashedSubpacketsFunction != null) @@ -126,12 +220,25 @@ PGPSignatureSubpacketGenerator applyToHashedSubpackets(PGPSignatureSubpacketGene return hashedSubpackets; } + /** + * Set a function, which is applied to the unhashed subpackets area of the signature to-be-generated. + * + * @param subpacketsFunction function to apply to the unhashed signature subpackets + * @return parameters + */ public SignatureParameters setUnhashedSubpacketsFunction(SignatureSubpacketsFunction subpacketsFunction) { this.unhashedSubpacketsFunction = subpacketsFunction; return this; } + /** + * Apply the unhashed subpackets function set via {@link #setUnhashedSubpacketsFunction(SignatureSubpacketsFunction)} + * to the given unhashed subpackets. + * + * @param unhashedSubpackets unhashed signature subpackets + * @return modified unhashed subpackets + */ PGPSignatureSubpacketGenerator applyToUnhashedSubpackets(PGPSignatureSubpacketGenerator unhashedSubpackets) { if (unhashedSubpacketsFunction != null) @@ -141,13 +248,29 @@ PGPSignatureSubpacketGenerator applyToUnhashedSubpackets(PGPSignatureSubpacketGe return unhashedSubpackets; } + /** + * Callback, allowing the user to modify {@link SignatureParameters} before use. + */ public interface Callback { + /** + * Apply custom changes to {@link SignatureParameters}. + * + * @param parameters parameters instance + * @return modified parameters, or null + */ default SignatureParameters apply(SignatureParameters parameters) { return parameters; } + /** + * Shortcut method returning a {@link Callback} which only applies the given + * {@link SignatureSubpacketsFunction} to the hashed signature subpacket area of a signature. + * + * @param function signature subpackets function to apply to the hashed area + * @return callback + */ static Callback modifyHashedSubpackets(SignatureSubpacketsFunction function) { return new Callback() From 9e2eb7df065edafabae9c6267c82eff5b7323cab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 17:01:17 +0100 Subject: [PATCH 099/154] Add javadoc to bc and jcajce classes --- .../openpgp/api/bc/BcOpenPGPApi.java | 18 ++++++++++++++++-- .../api/bc/BcOpenPGPImplementation.java | 3 +++ .../openpgp/api/jcajce/JcaOpenPGPApi.java | 3 +++ .../api/jcajce/JcaOpenPGPImplementation.java | 3 +++ .../api/jcajce/JcaOpenPGPKeyGenerator.java | 3 +++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java index 6fb16e298c..297cd8cdac 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPApi.java @@ -2,22 +2,36 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPImplementation; import org.bouncycastle.openpgp.api.OpenPGPPolicy; import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import java.util.Date; +/** + * Implementation of {@link OpenPGPApi} using Bouncy Castles implementation of OpenPGP classes. + */ public class BcOpenPGPApi extends OpenPGPApi { public BcOpenPGPApi() { - super(new BcOpenPGPImplementation()); + this(new BcOpenPGPImplementation()); + } + + public BcOpenPGPApi(OpenPGPImplementation implementation) + { + super(implementation); } public BcOpenPGPApi(OpenPGPPolicy policy) { - super(new BcOpenPGPImplementation(), policy); + this(new BcOpenPGPImplementation(), policy); + } + + public BcOpenPGPApi(OpenPGPImplementation implementation, OpenPGPPolicy policy) + { + super(implementation, policy); } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java index 8ef91c9f5d..b603d83403 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPImplementation.java @@ -40,6 +40,9 @@ import java.io.InputStream; +/** + * Implementation of {@link OpenPGPImplementation} using Bouncy Castles implementation of OpenPGP classes. + */ public class BcOpenPGPImplementation extends OpenPGPImplementation { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java index ba09751937..771208cdab 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPApi.java @@ -10,6 +10,9 @@ import java.security.SecureRandom; import java.util.Date; +/** + * Implementation of {@link OpenPGPApi} using the JCA/JCE implementation of OpenPGP classes. + */ public class JcaOpenPGPApi extends OpenPGPApi { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java index 477908d080..00adce71e5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPImplementation.java @@ -44,6 +44,9 @@ import java.security.Provider; import java.security.SecureRandom; +/** + * Implementation of {@link OpenPGPImplementation} using the JCA/JCE implementation of OpenPGP classes. + */ public class JcaOpenPGPImplementation extends OpenPGPImplementation { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java index f66facfe49..c0401336f7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPKeyGenerator.java @@ -7,6 +7,9 @@ import java.security.SecureRandom; import java.util.Date; +/** + * JCA/JCE implementation of the {@link OpenPGPKeyGenerator}. + */ public class JcaOpenPGPKeyGenerator extends OpenPGPKeyGenerator { From a9abf56bb8ee724032b5ef8a4ad69c9d5eeefd10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 17:03:03 +0100 Subject: [PATCH 100/154] Clean up OpenPGPKeyPrinter --- .../openpgp/api/util/DebugPrinter.java | 205 ------------------ .../openpgp/api/util/OpenPGPKeyPrinter.java | 135 ++++++++++++ 2 files changed, 135 insertions(+), 205 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java deleted file mode 100644 index 97c9a8fea8..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/util/DebugPrinter.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.bouncycastle.openpgp.api.util; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.api.OpenPGPCertificate; -import org.bouncycastle.openpgp.api.OpenPGPDefaultPolicy; -import org.bouncycastle.openpgp.api.OpenPGPKeyReader; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; - -import java.io.IOException; -import java.util.Date; - -public class DebugPrinter -{ - - private static final String hardRevokedPrimaryKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + - "\n" + - "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + - "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + - "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + - "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + - "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + - "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwN8EIAEKAJMFglwqrYAJ\n" + - "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + - "ZzUC0OZfTpIdwlwf0ObCTwna1jQBSX993ccnmOrNte5LIx0CS2V5IG1hdGVyaWFs\n" + - "IGhhcyBiZWVuIGNvbXByb21pc2VkFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAAJA5\n" + - "CACTlymVijD9/t/SUBh3QihI9xjk+l2dGcFN64qkYEoplAJKedpO3z9niE9ejByF\n" + - "4tqn5BklxUGaRjq3Sgy0EQAi/nkgSq0cQX/aG2UoIs+OYbqzSktZAXIPUiQI5Ir5\n" + - "OYyALBJo03TxHHMOIBrLERVJiDGGoFNY58jQ7kUD6/XtRvpXNuQnfpRH4sAX+VQo\n" + - "fC5WojyWsiIv1aXwOJOA1IXSCHmK7lFuWVyZ6f/SGYpMnIROE1hzaRAVaaMhjcw1\n" + - "2gr5fKi/3Sd2agzwLbLfqvvYD9BI4yKkysTMp6t2ZbwcpvlWp/8Yu1Zrmf5moLJY\n" + - "6BveLKJdm/Th6Tik4dDP/WvCwsDEBB8BCgB4BYJeC+EACRAIrVHK5HDwBkcUAAAA\n" + - "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeHLGXtWodbY9gI8X3Q\n" + - "zLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqCApG3522xqAitUcrk\n" + - "cPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySjTyk+ytK1Q5E8NSUY\n" + - "k3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ/At+Bw3OPeWZ68hz\n" + - "QfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/jCEYM5Kfg4NC1yVZ\n" + - "w7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8EQq9veCfHYPwqMAH\n" + - "5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIFzvwpgKbkzb2m3Lfg\n" + - "OyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWCWkl6AAkQCK1RyuRw\n" + - "8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn1WXYy2Gc\n" + - "Q19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEWIQTjLLbaggKRt+dt\n" + - "sagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsUG65rE8R4QGFvHhhX\n" + - "M/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZuflYRmct3t0B+CfxN\n" + - "9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwkdOKkm6VVAiAKZ4QR\n" + - "8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23ZvFL1TxVx/rhxM04\n" + - "Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+eatJt1bXsNioiFIuMC\n" + - "ouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1bGlldEBleGFtcGxl\n" + - "Lm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3Rh\n" + - "dGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa5AImO40vTfrIbkXR\n" + - "2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAn/UIALMbXwG8\n" + - "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\n" + - "=x/EN\n" + - "-----END PGP PUBLIC KEY BLOCK-----\n"; - - private static final String v6SecretKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + - "\n" + - "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB\n" + - "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ\n" + - "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + - "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh\n" + - "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe\n" + - "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/\n" + - "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG\n" + - "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + - "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE\n" + - "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr\n" + - "k0mXubZvyl4GBg==\n" + - "-----END PGP PRIVATE KEY BLOCK-----"; - - public static void main(String[] args) - throws IOException - { - OpenPGPKeyReader reader = new OpenPGPKeyReader(); - OpenPGPCertificate certificate = reader.parseCertificate(v6SecretKey); - // -DM System.out.println - System.out.println(toString(certificate, new Date())); - } - - public static String toString(OpenPGPCertificate certificate, Date evaluationTime) - { - StringBuilder sb = new StringBuilder(); - for (OpenPGPCertificate.OpenPGPCertificateComponent component : certificate.getComponents()) - { - if (component.isBoundAt(evaluationTime)) - { - green(sb, component.toDetailString()).append("\n"); - } - else - { - red(sb, component.toDetailString()).append("\n"); - } - - OpenPGPCertificate.OpenPGPSignatureChains chains = component.getSignatureChains(); - for (OpenPGPCertificate.OpenPGPSignatureChain chain : chains) - { - boolean revocation = chain.isRevocation(); - boolean isHardRevocation = chain.isHardRevocation(); - String indent = ""; - for (OpenPGPCertificate.OpenPGPSignatureChain.Link link : chain) - { - indent = indent + " "; - sb.append(indent); - try - { - link.verify(new BcPGPContentVerifierBuilderProvider(), new OpenPGPDefaultPolicy()); - if (revocation) - { - if (isHardRevocation) - { - red(sb, link.toString()).append("\n"); - } - else - { - yellow(sb, link.toString()).append("\n"); - } - } - else - { - green(sb, link.toString()).append("\n"); - } - } - catch (PGPException e) - { - red(sb, link.toString()).append("\n"); - } - } - } - } - - return sb.toString(); - } - - private static StringBuilder red(StringBuilder sb, String text) - { - return sb.append("\033[31m").append(text).append("\033[0m"); - } - - private static StringBuilder redBg(StringBuilder sb, String text) - { - return sb.append("\033[41m").append(text).append("\033[0m"); - } - - private static StringBuilder green(StringBuilder sb, String text) - { - return sb.append("\033[32m").append(text).append("\033[0m"); - } - - private static StringBuilder greenBg(StringBuilder sb, String text) - { - return sb.append("\033[42m").append(text).append("\033[0m"); - } - - private static StringBuilder yellow(StringBuilder sb, String text) - { - return sb.append("\033[33m").append(text).append("\033[0m"); - } - - private static StringBuilder yellowBg(StringBuilder sb, String text) - { - return sb.append("\033[43m").append(text).append("\033[0m"); - } - -} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java new file mode 100644 index 0000000000..017fa50e39 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java @@ -0,0 +1,135 @@ +package org.bouncycastle.openpgp.api.util; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPDefaultPolicy; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Date; + +/** + * Class to debug-print OpenPGP certificates and keys. + */ +public class OpenPGPKeyPrinter +{ + + public static void main(String[] args) + throws IOException + { + OpenPGPApi api = new BcOpenPGPApi(); + + if (args.length == 0) + { + System.err.println("Usage: OpenPGPKeyPrinter path/to/file..."); + System.exit(1); + } + + for (String path : args) + { + File file = new File(path); + if (!file.exists() || !file.isFile()) + { + System.err.println("Error: " + path + " is not a file or does not exist."); + System.exit(1); + } + + try (FileInputStream fIn = new FileInputStream(file)) + { + OpenPGPCertificate certOrKey = api.readKeyOrCertificate() + .parseCertificateOrKey(fIn); + // -DM System.out.println + System.out.println(toString(certOrKey, new Date())); + } + } + } + + public static String toString(OpenPGPCertificate certificate, Date evaluationTime) + { + StringBuilder sb = new StringBuilder(); + for (OpenPGPCertificate.OpenPGPCertificateComponent component : certificate.getComponents()) + { + if (component.isBoundAt(evaluationTime)) + { + green(sb, component.toDetailString()).append("\n"); + } + else + { + red(sb, component.toDetailString()).append("\n"); + } + + OpenPGPCertificate.OpenPGPSignatureChains chains = component.getSignatureChains(); + for (OpenPGPCertificate.OpenPGPSignatureChain chain : chains) + { + boolean revocation = chain.isRevocation(); + boolean isHardRevocation = chain.isHardRevocation(); + String indent = ""; + for (OpenPGPCertificate.OpenPGPSignatureChain.Link link : chain) + { + indent = indent + " "; + sb.append(indent); + try + { + link.verify(new BcPGPContentVerifierBuilderProvider(), new OpenPGPDefaultPolicy()); + if (revocation) + { + if (isHardRevocation) + { + red(sb, link.toString()).append("\n"); + } + else + { + yellow(sb, link.toString()).append("\n"); + } + } + else + { + green(sb, link.toString()).append("\n"); + } + } + catch (PGPException e) + { + red(sb, link.toString()).append("\n"); + } + } + } + } + + return sb.toString(); + } + + private static StringBuilder red(StringBuilder sb, String text) + { + return sb.append("\033[31m").append(text).append("\033[0m"); + } + + private static StringBuilder redBg(StringBuilder sb, String text) + { + return sb.append("\033[41m").append(text).append("\033[0m"); + } + + private static StringBuilder green(StringBuilder sb, String text) + { + return sb.append("\033[32m").append(text).append("\033[0m"); + } + + private static StringBuilder greenBg(StringBuilder sb, String text) + { + return sb.append("\033[42m").append(text).append("\033[0m"); + } + + private static StringBuilder yellow(StringBuilder sb, String text) + { + return sb.append("\033[33m").append(text).append("\033[0m"); + } + + private static StringBuilder yellowBg(StringBuilder sb, String text) + { + return sb.append("\033[43m").append(text).append("\033[0m"); + } + +} From 34449859cf3230db1001851993e606831b464663 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 17:03:15 +0100 Subject: [PATCH 101/154] Add javadoc to UTCUtil --- .../main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java index d2013cc6e5..78ce9a5f7d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/UTCUtil.java @@ -5,6 +5,9 @@ import java.util.Date; import java.util.TimeZone; +/** + * Utility class for parsing and formatting UTC timestamps. + */ public class UTCUtil { private static SimpleDateFormat utc() From 773533e3e5cfe60482a52b9de903e037f41d9cdf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 17:12:45 +0100 Subject: [PATCH 102/154] Suppress warnings in OpenPGPKeyPrinter --- .../org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java b/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java index 017fa50e39..5ff6f381b8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/util/OpenPGPKeyPrinter.java @@ -25,7 +25,9 @@ public static void main(String[] args) if (args.length == 0) { + // -DM System.err.println System.err.println("Usage: OpenPGPKeyPrinter path/to/file..."); + // -DM System.exit System.exit(1); } @@ -34,7 +36,9 @@ public static void main(String[] args) File file = new File(path); if (!file.exists() || !file.isFile()) { + // -DM System.err.println System.err.println("Error: " + path + " is not a file or does not exist."); + // -DM System.exit System.exit(1); } From 4f616528626136601e403fcf8aa602d9d3a8ad3a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 17:14:03 +0100 Subject: [PATCH 103/154] Introduce OpenPGPSignatureException, exposing the OpenPGPSignature --- .../openpgp/api/OpenPGPCertificate.java | 22 +++++++------- .../openpgp/api/OpenPGPSignature.java | 29 ++++++++++++------- .../IncorrectOpenPGPSignatureException.java | 15 ++++++++++ .../IncorrectPGPSignatureException.java | 15 ---------- .../MalformedOpenPGPSignatureException.java | 16 ++++++++++ .../MalformedPGPSignatureException.java | 16 ---------- .../exception/MissingIssuerCertException.java | 8 ++--- .../exception/OpenPGPSignatureException.java | 21 ++++++++++++++ 8 files changed, 86 insertions(+), 56 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectOpenPGPSignatureException.java delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedOpenPGPSignatureException.java delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPSignatureException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 5908f282e0..79c437f903 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -28,8 +28,8 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.api.exception.IncorrectPGPSignatureException; -import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.IncorrectOpenPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.MalformedOpenPGPSignatureException; import org.bouncycastle.openpgp.api.exception.MissingIssuerCertException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; @@ -947,7 +947,7 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi if (issuer == null) { // No issuer available - throw new MissingIssuerCertException("Issuer certificate unavailable."); + throw new MissingIssuerCertException(this, "Issuer certificate unavailable."); } sanitize(issuer, policy); @@ -1022,7 +1022,8 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c if (embeddedSignatures.isEmpty()) { - throw new MalformedPGPSignatureException( + throw new MalformedOpenPGPSignatureException( + this, "Signing key SubkeyBindingSignature MUST contain embedded PrimaryKeyBindingSignature."); } PGPSignature primaryKeyBinding = embeddedSignatures.get(0); @@ -1036,9 +1037,10 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); } - protected void verifyKeySignature(OpenPGPComponentKey issuer, - OpenPGPComponentKey target, - PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) + protected void verifyKeySignature( + OpenPGPComponentKey issuer, + OpenPGPComponentKey target, + PGPContentVerifierBuilderProvider contentVerifierBuilderProvider) throws PGPSignatureException { this.isTested = true; @@ -1062,7 +1064,7 @@ else if (signature.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) if (!isCorrect) { - throw new IncorrectPGPSignatureException("Key Signature is not correct."); + throw new IncorrectOpenPGPSignatureException(this, "Key Signature is not correct."); } } catch (PGPException e) @@ -1084,7 +1086,7 @@ protected void verifyUserIdSignature(OpenPGPComponentKey issuer, isCorrect = signature.verifyCertification(target.getUserId(), target.getPrimaryKey().getPGPPublicKey()); if (!isCorrect) { - throw new IncorrectPGPSignatureException("UserID Signature is not correct."); + throw new IncorrectOpenPGPSignatureException(this, "UserID Signature is not correct."); } } catch (PGPException e) @@ -1106,7 +1108,7 @@ protected void verifyUserAttributeSignature(OpenPGPComponentKey issuer, isCorrect = signature.verifyCertification(target.getUserAttribute(), target.getPrimaryKey().getPGPPublicKey()); if (!isCorrect) { - throw new IncorrectPGPSignatureException("UserAttribute Signature is not correct."); + throw new IncorrectOpenPGPSignatureException(this, "UserAttribute Signature is not correct."); } } catch (PGPException e) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 6e36db86cc..41acec72ca 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -13,7 +13,7 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureException; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.bouncycastle.openpgp.api.exception.MalformedPGPSignatureException; +import org.bouncycastle.openpgp.api.exception.MalformedOpenPGPSignatureException; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.util.encoders.Hex; @@ -249,7 +249,7 @@ public boolean isCertification() * Check certain requirements for OpenPGP signatures. * * @param issuer signature issuer - * @throws MalformedPGPSignatureException if the signature is malformed + * @throws MalformedOpenPGPSignatureException if the signature is malformed */ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, OpenPGPPolicy policy) @@ -267,26 +267,30 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); if (hashed == null) { - throw new MalformedPGPSignatureException("Missing hashed signature subpacket area."); + throw new MalformedOpenPGPSignatureException( + this, "Missing hashed signature subpacket area."); } PGPSignatureSubpacketVector unhashed = signature.getUnhashedSubPackets(); if (hashed.getSignatureCreationTime() == null) { // Signatures MUST have hashed creation time subpacket - throw new MalformedPGPSignatureException("Signature does not have a hashed SignatureCreationTime subpacket."); + throw new MalformedOpenPGPSignatureException( + this, "Signature does not have a hashed SignatureCreationTime subpacket."); } if (hashed.getSignatureCreationTime().before(issuer.getCreationTime())) { - throw new MalformedPGPSignatureException("Signature predates issuer key creation time."); + throw new MalformedOpenPGPSignatureException( + this, "Signature predates issuer key creation time."); } for (NotationData notation : hashed.getNotationDataOccurrences()) { if (notation.isCritical()) { - throw new MalformedPGPSignatureException("Critical unknown NotationData encountered: " + notation.getNotationName()); + throw new MalformedOpenPGPSignatureException( + this, "Critical unknown NotationData encountered: " + notation.getNotationName()); } } @@ -296,8 +300,8 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, if (unknownSubpacket.isCritical() && unknownSubpacket.getClass().equals(SignatureSubpacket.class)) { - throw new MalformedPGPSignatureException("Critical hashed unknown SignatureSubpacket encountered: " - + unknownSubpacket.getType()); + throw new MalformedOpenPGPSignatureException( + this, "Critical hashed unknown SignatureSubpacket encountered: " + unknownSubpacket.getType()); } } @@ -309,7 +313,8 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null && unhashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null) { - throw new MalformedPGPSignatureException("Missing IssuerKeyID and IssuerFingerprint subpacket."); + throw new MalformedOpenPGPSignatureException( + this, "Missing IssuerKeyID and IssuerFingerprint subpacket."); } break; @@ -320,11 +325,13 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, case SignaturePacket.VERSION_6: if (hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) != null) { - throw new MalformedPGPSignatureException("v6 signature MUST NOT contain IssuerKeyID subpacket."); + throw new MalformedOpenPGPSignatureException( + this, "v6 signature MUST NOT contain IssuerKeyID subpacket."); } if (hashed.getIssuerFingerprint() == null && unhashed.getIssuerFingerprint() == null) { - throw new MalformedPGPSignatureException("v6 signature MUST contain IssuerFingerprint subpacket."); + throw new MalformedOpenPGPSignatureException( + this, "v6 signature MUST contain IssuerFingerprint subpacket."); } break; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectOpenPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectOpenPGPSignatureException.java new file mode 100644 index 0000000000..44d5c34aa4 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectOpenPGPSignatureException.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.api.OpenPGPSignature; + +/** + * An OpenPGP signature is not correct. + */ +public class IncorrectOpenPGPSignatureException + extends OpenPGPSignatureException +{ + public IncorrectOpenPGPSignatureException(OpenPGPSignature signature, String message) + { + super(signature, message); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java deleted file mode 100644 index 8d7bd5c601..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/IncorrectPGPSignatureException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.bouncycastle.openpgp.api.exception; - -import org.bouncycastle.openpgp.PGPSignatureException; - -/** - * An OpenPGP signature is not correct. - */ -public class IncorrectPGPSignatureException - extends PGPSignatureException -{ - public IncorrectPGPSignatureException(String message) - { - super(message); - } -} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedOpenPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedOpenPGPSignatureException.java new file mode 100644 index 0000000000..06d941a7cf --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedOpenPGPSignatureException.java @@ -0,0 +1,16 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.api.OpenPGPSignature; + +/** + * An OpenPGP Signature is malformed (missing required subpackets, etc.). + */ +public class MalformedOpenPGPSignatureException + extends OpenPGPSignatureException +{ + + public MalformedOpenPGPSignatureException(OpenPGPSignature signature, String message) + { + super(signature, message); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java deleted file mode 100644 index f83695359d..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MalformedPGPSignatureException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.bouncycastle.openpgp.api.exception; - -import org.bouncycastle.openpgp.PGPSignatureException; - -/** - * An OpenPGP Signature is malformed (missing required subpackets, etc.). - */ -public class MalformedPGPSignatureException - extends PGPSignatureException -{ - - public MalformedPGPSignatureException(String message) - { - super(message); - } -} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java index ded366a9ef..4a37432966 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/MissingIssuerCertException.java @@ -1,15 +1,15 @@ package org.bouncycastle.openpgp.api.exception; -import org.bouncycastle.openpgp.PGPSignatureException; +import org.bouncycastle.openpgp.api.OpenPGPSignature; /** * The OpenPGP certificate (public key) required to verify a signature is not available. */ public class MissingIssuerCertException - extends PGPSignatureException + extends OpenPGPSignatureException { - public MissingIssuerCertException(String message) + public MissingIssuerCertException(OpenPGPSignature signature, String message) { - super(message); + super(signature, message); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPSignatureException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPSignatureException.java new file mode 100644 index 0000000000..16df89d966 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPSignatureException.java @@ -0,0 +1,21 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPSignatureException; +import org.bouncycastle.openpgp.api.OpenPGPSignature; + +public class OpenPGPSignatureException + extends PGPSignatureException +{ + private final OpenPGPSignature signature; + + public OpenPGPSignatureException(OpenPGPSignature signature, String message) + { + super(message); + this.signature = signature; + } + + public OpenPGPSignature getSignature() + { + return signature; + } +} From dcc309edaec2382099192dbdc5aadfbec5a04ccf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 18:03:46 +0100 Subject: [PATCH 104/154] Introduce OpenPGPKeyException exposing problematic (component) keys --- ...ractOpenPGPDocumentSignatureGenerator.java | 88 +++++++++++++++++-- .../bouncycastle/openpgp/api/OpenPGPKey.java | 2 +- .../openpgp/api/OpenPGPMessageGenerator.java | 6 +- .../InvalidEncryptionKeyException.java | 29 +++++- .../exception/InvalidSigningKeyException.java | 29 +++++- .../api/exception/KeyPassphraseException.java | 30 ++++++- .../api/exception/OpenPGPKeyException.java | 68 ++++++++++++++ ...OpenPGPDetachedSignatureProcessorTest.java | 8 +- 8 files changed, 231 insertions(+), 29 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPKeyException.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java index 60d0fa49f4..368c30db22 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java @@ -34,6 +34,7 @@ public class AbstractOpenPGPDocumentSignatureGenerator select(OpenPGPCertificate certificate, OpenPGPPolicy policy) { + // Sign with all acceptable signing subkeys of the given key return certificate.getSigningKeys() .stream() .filter(key -> policy.isAcceptablePublicKey(key.getPGPPublicKey())) @@ -60,13 +61,28 @@ public T setSigningKeySelector(SubkeySelector signingKeySelector) return (T) this; } - + /** + * Add a passphrase for unlocking signing keys to the set of available passphrases. + * + * @param passphrase passphrase + * @return this + */ public T addKeyPassphrase(char[] passphrase) { defaultKeyPassphraseProvider.addPassphrase(passphrase); return (T) this; } + /** + * Add an {@link OpenPGPKey} for message signing. + * The {@link #signingKeySelector} is responsible for selecting one or more subkeys of the key to sign with. + * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, + * this method will throw an {@link InvalidSigningKeyException}. + * + * @param key OpenPGP key + * @return this + * @throws InvalidSigningKeyException if the key is not capable of signing + */ public T addSigningKey( OpenPGPKey key) throws InvalidSigningKeyException @@ -75,17 +91,17 @@ public T addSigningKey( } /** - * Add an {@link OpenPGPKey} as signing key. + * Add an {@link OpenPGPKey} for message signing, using the provided {@link KeyPassphraseProvider} to + * unlock protected subkeys. + * The {@link #signingKeySelector} is responsible for selecting one or more subkeys of the key to sign with. * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, * this method will throw an {@link InvalidSigningKeyException}. - * Otherwise, all capable signing subkeys will be used to create detached signatures. * * @param key OpenPGP key * @param passphraseProvider provides the passphrase to unlock the signing key * @return this * * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey - * @throws PGPException if signing fails */ public T addSigningKey( OpenPGPKey key, @@ -95,6 +111,18 @@ public T addSigningKey( return addSigningKey(key, passphraseProvider, null); } + /** + * Add an {@link OpenPGPKey} for message signing, using the {@link SignatureParameters.Callback} to + * allow modification of the signature contents. + * The {@link #signingKeySelector} is responsible for selecting one or more subkeys of the key to sign with. + * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, + * this method will throw an {@link InvalidSigningKeyException}. + * + * @param key OpenPGP key + * @param signatureCallback optional callback to modify the signature contents with + * @return this + * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey + */ public T addSigningKey( OpenPGPKey key, SignatureParameters.Callback signatureCallback) @@ -103,6 +131,20 @@ public T addSigningKey( return addSigningKey(key, defaultKeyPassphraseProvider, signatureCallback); } + /** + * Add an {@link OpenPGPKey} for message signing, using the given {@link KeyPassphraseProvider} + * for unlocking protected subkeys and using the {@link SignatureParameters.Callback} to allow + * modification of the signature contents. + * The {@link #signingKeySelector} is responsible for selecting one or more subkeys of the key to sign with. + * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, + * this method will throw an {@link InvalidSigningKeyException}. + * + * @param key OpenPGP key + * @param passphraseProvider key passphrase provider + * @param signatureCallback optional callback to modify the signature contents with + * @return this + * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey + */ public T addSigningKey( OpenPGPKey key, KeyPassphraseProvider passphraseProvider, @@ -112,7 +154,7 @@ public T addSigningKey( List signingSubkeys = signingKeySelector.select(key, policy); if (signingSubkeys.isEmpty()) { - throw new InvalidSigningKeyException("Key " + key.getPrettyFingerprint() + " cannot sign."); + throw new InvalidSigningKeyException(key); } for (OpenPGPCertificate.OpenPGPComponentKey subkey : signingSubkeys) @@ -124,6 +166,17 @@ public T addSigningKey( return (T) this; } + /** + * Add the given signing (sub-)key for message signing, using the optional passphrase to unlock the + * key in case its locked, and using the given {@link SignatureParameters.Callback} to allow + * modification of the signature contents. + * + * @param signingKey signing (sub-)key + * @param passphrase optional subkey passphrase + * @param signatureCallback optional callback to modify the signature contents + * @return this + * @throws InvalidSigningKeyException if the subkey is not signing-capable + */ public T addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, char[] passphrase, @@ -136,6 +189,17 @@ public T addSigningKey( signatureCallback); } + /** + * Add the given signing (sub-)key for message signing, using the passphrase provider to unlock the + * key in case its locked, and using the given {@link SignatureParameters.Callback} to allow + * modification of the signature contents. + * + * @param signingKey signing (sub-)key + * @param passphraseProvider passphrase provider for unlocking the subkey + * @param signatureCallback optional callback to modify the signature contents + * @return this + * @throws InvalidSigningKeyException if the subkey is not signing-capable + */ public T addSigningKey( OpenPGPKey.OpenPGPSecretKey signingKey, KeyPassphraseProvider passphraseProvider, @@ -144,7 +208,7 @@ public T addSigningKey( { if (!signingKey.isSigningKey()) { - throw new InvalidSigningKeyException("Subkey cannot sign."); + throw new InvalidSigningKeyException(signingKey); } signingKeys.add(signingKey); @@ -174,8 +238,7 @@ protected PGPSignatureGenerator initSignatureGenerator( if (!signingKey.isSigningKey(parameters.getSignatureCreationTime())) { - throw new InvalidSigningKeyException("Provided key " + signingKey.getKeyIdentifier() + - " is not capable of creating data signatures."); + throw new InvalidSigningKeyException(signingKey); } PGPSignatureGenerator sigGen = new PGPSignatureGenerator( @@ -201,6 +264,8 @@ protected PGPSignatureGenerator initSignatureGenerator( private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key) { + // Determine the Hash Algorithm to use by inspecting the signing key's hash algorithm preferences + // TODO: Instead inspect the hash algorithm preferences of recipient certificates? PreferredAlgorithms hashPreferences = key.getHashAlgorithmPreferences(); if (hashPreferences != null) { @@ -216,6 +281,13 @@ private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key return policy.getDefaultDocumentSignatureHashAlgorithm(); } + /** + * Set a callback that will be fired, if a passphrase for a protected signing key is missing. + * This can be used for example to implement interactive on-demand passphrase prompting. + * + * @param callback passphrase provider + * @return builder + */ public T setMissingKeyPassphraseCallback(KeyPassphraseProvider callback) { defaultKeyPassphraseProvider.setMissingPassphraseCallback(callback); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index bf89d4074b..6cb60aba7f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -274,7 +274,7 @@ public PGPPrivateKey unlock(char[] passphrase) } catch (PGPException e) { - throw new KeyPassphraseException(e); + throw new KeyPassphraseException(this, e); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index 4994dd71a8..ea6109b9be 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -110,8 +110,7 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip subkeySelector.select(recipientCertificate, policy); if (subkeys.isEmpty()) { - throw new InvalidEncryptionKeyException("Certificate " + recipientCertificate.getKeyIdentifier() + - " does not have valid encryption subkeys."); + throw new InvalidEncryptionKeyException(recipientCertificate); } this.encryptionKeys.addAll(subkeys); return this; @@ -130,8 +129,7 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenP { if (!encryptionKey.isEncryptionKey()) { - throw new InvalidEncryptionKeyException("Provided subkey " + encryptionKey.getKeyIdentifier() + - " is not a valid encryption key."); + throw new InvalidEncryptionKeyException(encryptionKey); } encryptionKeys.add(encryptionKey); return this; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java index 0b720662dd..9e3f04b488 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidEncryptionKeyException.java @@ -1,16 +1,37 @@ package org.bouncycastle.openpgp.api.exception; -import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.util.Arrays; /** * Exception that gets thrown if the user tries to encrypt a message for an * {@link org.bouncycastle.openpgp.api.OpenPGPCertificate} that does not contain any usable, valid encryption keys. */ public class InvalidEncryptionKeyException - extends PGPException + extends OpenPGPKeyException { - public InvalidEncryptionKeyException(String message) + + public InvalidEncryptionKeyException(OpenPGPCertificate certificate) + { + super(certificate, "Certificate " + certificate.getKeyIdentifier() + + " does not contain any usable subkeys capable of encryption."); + } + + public InvalidEncryptionKeyException(OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey) + { + super(encryptionSubkey, componentKeyErrorMessage(encryptionSubkey)); + } + + private static String componentKeyErrorMessage(OpenPGPCertificate.OpenPGPComponentKey componentKey) { - super(message); + if (componentKey.getKeyIdentifier().equals(componentKey.getCertificate().getKeyIdentifier())) + { + return "The primary key " + componentKey.getKeyIdentifier() + " is not usable for encryption."; + } + else + { + return "The subkey " + componentKey.getKeyIdentifier() + " from the certificate " + + componentKey.getCertificate().getKeyIdentifier() + " is not usable for encryption."; + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java index 773b66a5c2..fccdefecb9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/InvalidSigningKeyException.java @@ -1,12 +1,33 @@ package org.bouncycastle.openpgp.api.exception; -import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; public class InvalidSigningKeyException - extends PGPException + extends OpenPGPKeyException { - public InvalidSigningKeyException(String message) + + public InvalidSigningKeyException(OpenPGPKey key) + { + super(key, "The key " + key.getKeyIdentifier() + + " does not contain any usable component keys capable of signing."); + } + + public InvalidSigningKeyException(OpenPGPCertificate.OpenPGPComponentKey componentKey) + { + super(componentKey, componentKeyErrorMessage(componentKey)); + } + + private static String componentKeyErrorMessage(OpenPGPCertificate.OpenPGPComponentKey componentKey) { - super(message); + if (componentKey.getKeyIdentifier().equals(componentKey.getCertificate().getKeyIdentifier())) + { + return "The primary key " + componentKey.getKeyIdentifier() + " is not usable for signing."; + } + else + { + return "The subkey " + componentKey.getKeyIdentifier() + " from the certificate " + + componentKey.getCertificate().getKeyIdentifier() + " is not usable for signing."; + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java index 48f9140ad4..7a51216fdf 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/KeyPassphraseException.java @@ -1,12 +1,34 @@ package org.bouncycastle.openpgp.api.exception; -import org.bouncycastle.openpgp.PGPException; + +import org.bouncycastle.openpgp.api.OpenPGPCertificate; public class KeyPassphraseException - extends PGPException + extends OpenPGPKeyException { - public KeyPassphraseException(Exception cause) + private final Exception cause; + + public KeyPassphraseException(OpenPGPCertificate.OpenPGPComponentKey key, Exception cause) + { + super(key, componentKeyErrorMessage(key, cause)); + this.cause = cause; + } + + private static String componentKeyErrorMessage(OpenPGPCertificate.OpenPGPComponentKey key, Exception cause) + { + if (key.getKeyIdentifier().equals(key.getCertificate().getKeyIdentifier())) + { + return "Cannot unlock primary key " + key.getKeyIdentifier() + ": " + cause.getMessage(); + } + else + { + return "Cannot unlock subkey " + key.getKeyIdentifier() + " from key " + + key.getCertificate().getKeyIdentifier() + ": " + cause.getMessage(); + } + } + + public Exception getCause() { - super("Cannot unlock secret key", cause); + return cause; } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPKeyException.java b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPKeyException.java new file mode 100644 index 0000000000..04e5787ea5 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/exception/OpenPGPKeyException.java @@ -0,0 +1,68 @@ +package org.bouncycastle.openpgp.api.exception; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; + +/** + * Exception representing an unusable or invalid {@link org.bouncycastle.openpgp.api.OpenPGPKey} + * or {@link OpenPGPCertificate}. + * Note: The term "key" is used to refer to both a certificate and a key. + */ +public class OpenPGPKeyException + extends PGPException +{ + private final OpenPGPCertificate key; + private final OpenPGPCertificate.OpenPGPComponentKey componentKey; + + private OpenPGPKeyException(OpenPGPCertificate key, + OpenPGPCertificate.OpenPGPComponentKey componentKey, + String message) + { + super(message); + this.key = key; + this.componentKey = componentKey; + } + + /** + * Something is wrong with a key or certificate in general (no particular subkey). + * + * @param key certificate or key + * @param message message + */ + public OpenPGPKeyException(OpenPGPCertificate key, String message) + { + this(key, null, message); + } + + /** + * Something is wrong with an individual component key of a key or certificate. + * + * @param componentKey component key + * @param message message + */ + public OpenPGPKeyException(OpenPGPCertificate.OpenPGPComponentKey componentKey, String message) + { + this(componentKey.getCertificate(), componentKey, message); + } + + /** + * Return the problematic key or certificate. + * + * @return key or certificate + */ + public OpenPGPCertificate getKey() + { + return key; + } + + /** + * Return the problematic component key. + * Might be null, if the problem affects the entire key or certificate. + * + * @return component key + */ + public OpenPGPCertificate.OpenPGPComponentKey getComponentKey() + { + return componentKey; + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java index 172bf507dc..9c2000b850 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -117,7 +117,7 @@ private void createVerifyV6Signature(OpenPGPApi api) private void missingPassphraseThrows(OpenPGPApi api) { isNotNull(testException( - "Cannot unlock secret key", + "Cannot unlock primary key CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9: Exception decrypting key", "KeyPassphraseException", new TestExceptionOperation() { @@ -135,7 +135,7 @@ public void operation() private void wrongPassphraseThrows(OpenPGPApi api) { isNotNull(testException( - "Cannot unlock secret key", + "Cannot unlock primary key CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9: Exception decrypting key", "KeyPassphraseException", new TestExceptionOperation() { @@ -222,7 +222,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa ).build(); isNotNull(testException( - "Key " + noSigningKey.getPrettyFingerprint() + " cannot sign.", + "The key " + noSigningKey.getKeyIdentifier() + " does not contain any usable component keys capable of signing.", "InvalidSigningKeyException", new TestExceptionOperation() { @@ -265,7 +265,7 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa ).build(); isNotNull(testException( - "Subkey cannot sign.", + "The primary key " + noSigningKey.getPrimaryKey().getKeyIdentifier() + " is not usable for signing.", "InvalidSigningKeyException", new TestExceptionOperation() { From ff16e8b4a0d4bbce4ca6aa2578eae95115f8ee44 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 18:17:37 +0100 Subject: [PATCH 105/154] Add javadoc to OpenPGPApi --- .../bouncycastle/openpgp/api/OpenPGPApi.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java index a63048c2ca..fed7b7ef95 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -5,82 +5,210 @@ import java.util.Date; +/** + * Main entry to the high level OpenPGP API. + */ public abstract class OpenPGPApi { private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; + /** + * Instantiate an {@link OpenPGPApi} based on the given {@link OpenPGPImplementation}. + * + * @param implementation OpenPGP implementation + */ public OpenPGPApi(OpenPGPImplementation implementation) { this(implementation, implementation.policy()); } + /** + * Instantiate an {@link OpenPGPApi} object, passing in an {@link OpenPGPImplementation} and custom + * {@link OpenPGPPolicy}. + * + * @param implementation OpenPGP implementation + * @param policy algorithm policy + */ public OpenPGPApi(OpenPGPImplementation implementation, OpenPGPPolicy policy) { this.implementation = implementation; this.policy = policy; } + /** + * Return an {@link OpenPGPKeyReader} which can be used to parse binary or ASCII armored + * {@link OpenPGPKey OpenPGPKeys} or {@link OpenPGPCertificate OpenPGPCertificates}. + * + * @return key reader + */ public OpenPGPKeyReader readKeyOrCertificate() { return new OpenPGPKeyReader(implementation, policy); } + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys}. + * This method returns a generator for OpenPGP v6 keys as defined by rfc9580. + * + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public OpenPGPKeyGenerator generateKey() throws PGPException { return generateKey(PublicKeyPacket.VERSION_6); } + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys} + * of the given key version. + * Valid version numbers are: + *
    + *
  • {@link PublicKeyPacket#VERSION_4} (rfc4880)
  • + *
  • {@link PublicKeyPacket#VERSION_6} (rfc9580)
  • + *
  • {@link PublicKeyPacket#LIBREPGP_5} (LibrePGP; experimental)
  • + *
+ * + * @param version key version number + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public abstract OpenPGPKeyGenerator generateKey(int version) throws PGPException; + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys}. + * The key and signatures will have a creation time of the passed creationTime. + * This method returns a generator for OpenPGP v6 keys as defined by rfc9580. + * + * @param creationTime key + signature creation time + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public OpenPGPKeyGenerator generateKey(Date creationTime) throws PGPException { return generateKey(PublicKeyPacket.VERSION_6, creationTime); } + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys} + * of the given key version. + * The key and signatures will have a creation time of the passed creationTime. + * Valid version numbers are: + *
    + *
  • {@link PublicKeyPacket#VERSION_4} (rfc4880)
  • + *
  • {@link PublicKeyPacket#VERSION_6} (rfc9580)
  • + *
  • {@link PublicKeyPacket#LIBREPGP_5} (LibrePGP; experimental)
  • + *
+ * + * @param version key version number + * @param creationTime key + signatures creation time + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public abstract OpenPGPKeyGenerator generateKey(int version, Date creationTime) throws PGPException; + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys}. + * The key and signatures will have a creation time of the passed creationTime. + * If aeadProtection is true, the key will use AEAD+Argon2 to protect the secret key material, + * otherwise it will use salted+iterated CFB mode. + * This method returns a generator for OpenPGP v6 keys as defined by rfc9580. + * + * @param creationTime key + signature creation time + * @param aeadProtection whether to use AEAD or CFB protection + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public OpenPGPKeyGenerator generateKey(Date creationTime, boolean aeadProtection) throws PGPException { return generateKey(PublicKeyPacket.VERSION_6, creationTime, aeadProtection); } + /** + * Return an {@link OpenPGPKeyGenerator} which can be used to generate {@link OpenPGPKey OpenPGPKeys} + * of the given key version. + * The key and signatures will have a creation time of the passed creationTime. + * If aeadProtection is true, the key will use AEAD+Argon2 to protect the secret key material, + * otherwise it will use salted+iterated CFB mode. + * Valid version numbers are: + *
    + *
  • {@link PublicKeyPacket#VERSION_4} (rfc4880)
  • + *
  • {@link PublicKeyPacket#VERSION_6} (rfc9580)
  • + *
  • {@link PublicKeyPacket#LIBREPGP_5} (LibrePGP; experimental)
  • + *
+ * + * @param creationTime key + signature creation time + * @param aeadProtection whether to use AEAD or CFB protection + * @return key generator + * @throws PGPException if the key generator cannot be set up + */ public abstract OpenPGPKeyGenerator generateKey(int version, Date creationTime, boolean aeadProtection) throws PGPException; + /** + * Create an inline-signed and/or encrypted OpenPGP message. + * + * @return message generator + */ public OpenPGPMessageGenerator signAndOrEncryptMessage() { return new OpenPGPMessageGenerator(implementation, policy); } + /** + * Create one or more detached signatures over some data. + * + * @return signature generator + */ public OpenPGPDetachedSignatureGenerator createDetachedSignature() { return new OpenPGPDetachedSignatureGenerator(implementation, policy); } + /** + * Decrypt and/or verify an OpenPGP message. + * + * @return message processor + */ public OpenPGPMessageProcessor decryptAndOrVerifyMessage() { return new OpenPGPMessageProcessor(implementation, policy); } + /** + * Verify detached signatures over some data. + * + * @return signature processor + */ public OpenPGPDetachedSignatureProcessor verifyDetachedSignature() { return new OpenPGPDetachedSignatureProcessor(implementation, policy); } + /** + * Modify an {@link OpenPGPKey}. + * + * @param key OpenPGP key + * @return key editor + */ public OpenPGPKeyEditor editKey(OpenPGPKey key) { return new OpenPGPKeyEditor(key, implementation, policy); } + /** + * Return the underlying {@link OpenPGPImplementation} of this API handle. + * + * @return OpenPGP implementation + */ public OpenPGPImplementation getImplementation() { return implementation; From 4e2403f40faf81791a41a3984de327351b078c6e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 18:30:48 +0100 Subject: [PATCH 106/154] Remove useless method overrides --- .../OpenPGPDetachedSignatureGenerator.java | 60 ----------------- .../openpgp/api/OpenPGPMessageGenerator.java | 65 ------------------- 2 files changed, 125 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java index 759d4babd6..910e8ce316 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPDetachedSignatureGenerator.java @@ -3,7 +3,6 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import java.io.IOException; import java.io.InputStream; @@ -56,65 +55,6 @@ public OpenPGPDetachedSignatureGenerator(OpenPGPImplementation implementation, O super(implementation, policy); } - public OpenPGPDetachedSignatureGenerator addKeyPassphrase(char[] passphrase) - { - return super.addKeyPassphrase(passphrase); - } - - public OpenPGPDetachedSignatureGenerator addSigningKey( - OpenPGPKey key) - throws InvalidSigningKeyException - { - return super.addSigningKey(key); - } - - /** - * Add an {@link OpenPGPKey} as signing key. - * If no (sub-)key in the signing key is capable of creating signatures, or if the key is expired or revoked, - * this method will throw an {@link InvalidSigningKeyException}. - * Otherwise, all capable signing subkeys will be used to create detached signatures. - * - * @param key OpenPGP key - * @param passphraseProvider provides the passphrase to unlock the signing key - * @return this - * - * @throws InvalidSigningKeyException if the OpenPGP key does not contain a usable signing subkey - * @throws InvalidSigningKeyException if the key cannot sign - */ - public OpenPGPDetachedSignatureGenerator addSigningKey( - OpenPGPKey key, - KeyPassphraseProvider passphraseProvider) - throws InvalidSigningKeyException - { - return super.addSigningKey(key, passphraseProvider); - } - - public OpenPGPDetachedSignatureGenerator addSigningKey( - OpenPGPKey.OpenPGPSecretKey signingKey, - char[] passphrase, - SignatureParameters.Callback signatureCallback) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, passphrase, signatureCallback); - } - - public OpenPGPDetachedSignatureGenerator addSigningKey( - OpenPGPKey.OpenPGPSecretKey signingKey, - KeyPassphraseProvider passphraseProvider, - SignatureParameters.Callback signatureCallback) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, passphraseProvider, signatureCallback); - } - - public OpenPGPDetachedSignatureGenerator addSigningKey( - OpenPGPKey signingKey, - SignatureParameters.Callback signatureCallback) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, defaultKeyPassphraseProvider, signatureCallback); - } - /** * Pass in an {@link InputStream} containing the data that shall be signed and return a list of detached * signatures. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java index ea6109b9be..123e4b078c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageGenerator.java @@ -16,7 +16,6 @@ import org.bouncycastle.openpgp.PGPPadding; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.api.exception.InvalidEncryptionKeyException; -import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; @@ -148,70 +147,6 @@ public OpenPGPMessageGenerator addEncryptionPassphrase(char[] passphrase) return this; } - /** - * Sign the message using a secret signing key. - * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by - * calling {@link #setSigningKeySelector(SubkeySelector)}. - * - * @param signingKey OpenPGP key - * @return this - * @throws InvalidSigningKeyException if the key is not capable of signing messages - */ - public OpenPGPMessageGenerator addSigningKey(OpenPGPKey signingKey) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey); - } - - /** - * Sign the message using a secret signing key. - * The signing subkey(s) will be selected by the default {@link SubkeySelector} which can be replaced by - * calling {@link #setSigningKeySelector(SubkeySelector)}. - * - * @param signingKey OpenPGP key - * @param signingKeyDecryptorProvider provider for decryptors to unlock the signing (sub-)keys. - * @return this - * @throws InvalidSigningKeyException if the key is not capable of signing messages - */ - public OpenPGPMessageGenerator addSigningKey( - OpenPGPKey signingKey, - KeyPassphraseProvider signingKeyDecryptorProvider) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, signingKeyDecryptorProvider); - } - - public OpenPGPMessageGenerator addSigningKey( - OpenPGPKey.OpenPGPSecretKey signingKey, - char[] passphrase, - SignatureParameters.Callback signatureCallback) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, passphrase, signatureCallback); - } - - /** - * Sign the message using a signing-capable (sub-)key. - * If the signing key is protected with a passphrase, the given {@link KeyPassphraseProvider} can be - * used to unlock the key. - * The signature can be customized by providing a {@link SignatureParameters.Callback}, which can change - * the signature type, creation time and signature subpackets. - * - * @param signingKey signing-capable subkey - * @param signingKeyPassphraseProvider provider for key passphrases - * @param signatureParameterCallback callback to modify the signature - * @return this - * @throws InvalidSigningKeyException if the key cannot be used for signing - */ - public OpenPGPMessageGenerator addSigningKey( - OpenPGPKey.OpenPGPSecretKey signingKey, - KeyPassphraseProvider signingKeyPassphraseProvider, - SignatureParameters.Callback signatureParameterCallback) - throws InvalidSigningKeyException - { - return super.addSigningKey(signingKey, signingKeyPassphraseProvider, signatureParameterCallback); - } - /** * Specify, whether the output OpenPGP message will be ASCII armored or not. * From a0fc6ceb9ba9112d1725004817da1fd6af289101 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 18:53:19 +0100 Subject: [PATCH 107/154] Add javadoc to OpenPGPPolicy --- .../openpgp/api/OpenPGPPolicy.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java index 1e3b199a47..c1307bb516 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPPolicy.java @@ -10,33 +10,82 @@ import java.util.HashSet; import java.util.Set; +/** + * Policy for OpenPGP algorithms and features. + */ public interface OpenPGPPolicy { + /** + * Return true, if the given {@link PGPPublicKey} is an acceptable signing key. + * Note: Although signing requires a secret key, we perform checks on the public part for consistency. + * + * @param key key + * @return true if acceptable signing key + */ default boolean isAcceptableSigningKey(PGPPublicKey key) { return isAcceptablePublicKey(key); } + /** + * Return true, if the given {@link PGPPublicKey} is an acceptable signature verification key. + * Note: The asymmetry between this and {@link #isAcceptableSigningKey(PGPPublicKey)} is useful + * to prevent creation of signatures using a legacy key, while still allowing verification of + * signatures made using the same key. + * + * @param key key + * @return true if acceptable verification key + */ default boolean isAcceptableVerificationKey(PGPPublicKey key) { return isAcceptablePublicKey(key); } + /** + * Return true, if the given {@link PGPPublicKey} is acceptable for encrypting messages. + * + * @param key key + * @return true if acceptable encryption key + */ default boolean isAcceptableEncryptionKey(PGPPublicKey key) { return isAcceptablePublicKey(key); } + /** + * Return true, if the given {@link PGPPublicKey} is acceptable for decrypting messages. + * Note: Although decryption requires a secret key, we perform checks on the public part for consistency. + * The asymmetry between this and {@link #isAcceptableEncryptionKey(PGPPublicKey)} is useful + * to prevent creation of new encrypted messages using a legacy key, while still allowing decryption + * of existing messages using the same key. + * + * @param key key + * @return true if acceptable decryption key + */ default boolean isAcceptableDecryptionKey(PGPPublicKey key) { return isAcceptablePublicKey(key); } + /** + * Return true, if the given {@link PGPPublicKey} is acceptable. + * + * @param key key + * @return true if acceptable key + */ default boolean isAcceptablePublicKey(PGPPublicKey key) { return isAcceptablePublicKeyStrength(key.getAlgorithm(), key.getBitStrength()); } + /** + * Return true, if the given {@link PGPSignature} is acceptable (uses acceptable hash algorithm, + * does not contain unknown critical notations or subpackets). + * Note: A signature being acceptable does NOT mean that it is correct or valid. + * + * @param signature signature + * @return true if acceptable + */ default boolean isAcceptableSignature(PGPSignature signature) { return hasAcceptableSignatureHashAlgorithm(signature) && @@ -44,6 +93,12 @@ default boolean isAcceptableSignature(PGPSignature signature) hasNoCriticalUnknownSubpackets(signature); } + /** + * Return true, if the given {@link PGPSignature} was made using an acceptable signature hash algorithm. + * + * @param signature signature + * @return true if hash algorithm is acceptable + */ default boolean hasAcceptableSignatureHashAlgorithm(PGPSignature signature) { switch (signature.getSignatureType()) @@ -69,21 +124,44 @@ default boolean hasAcceptableSignatureHashAlgorithm(PGPSignature signature) } } + /** + * Return true, if the {@link PGPSignature} uses an acceptable data/document signature hash algorithm. + * + * @param signature data / document signature + * @return true if hash algorithm is acceptable + */ default boolean hasAcceptableDocumentSignatureHashAlgorithm(PGPSignature signature) { return isAcceptableDocumentSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); } + /** + * Return true, if the {@link PGPSignature} uses an acceptable revocation signature hash algorithm. + * + * @param signature revocation signature + * @return true if hash algorithm is acceptable + */ default boolean hasAcceptableRevocationSignatureHashAlgorithm(PGPSignature signature) { return isAcceptableRevocationSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); } + /** + * Return true, if the {@link PGPSignature} uses an acceptable certification signature hash algorithm. + * + * @param signature certification signature + * @return true if hash algorithm is acceptable + */ default boolean hasAcceptableCertificationSignatureHashAlgorithm(PGPSignature signature) { return isAcceptableCertificationSignatureHashAlgorithm(signature.getHashAlgorithm(), signature.getCreationTime()); } + /** + * Return true, if the hashed subpacket area of the signature does NOT contain unknown critical notations. + * @param signature signature + * @return true if signature is free from unknown critical notations + */ default boolean hasNoCriticalUnknownNotations(PGPSignature signature) { PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); @@ -105,6 +183,11 @@ default boolean hasNoCriticalUnknownNotations(PGPSignature signature) return true; } + /** + * Return true, if the hashed subpacket area of the signature does NOT contain unknown critical subpackets. + * @param signature signature + * @return true if signature is free from unknown critical subpackets + */ default boolean hasNoCriticalUnknownSubpackets(PGPSignature signature) { PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); @@ -128,29 +211,102 @@ default boolean hasNoCriticalUnknownSubpackets(PGPSignature signature) return true; } + /** + * Return true, if the given signature subpacket ID is known by the implementation. + * Note: This method is only called for subpackets not recognized by + * {@link org.bouncycastle.bcpg.SignatureSubpacketInputStream}. + * + * @param signatureSubpacketTag signature subpacket ID + * @return true if subpacket tag is known + */ default boolean isKnownSignatureSubpacket(int signatureSubpacketTag) { + // Overwrite this, allowing custom critical signature subpackets return false; } + /** + * Return true, if the given hash algorithm is - at signature creation time - an acceptable document signature + * hash algorithm. + * + * @param hashAlgorithmId hash algorithm ID + * @param signatureCreationTime optional signature creation time + * @return true if hash algorithm is acceptable at creation time + */ boolean isAcceptableDocumentSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + /** + * Return true, if the given hash algorithm is - at signature creation time - an acceptable revocation signature + * hash algorithm. + * + * @param hashAlgorithmId hash algorithm ID + * @param signatureCreationTime optional signature creation time + * @return true if hash algorithm is acceptable at creation time + */ boolean isAcceptableRevocationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + /** + * Return true, if the given hash algorithm is - at signature creation time - an acceptable certification signature + * hash algorithm. + * + * @param hashAlgorithmId hash algorithm ID + * @param signatureCreationTime optional signature creation time + * @return true if hash algorithm is acceptable at creation time + */ boolean isAcceptableCertificationSignatureHashAlgorithm(int hashAlgorithmId, Date signatureCreationTime); + /** + * Return the default certification signature hash algorithm ID. + * This is used as fallback, if negotiation of a commonly supported hash algorithm fails. + * + * @return default certification signature hash algorithm ID + */ int getDefaultCertificationSignatureHashAlgorithm(); + /** + * Return the default document signature hash algorithm ID. + * This is used as fallback, if negotiation of a commonly supported hash algorithm fails. + * + * @return default document signature hash algorithm ID + */ int getDefaultDocumentSignatureHashAlgorithm(); + /** + * Return true, if the given symmetric-key algorithm is acceptable. + * + * @param symmetricKeyAlgorithmId symmetric-key algorithm + * @return true if symmetric-key algorithm is acceptable + */ boolean isAcceptableSymmetricKeyAlgorithm(int symmetricKeyAlgorithmId); + /** + * Return the default symmetric-key algorithm, which is used as a fallback if symmetric encryption algorithm + * negotiation fails. + * + * @return default symmetric-key algorithm + */ int getDefaultSymmetricKeyAlgorithm(); + /** + * Return true, if the given bitStrength is acceptable for the given public key algorithm ID. + * + * @param publicKeyAlgorithmId ID of a public key algorithm + * @param bitStrength key bit strength + * @return true if strength is acceptable + */ boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitStrength); + /** + * Return the policies {@link OpenPGPNotationRegistry} containing known notation names. + * + * @return notation registry + */ OpenPGPNotationRegistry getNotationRegistry(); + /** + * The {@link OpenPGPNotationRegistry} can be used to register known notations, such that signatures containing + * notation instances of the same name, which are marked as critical do not invalidate the signature. + */ class OpenPGPNotationRegistry { private final Set knownNotations = new HashSet<>(); From 05edc836621a5e3267ec4ef5f21a58f04b1e7bab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 18:59:33 +0100 Subject: [PATCH 108/154] Add javadoc to OpenPGPKeyReader --- .../openpgp/api/OpenPGPKeyReader.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java index 4a731aef4e..bdd03a5d56 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -13,6 +13,9 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; +/** + * Reader for {@link OpenPGPKey OpenPGPKeys} or {@link OpenPGPCertificate OpenPGPCertificates}. + */ public class OpenPGPKeyReader { private final OpenPGPImplementation implementation; @@ -34,6 +37,13 @@ public OpenPGPKeyReader(OpenPGPImplementation implementation, OpenPGPPolicy poli this.policy = policy; } + /** + * Parse a single {@link OpenPGPCertificate} from an ASCII armored string. + * + * @param armored ASCII armored string + * @return parsed certificate + * @throws IOException if the parsed object is a secret key or if the cert cannot be parsed + */ public OpenPGPCertificate parseCertificate(String armored) throws IOException { @@ -45,6 +55,13 @@ public OpenPGPCertificate parseCertificate(String armored) return certificate; } + /** + * Parse a single {@link OpenPGPCertificate} from an {@link InputStream}. + * + * @param inputStream ASCII armored or binary input stream + * @return parsed certificate + * @throws IOException if the parsed object is a secret key or if the cert cannot be parsed + */ public OpenPGPCertificate parseCertificate(InputStream inputStream) throws IOException { @@ -56,6 +73,13 @@ public OpenPGPCertificate parseCertificate(InputStream inputStream) return certificate; } + /** + * Parse a single {@link OpenPGPCertificate} from bytes. + * + * @param bytes ASCII armored or binary bytes + * @return parsed certificate + * @throws IOException if the parsed object is a secret key or if the cert cannot be parsed + */ public OpenPGPCertificate parseCertificate(byte[] bytes) throws IOException { @@ -67,18 +91,39 @@ public OpenPGPCertificate parseCertificate(byte[] bytes) return certificate; } + /** + * Parse a single {@link OpenPGPCertificate} or {@link OpenPGPKey} from an ASCII armored string. + * + * @param armored ASCII armored string + * @return parsed certificate or key + * @throws IOException if the key or certificate cannot be parsed + */ public OpenPGPCertificate parseCertificateOrKey(String armored) throws IOException { return parseCertificateOrKey(armored.getBytes(StandardCharsets.UTF_8)); } + /** + * Parse a single {@link OpenPGPCertificate} or {@link OpenPGPKey} from an {@link InputStream}. + * + * @param inputStream input stream containing the ASCII armored or binary key or certificate + * @return parsed certificate or key + * @throws IOException if the key or certificate cannot be parsed + */ public OpenPGPCertificate parseCertificateOrKey(InputStream inputStream) throws IOException { return parseCertificateOrKey(Streams.readAll(inputStream)); } + /** + * Parse a single {@link OpenPGPCertificate} or {@link OpenPGPKey} from bytes. + * + * @param bytes ASCII armored or binary key or certificate + * @return parsed certificate or key + * @throws IOException if the key or certificate cannot be parsed + */ public OpenPGPCertificate parseCertificateOrKey(byte[] bytes) throws IOException { @@ -109,18 +154,39 @@ else if (object instanceof PGPPublicKeyRing) } } + /** + * Parse an {@link OpenPGPKey} from an ASCII armored string. + * + * @param armored ASCII armored string + * @return parsed key + * @throws IOException if the key cannot be parsed. + */ public OpenPGPKey parseKey(String armored) throws IOException { return parseKey(armored.getBytes(StandardCharsets.UTF_8)); } + /** + * Parse an {@link OpenPGPKey} from an {@link InputStream} + * + * @param inputStream containing the ASCII armored or binary key + * @return parsed key + * @throws IOException if the key cannot be parsed. + */ public OpenPGPKey parseKey(InputStream inputStream) throws IOException { return parseKey(Streams.readAll(inputStream)); } + /** + * Parse an {@link OpenPGPKey} from bytes. + * + * @param bytes ASCII armored or binary key + * @return parsed key + * @throws IOException if the key cannot be parsed. + */ public OpenPGPKey parseKey(byte[] bytes) throws IOException { From 06b3de810d69a0f57deadc1164d4b982631a78b6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jan 2025 19:15:15 +0100 Subject: [PATCH 109/154] Add javadoc to OpenPGPKeyEditor --- .../openpgp/api/OpenPGPKeyEditor.java | 103 ++++++++++++++---- .../api/test/OpenPGPKeyEditorTest.java | 4 +- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index fee5bf6fd7..060b47f245 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -10,9 +10,10 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.api.exception.OpenPGPKeyException; public class OpenPGPKeyEditor - extends AbstractOpenPGPKeySignatureGenerator + extends AbstractOpenPGPKeySignatureGenerator { private final OpenPGPImplementation implementation; @@ -36,6 +37,15 @@ public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation, Op this.policy = policy; } + /** + * Add a direct-key signature to the primary key. + * The contents of the direct-key signature can be modified by providing a {@link SignatureParameters.Callback}. + * + * @param primaryKeyPassphrase passphrase of the primary key + * @param callback callback to modify the direct-key signature contents + * @return this + * @throws PGPException if the key cannot be modified + */ public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, SignatureParameters.Callback callback) throws PGPException @@ -81,12 +91,31 @@ public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, return this; } + /** + * Add a user-id to the primary key. + * If the key already contains the given user-id, a new certification signature will be added to the user-id. + * @param userId user-id + * @param primaryKeyPassphrase passphrase to unlock the primary key + * @return this + * @throws PGPException if the key cannot be modified + */ public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) throws PGPException { return addUserId(userId, primaryKeyPassphrase, null); } + /** + * Add a user-id to the primary key, modifying the contents of the certification signature using the given + * {@link SignatureParameters.Callback}. + * If the key already contains the given user-id, a new certification signature will be added to the user-id. + * + * @param userId user-id + * @param primaryKeyPassphrase passphrase to unlock the primary key + * @param callback callback to modify the certification signature contents + * @return this + * @throws PGPException if the key cannot be modified + */ public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase, SignatureParameters.Callback callback) @@ -138,43 +167,62 @@ public OpenPGPKeyEditor addUserId(String userId, return this; } - public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey subkey, + /** + * Change the passphrase of the given component key. + * + * @param componentKey component key, whose passphrase shall be changed + * @param oldPassphrase old passphrase (or null) + * @param newPassphrase new passphrase (or null) + * @param useAEAD whether to use AEAD + * @return this + * @throws OpenPGPKeyException if the secret component of the component key is missing + * @throws PGPException if the key passphrase cannot be changed + */ + public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey componentKey, char[] oldPassphrase, char[] newPassphrase, boolean useAEAD) + throws OpenPGPKeyException, PGPException { - OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(subkey); + OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(componentKey); if (secretKey == null) { - throw new IllegalArgumentException("Subkey is not part of the key."); + throw new OpenPGPKeyException(componentKey, "Secret component key " + componentKey.getKeyIdentifier() + + " is missing from the key."); } - try - { - PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); - PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( - secretKey.getPGPSecretKey(), - implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), - implementation.pbeSecretKeyEncryptorFactory(useAEAD) - .build( - newPassphrase, - secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), - implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); - secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); - key = new OpenPGPKey(secretKeys, implementation, policy); - } - catch (PGPException e) - { - throw new RuntimeException(e); - } + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); + PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( + secretKey.getPGPSecretKey(), + implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), + implementation.pbeSecretKeyEncryptorFactory(useAEAD) + .build( + newPassphrase, + secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), + implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); + secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); + key = new OpenPGPKey(secretKeys, implementation, policy); + return this; } + /** + * Return the modified {@link OpenPGPKey}. + * @return modified key + */ public OpenPGPKey done() { return key; } + /** + * Revoke the given {@link OpenPGPCertificate.OpenPGPUserId}. + * + * @param userId user-id to be revoked + * @param primaryKeyPassphrase passphrase of the primary key + * @return this + * @throws PGPException if the key cannot be modified + */ public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, char[] primaryKeyPassphrase) throws PGPException @@ -182,6 +230,17 @@ public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, return revokeUserId(userId, primaryKeyPassphrase, null); } + + /** + * Revoke the given {@link OpenPGPCertificate.OpenPGPUserId}, allowing modification of the revocation signature + * using the given {@link SignatureParameters.Callback}. + * + * @param userId user-id to revoke + * @param primaryKeyPassphrase passphrase to unlock the primary key with + * @param callback callback to modify the revocation signature contents + * @return this + * @throws PGPException if the key cannot be modified + */ public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, char[] primaryKeyPassphrase, SignatureParameters.Callback callback) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index 18a588aa2d..8e4e3868ce 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -157,7 +157,7 @@ public SignatureParameters apply(SignatureParameters parameters) } private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) - throws IOException + throws IOException, PGPException { OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); isFalse(key.getPrimarySecretKey().isLocked()); @@ -173,7 +173,7 @@ private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) } private void changePassphraseUnprotectedToAEADTest(OpenPGPApi api) - throws IOException + throws IOException, PGPException { OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); isFalse("Expect key to be unprotected", key.getPrimarySecretKey().isLocked()); From c9bb16bc2023e2d724ac2a0ffa29d70b34836297 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 24 Jan 2025 14:44:47 +0100 Subject: [PATCH 110/154] Implement further functionality of OpenPGPKeyEditor --- .../bouncycastle/openpgp/api/OpenPGPApi.java | 28 +- .../openpgp/api/OpenPGPCertificate.java | 10 + .../bouncycastle/openpgp/api/OpenPGPKey.java | 10 + .../openpgp/api/OpenPGPKeyEditor.java | 496 ++++++++++++++---- .../openpgp/api/SignatureParameters.java | 16 + .../api/test/OpenPGPKeyEditorTest.java | 113 +++- .../api/test/OpenPGPV6KeyGeneratorTest.java | 2 +- 7 files changed, 564 insertions(+), 111 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java index fed7b7ef95..7eea930758 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPApi.java @@ -193,15 +193,39 @@ public OpenPGPDetachedSignatureProcessor verifyDetachedSignature() return new OpenPGPDetachedSignatureProcessor(implementation, policy); } + public OpenPGPKeyEditor editKey(OpenPGPKey key) + throws PGPException + { + return editKey(key, (char[]) null); + } + + public OpenPGPKeyEditor editKey(OpenPGPKey key, char[] primaryKeyPassphrase) + throws PGPException + { + return new OpenPGPKeyEditor( + key, + new KeyPassphraseProvider() + { + @Override + public char[] getKeyPassword(OpenPGPKey.OpenPGPSecretKey key) + { + return primaryKeyPassphrase; + } + }, + implementation, + policy); + } + /** * Modify an {@link OpenPGPKey}. * * @param key OpenPGP key * @return key editor */ - public OpenPGPKeyEditor editKey(OpenPGPKey key) + public OpenPGPKeyEditor editKey(OpenPGPKey key, KeyPassphraseProvider primaryKeyPassphraseProvider) + throws PGPException { - return new OpenPGPKeyEditor(key, implementation, policy); + return new OpenPGPKeyEditor(key, primaryKeyPassphraseProvider, implementation, policy); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 79c437f903..a9b1e7bde2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -713,6 +713,16 @@ public OpenPGPUserId getUserId(String userId) return null; } + public Date getExpirationTime() + { + return getExpirationTime(new Date()); + } + + public Date getExpirationTime(Date evaluationTime) + { + return getPrimaryKey().getKeyExpirationDateAt(evaluationTime); + } + /** * Component on an OpenPGP certificate. * Components can either be {@link OpenPGPComponentKey keys} or {@link OpenPGPIdentityComponent identities}. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 6cb60aba7f..5119d7eec6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -251,6 +251,16 @@ public boolean isLocked() return getPGPSecretKey().getS2KUsage() != SecretKeyPacket.USAGE_NONE; } + public PGPPrivateKey unlock(KeyPassphraseProvider passphraseProvider) + throws PGPException + { + if (!isLocked()) + { + return unlock((char[]) null); + } + return unlock(passphraseProvider.getKeyPassword(this)); + } + /** * Access the {@link PGPPrivateKey} by unlocking the potentially locked secret key using the provided * passphrase. Note: If the key is not locked, it is sufficient to pass null as passphrase. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index 060b47f245..69a87184ce 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -1,7 +1,11 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyUtils; +import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPKeyValidationException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -11,6 +15,10 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.OpenPGPKeyException; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; + +import java.io.IOException; +import java.util.Date; public class OpenPGPKeyEditor extends AbstractOpenPGPKeySignatureGenerator @@ -19,47 +27,46 @@ public class OpenPGPKeyEditor private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; private OpenPGPKey key; + private final PGPPrivateKey privatePrimaryKey; - public OpenPGPKeyEditor(OpenPGPKey key) + public OpenPGPKeyEditor(OpenPGPKey key, KeyPassphraseProvider passphraseProvider) + throws PGPException { - this(key, key.implementation); + this(key, passphraseProvider, key.implementation); } - public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation) + public OpenPGPKeyEditor(OpenPGPKey key, + KeyPassphraseProvider passphraseProvider, + OpenPGPImplementation implementation) + throws PGPException { - this(key, implementation, implementation.policy()); + this(key, passphraseProvider, implementation, implementation.policy()); } - public OpenPGPKeyEditor(OpenPGPKey key, OpenPGPImplementation implementation, OpenPGPPolicy policy) + public OpenPGPKeyEditor(OpenPGPKey key, + KeyPassphraseProvider passphraseProvider, + OpenPGPImplementation implementation, + OpenPGPPolicy policy) + throws PGPException { this.key = key; + this.privatePrimaryKey = key.getPrimarySecretKey().unlock(passphraseProvider); this.implementation = implementation; this.policy = policy; } - /** - * Add a direct-key signature to the primary key. - * The contents of the direct-key signature can be modified by providing a {@link SignatureParameters.Callback}. - * - * @param primaryKeyPassphrase passphrase of the primary key - * @param callback callback to modify the direct-key signature contents - * @return this - * @throws PGPException if the key cannot be modified - */ - public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, - SignatureParameters.Callback callback) + public OpenPGPKeyEditor addDirectKeySignature(SignatureParameters.Callback signatureCallback) throws PGPException { SignatureParameters parameters = SignatureParameters.directKeySignature(policy); - if (callback != null) + if (signatureCallback != null) { - parameters = callback.apply(parameters); + parameters = signatureCallback.apply(parameters); } if (parameters != null) { PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( implementation.pgpContentSignerBuilder( @@ -94,15 +101,15 @@ public OpenPGPKeyEditor addDirectKeySignature(char[] primaryKeyPassphrase, /** * Add a user-id to the primary key. * If the key already contains the given user-id, a new certification signature will be added to the user-id. - * @param userId user-id - * @param primaryKeyPassphrase passphrase to unlock the primary key + * + * @param userId user-id * @return this * @throws PGPException if the key cannot be modified */ - public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) + public OpenPGPKeyEditor addUserId(String userId) throws PGPException { - return addUserId(userId, primaryKeyPassphrase, null); + return addUserId(userId, null); } /** @@ -111,14 +118,12 @@ public OpenPGPKeyEditor addUserId(String userId, char[] primaryKeyPassphrase) * If the key already contains the given user-id, a new certification signature will be added to the user-id. * * @param userId user-id - * @param primaryKeyPassphrase passphrase to unlock the primary key - * @param callback callback to modify the certification signature contents + * @param signatureCallback callback to modify the certification signature contents * @return this * @throws PGPException if the key cannot be modified */ public OpenPGPKeyEditor addUserId(String userId, - char[] primaryKeyPassphrase, - SignatureParameters.Callback callback) + SignatureParameters.Callback signatureCallback) throws PGPException { if (userId == null || userId.trim().isEmpty()) @@ -127,15 +132,14 @@ public OpenPGPKeyEditor addUserId(String userId, } SignatureParameters parameters = SignatureParameters.certification(policy); - if (callback != null) + if (signatureCallback != null) { - parameters = callback.apply(parameters); + parameters = signatureCallback.apply(parameters); } if (parameters != null) { PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( implementation.pgpContentSignerBuilder( @@ -168,127 +172,417 @@ public OpenPGPKeyEditor addUserId(String userId, } /** - * Change the passphrase of the given component key. + * Revoke the given {@link OpenPGPCertificate.OpenPGPIdentityComponent}. * - * @param componentKey component key, whose passphrase shall be changed - * @param oldPassphrase old passphrase (or null) - * @param newPassphrase new passphrase (or null) - * @param useAEAD whether to use AEAD + * @param identity user-id to be revoked * @return this - * @throws OpenPGPKeyException if the secret component of the component key is missing - * @throws PGPException if the key passphrase cannot be changed + * @throws PGPException if the key cannot be modified */ - public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey componentKey, - char[] oldPassphrase, - char[] newPassphrase, - boolean useAEAD) - throws OpenPGPKeyException, PGPException + public OpenPGPKeyEditor revokeIdentity(OpenPGPCertificate.OpenPGPIdentityComponent identity) + throws PGPException { - OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(componentKey); - if (secretKey == null) + return revokeIdentity(identity, null); + } + + /** + * Revoke the given {@link OpenPGPCertificate.OpenPGPUserId}, allowing modification of the revocation signature + * using the given {@link SignatureParameters.Callback}. + * + * @param identity user-id to revoke + * @param signatureCallback callback to modify the revocation signature contents + * @return this + * @throws PGPException if the key cannot be modified + */ + public OpenPGPKeyEditor revokeIdentity(OpenPGPCertificate.OpenPGPIdentityComponent identity, + SignatureParameters.Callback signatureCallback) + throws PGPException + { + if (!key.getComponents().contains(identity)) { - throw new OpenPGPKeyException(componentKey, "Secret component key " + componentKey.getKeyIdentifier() + - " is missing from the key."); + throw new IllegalArgumentException("UserID or UserAttribute is not part of the certificate."); } - PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); - PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( - secretKey.getPGPSecretKey(), - implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), - implementation.pbeSecretKeyEncryptorFactory(useAEAD) - .build( - newPassphrase, - secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), - implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); - secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); - key = new OpenPGPKey(secretKeys, implementation, policy); + SignatureParameters parameters = SignatureParameters.certificationRevocation(policy); + if (signatureCallback != null) + { + parameters = signatureCallback.apply(parameters); + } + + if (parameters != null) + { + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + + PGPSignatureGenerator idSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + idSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + idSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + idSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPPublicKey pubKey; + if (identity instanceof OpenPGPCertificate.OpenPGPUserId) + { + OpenPGPCertificate.OpenPGPUserId userId = (OpenPGPCertificate.OpenPGPUserId) identity; + PGPSignature uidSig = idSigGen.generateCertification(userId.getUserId(), publicPrimaryKey); + pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId.getUserId(), uidSig); + } + else + { + OpenPGPCertificate.OpenPGPUserAttribute userAttribute = (OpenPGPCertificate.OpenPGPUserAttribute) identity; + PGPSignature uattrSig = idSigGen.generateCertification(userAttribute.getUserAttribute(), publicPrimaryKey); + pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userAttribute.getUserAttribute(), uattrSig); + } + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } return this; } - /** - * Return the modified {@link OpenPGPKey}. - * @return modified key - */ - public OpenPGPKey done() + public OpenPGPKeyEditor addEncryptionSubkey() + throws PGPException { - return key; + return addEncryptionSubkey(KeyPairGeneratorCallback.encryptionKey()); } - /** - * Revoke the given {@link OpenPGPCertificate.OpenPGPUserId}. - * - * @param userId user-id to be revoked - * @param primaryKeyPassphrase passphrase of the primary key - * @return this - * @throws PGPException if the key cannot be modified - */ - public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, - char[] primaryKeyPassphrase) + public OpenPGPKeyEditor addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback) throws PGPException { - return revokeUserId(userId, primaryKeyPassphrase, null); + return addEncryptionSubkey(keyGenCallback, key.getPrimaryKey().getVersion(), new Date()); } + public OpenPGPKeyEditor addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, + int version, + Date creationTime) + throws PGPException + { + PGPKeyPairGenerator kpGen = implementation.pgpKeyPairGeneratorProvider() + .get(version, creationTime); + return addEncryptionSubkey(keyGenCallback.generateFrom(kpGen), null); + } - /** - * Revoke the given {@link OpenPGPCertificate.OpenPGPUserId}, allowing modification of the revocation signature - * using the given {@link SignatureParameters.Callback}. - * - * @param userId user-id to revoke - * @param primaryKeyPassphrase passphrase to unlock the primary key with - * @param callback callback to modify the revocation signature contents - * @return this - * @throws PGPException if the key cannot be modified - */ - public OpenPGPKeyEditor revokeUserId(OpenPGPCertificate.OpenPGPUserId userId, - char[] primaryKeyPassphrase, - SignatureParameters.Callback callback) + public OpenPGPKeyEditor addEncryptionSubkey(PGPKeyPair encryptionSubkey, + SignatureParameters.Callback bindingSigCallback) throws PGPException { - if (!key.getComponents().contains(userId)) + if (!PublicKeyUtils.isEncryptionAlgorithm(encryptionSubkey.getPublicKey().getAlgorithm())) { - throw new IllegalArgumentException("UserID is not part of the certificate."); + throw new PGPKeyValidationException("Provided subkey is not encryption-capable."); } - SignatureParameters parameters = SignatureParameters.certificationRevocation(policy); - if (callback != null) + SignatureParameters parameters = SignatureParameters.subkeyBinding(policy); + + if (bindingSigCallback != null) { - parameters = callback.apply(parameters); + parameters = bindingSigCallback.apply(parameters); } if (parameters != null) { PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); - PGPPrivateKey privatePrimaryKey = key.getPrimarySecretKey().unlock(primaryKeyPassphrase); - PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + PGPSignatureGenerator subKeySigGen = new PGPSignatureGenerator( implementation.pgpContentSignerBuilder( publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + subKeySigGen.init(parameters.getSignatureType(), privatePrimaryKey); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets.setKeyFlags(KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); - uidSigGen.setHashedSubpackets(hashedSubpackets.generate()); + subKeySigGen.setHashedSubpackets(hashedSubpackets.generate()); // Unhashed subpackets PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); - uidSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + subKeySigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); // Inject signature into the certificate - PGPSignature uidSig = uidSigGen.generateCertification(userId.getUserId(), publicPrimaryKey); - PGPPublicKey pubKey = PGPPublicKey.addCertification(publicPrimaryKey, userId.getUserId(), uidSig); - PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), pubKey); - PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + PGPPublicKey publicSubKey = encryptionSubkey.getPublicKey(); + PGPSignature subKeySig = subKeySigGen.generateCertification(publicPrimaryKey, publicSubKey); + publicSubKey = PGPPublicKey.addCertification(publicSubKey, subKeySig); + PGPSecretKey secretSubkey = new PGPSecretKey( + encryptionSubkey.getPrivateKey(), + publicSubKey, + implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1), + false, + null); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.insertSecretKey(key.getPGPKeyRing(), secretSubkey); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } + return this; + } + + public OpenPGPKeyEditor addSigningSubkey() + throws PGPException + { + return addSigningSubkey(KeyPairGeneratorCallback.signingKey()); + } + + public OpenPGPKeyEditor addSigningSubkey(KeyPairGeneratorCallback keyGenCallback) + throws PGPException + { + return addSigningSubkey(keyGenCallback, key.getPrimaryKey().getVersion(), new Date()); + } + + public OpenPGPKeyEditor addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, + int version, + Date creationTime) + throws PGPException + { + PGPKeyPairGenerator kpGen = implementation.pgpKeyPairGeneratorProvider() + .get(version, creationTime); + return addSigningSubkey(keyGenCallback.generateFrom(kpGen), null, null); + } + public OpenPGPKeyEditor addSigningSubkey(PGPKeyPair signingSubkey, + SignatureParameters.Callback bindingSigCallback, + SignatureParameters.Callback backSigCallback) + throws PGPException + { + if (!PublicKeyUtils.isSigningAlgorithm(signingSubkey.getPublicKey().getAlgorithm())) + { + throw new PGPKeyValidationException("Provided subkey is not signing-capable."); + } + + SignatureParameters backSigParameters = SignatureParameters.primaryKeyBinding(policy); + if (backSigCallback != null) + { + backSigParameters = backSigCallback.apply(backSigParameters); + } + + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + + PGPSignature backSig = null; + if (backSigParameters != null) + { + PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder(signingSubkey.getPublicKey().getAlgorithm(), + backSigParameters.getSignatureHashAlgorithmId()), + signingSubkey.getPublicKey()); + backSigGen.init(backSigParameters.getSignatureType(), signingSubkey.getPrivateKey()); + + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, signingSubkey.getPublicKey()); + hashedSubpackets = backSigParameters.applyToHashedSubpackets(hashedSubpackets); + backSigGen.setHashedSubpackets(hashedSubpackets.generate()); + + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = backSigParameters.applyToUnhashedSubpackets(unhashedSubpackets); + backSigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + backSig = backSigGen.generateCertification(publicPrimaryKey, signingSubkey.getPublicKey()); + } + + SignatureParameters parameters = SignatureParameters.subkeyBinding(policy); + if (bindingSigCallback != null) + { + parameters = bindingSigCallback.apply(parameters); + } + + if (parameters != null) + { + PGPSignatureGenerator subKeySigGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + subKeySigGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets.setKeyFlags(KeyFlags.SIGN_DATA); + if (backSig != null) + { + try + { + hashedSubpackets.addEmbeddedSignature(true, backSig); + } + catch (IOException e) + { + throw new PGPException("Cannot encode embedded back-sig."); + } + } + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + subKeySigGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + subKeySigGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + // Inject signature into the certificate + PGPPublicKey publicSubKey = signingSubkey.getPublicKey(); + PGPSignature subKeySig = subKeySigGen.generateCertification(publicPrimaryKey, publicSubKey); + publicSubKey = PGPPublicKey.addCertification(publicSubKey, subKeySig); + PGPSecretKey secretSubkey = new PGPSecretKey( + signingSubkey.getPrivateKey(), + publicSubKey, + implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1), + false, + null); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.insertSecretKey(key.getPGPKeyRing(), secretSubkey); + + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } + + return this; + } + + public OpenPGPKeyEditor revokeComponentKey(OpenPGPCertificate.OpenPGPComponentKey componentKey) + throws PGPException + { + return revokeComponentKey(componentKey, null); + } + + public OpenPGPKeyEditor revokeComponentKey(OpenPGPCertificate.OpenPGPComponentKey componentKey, + SignatureParameters.Callback revocationSignatureCallback) + throws PGPException + { + boolean contained = key.getKey(componentKey.getKeyIdentifier()) != null; + if (!contained) + { + throw new IllegalArgumentException("Provided component key is not part of the OpenPGP key."); + } + + boolean isSubkeyRevocation = !componentKey.getKeyIdentifier().equals(key.getKeyIdentifier()); + SignatureParameters parameters; + if (isSubkeyRevocation) + { + // Generate Subkey Revocation Signature + parameters = SignatureParameters.subkeyRevocation(policy); + } + else + { + // Generate Key Revocation Signature + parameters = SignatureParameters.keyRevocation(policy); + } + + if (revocationSignatureCallback != null) + { + parameters = revocationSignatureCallback.apply(parameters); + } + + PGPPublicKey publicPrimaryKey = key.getPrimaryKey().getPGPPublicKey(); + PGPSignatureGenerator revGen = new PGPSignatureGenerator( + implementation.pgpContentSignerBuilder( + publicPrimaryKey.getAlgorithm(), + parameters.getSignatureHashAlgorithmId()), + publicPrimaryKey); + revGen.init(parameters.getSignatureType(), privatePrimaryKey); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + hashedSubpackets = parameters.applyToHashedSubpackets(hashedSubpackets); + revGen.setHashedSubpackets(hashedSubpackets.generate()); + + // Unhashed subpackets + PGPSignatureSubpacketGenerator unhashedSubpackets = new PGPSignatureSubpacketGenerator(); + unhashedSubpackets = parameters.applyToUnhashedSubpackets(unhashedSubpackets); + revGen.setUnhashedSubpackets(unhashedSubpackets.generate()); + + if (isSubkeyRevocation) + { + PGPPublicKey revokedKey = componentKey.getPGPPublicKey(); + PGPSignature revocation = revGen.generateCertification(publicPrimaryKey, revokedKey); + revokedKey = PGPPublicKey.addCertification(revokedKey, revocation); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), revokedKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); + this.key = new OpenPGPKey(secretKeyRing, implementation, policy); + } + else + { + PGPSignature revocation = revGen.generateCertification(publicPrimaryKey); + publicPrimaryKey = PGPPublicKey.addCertification(publicPrimaryKey, revocation); + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.insertPublicKey(key.getPGPPublicKeyRing(), publicPrimaryKey); + PGPSecretKeyRing secretKeyRing = PGPSecretKeyRing.replacePublicKeys(key.getPGPKeyRing(), publicKeyRing); this.key = new OpenPGPKey(secretKeyRing, implementation, policy); } + return this; } + + public OpenPGPKeyEditor revokeKey() + throws PGPException + { + return revokeKey(null); + } + + public OpenPGPKeyEditor revokeKey(SignatureParameters.Callback revocationSignatureCallback) + throws PGPException + { + return revokeComponentKey(key.getPrimaryKey(), revocationSignatureCallback); + } + + /** + * Change the passphrase of the given component key. + * + * @param componentKey component key, whose passphrase shall be changed + * @param oldPassphrase old passphrase (or null) + * @param newPassphrase new passphrase (or null) + * @param useAEAD whether to use AEAD + * @return this + * @throws OpenPGPKeyException if the secret component of the component key is missing + * @throws PGPException if the key passphrase cannot be changed + */ + public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey componentKey, + char[] oldPassphrase, + char[] newPassphrase, + boolean useAEAD) + throws OpenPGPKeyException, PGPException + { + OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(componentKey); + if (secretKey == null) + { + throw new OpenPGPKeyException(componentKey, "Secret component key " + componentKey.getKeyIdentifier() + + " is missing from the key."); + } + + PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); + PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( + secretKey.getPGPSecretKey(), + implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), + implementation.pbeSecretKeyEncryptorFactory(useAEAD) + .build( + newPassphrase, + secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), + implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); + secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); + key = new OpenPGPKey(secretKeys, implementation, policy); + + return this; + } + + /** + * Return the modified {@link OpenPGPKey}. + * @return modified key + */ + public OpenPGPKey done() + { + return key; + } + } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index 15c769e73b..667d934404 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -38,6 +38,14 @@ public static SignatureParameters directKeySignature(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + public static SignatureParameters keyRevocation(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.KEY_REVOCATION) + .setSignatureType(PGPSignature.KEY_REVOCATION) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + /** * Create a default signature parameters object for a certification signature. * The default signature type is {@link PGPSignature#POSITIVE_CERTIFICATION}, but can be changed to @@ -73,6 +81,14 @@ public static SignatureParameters subkeyBinding(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + public static SignatureParameters subkeyRevocation(OpenPGPPolicy policy) + { + return new SignatureParameters(PGPSignature.SUBKEY_REVOCATION) + .setSignatureType(PGPSignature.SUBKEY_REVOCATION) + .setSignatureHashAlgorithm(policy.getDefaultCertificationSignatureHashAlgorithm()) + .setSignatureCreationTime(new Date()); + } + /** * Create a default signature parameters object for a primary-key binding (back-sig) signature. * diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index 8e4e3868ce..c70bf057d9 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -47,7 +47,7 @@ private void performTestWith(OpenPGPApi api) addUserIdTest(api); softRevokeUserIdTest(api); hardRevokeUserIdTest(api); - /* + addEncryptionSubkeyTest(api); revokeEncryptionSubkeyTest(api); @@ -57,8 +57,6 @@ private void performTestWith(OpenPGPApi api) extendExpirationTimeTest(api); revokeCertificateTest(api); - - */ changePassphraseUnprotectedToCFBTest(api); changePassphraseUnprotectedToAEADTest(api); } @@ -84,7 +82,7 @@ private void addUserIdTest(OpenPGPApi api) isNull("Expect primary user-id to be null", key.getPrimaryUserId()); key = api.editKey(key) - .addUserId("Alice ", null) + .addUserId("Alice ") .done(); isEquals("Expect the new user-id to be primary now", @@ -104,7 +102,7 @@ private void softRevokeUserIdTest(OpenPGPApi api) isEquals("Alice Lovelace ", userId.getUserId()); key = api.editKey(key) - .revokeUserId(userId, null, new SignatureParameters.Callback() + .revokeIdentity(userId, new SignatureParameters.Callback() { @Override public SignatureParameters apply(SignatureParameters parameters) @@ -127,7 +125,6 @@ public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpa isFalse(key.getPrimaryUserId().isBoundAt(now)); } - private void hardRevokeUserIdTest(OpenPGPApi api) throws IOException, PGPException { @@ -141,7 +138,7 @@ private void hardRevokeUserIdTest(OpenPGPApi api) isEquals("Alice Lovelace ", userId.getUserId()); key = api.editKey(key) - .revokeUserId(userId, null, new SignatureParameters.Callback() + .revokeIdentity(userId, new SignatureParameters.Callback() { @Override public SignatureParameters apply(SignatureParameters parameters) @@ -156,6 +153,108 @@ public SignatureParameters apply(SignatureParameters parameters) isFalse(key.getPrimaryUserId().isBoundAt(now)); } + private void addEncryptionSubkeyTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + isEquals(1, key.getEncryptionKeys().size()); + + key = api.editKey(key) + .addEncryptionSubkey() + .done(); + + isEquals(2, key.getEncryptionKeys().size()); + } + + private void revokeEncryptionSubkeyTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPCertificate.OpenPGPComponentKey encryptionSubkey = key.getEncryptionKeys().get(0); + + key = api.editKey(key) + .revokeComponentKey(encryptionSubkey) + .done(); + + isEquals(0, key.getEncryptionKeys().size()); + } + + private void addSigningSubkeyTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + isEquals(1, key.getSigningKeys().size()); + + key = api.editKey(key) + .addSigningSubkey() + .done(); + + isEquals(2, key.getSigningKeys().size()); + } + + private void revokeSigningSubkeyTest(OpenPGPApi api) + throws PGPException + { + OpenPGPKey key = api.generateKey() + .classicKey(null) + .build(); + isEquals(1, key.getSigningKeys().size()); + + OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0); + key = api.editKey(key) + .revokeComponentKey(signingKey) + .done(); + isEquals(0, key.getSigningKeys().size()); + } + + private void extendExpirationTimeTest(OpenPGPApi api) + throws PGPException + { + Date n0 = new Date((new Date().getTime() / 1000) * 1000); + OpenPGPKey key = api.generateKey(n0, false) + .classicKey(null) + .build(); + isEquals("Default key generation MUST set expiration time of +5years", + key.getExpirationTime().getTime(), n0.getTime() + 5L * 31536000 * 1000); + + Date n1 = new Date(n0.getTime() + 1000); // 1 sec later + + key = api.editKey(key) + .addDirectKeySignature(new SignatureParameters.Callback() + { + @Override + public SignatureParameters apply(SignatureParameters parameters) + { + parameters.setSignatureCreationTime(n1); + parameters.setHashedSubpacketsFunction(new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) { + subpackets.setKeyExpirationTime(8L * 31536000); + return subpackets; + } + }); + return parameters; + } + }) + .done(); + + isEquals("At n1, the expiration time of the key MUST have changed to n0+8years", + key.getExpirationTime(n1).getTime(), n0.getTime() + 8L * 31536000 * 1000); + } + + private void revokeCertificateTest(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + + key = api.editKey(key) + .revokeKey() + .done(); + + isEquals(0, key.getEncryptionKeys().size()); + } + private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) throws IOException, PGPException { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index fd2ccfc585..10ec4e6b6c 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -401,7 +401,7 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) .build("primary-key-passphrase".toCharArray()); OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getEncryptionKeys().get(0); OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0); - key = api.editKey(key) + key = api.editKey(key, "primary-key-passphrase".toCharArray()) .changePassphrase(encryptionKey, "primary-key-passphrase".toCharArray(), "encryption-key-passphrase".toCharArray(), From ce14a0288e802b1bd35144c80d43b95eac27b3f9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 24 Jan 2025 15:08:15 +0100 Subject: [PATCH 111/154] Introduce APITest class for OpenPGPApi-related tests --- .../openpgp/api/test/APITest.java | 32 +++++++++++++++++++ .../test/DoubleBufferedInputStreamTest.java | 1 - .../api/test/OpenPGPCertificateTest.java | 15 ++------- ...OpenPGPDetachedSignatureProcessorTest.java | 16 ++-------- .../api/test/OpenPGPKeyEditorTest.java | 29 ++++------------- .../api/test/OpenPGPMessageGeneratorTest.java | 16 ++-------- .../api/test/OpenPGPMessageProcessorTest.java | 18 ++--------- .../api/test/OpenPGPV4KeyGenerationTest.java | 15 ++------- .../api/test/OpenPGPV6KeyGeneratorTest.java | 18 ++--------- 9 files changed, 52 insertions(+), 108 deletions(-) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/APITest.java diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/APITest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/APITest.java new file mode 100644 index 0000000000..2a597d8e9e --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/APITest.java @@ -0,0 +1,32 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; + +import java.io.IOException; +import java.util.Date; + +public abstract class APITest + extends AbstractPacketTest +{ + @Override + public void performTest() + throws Exception + { + performTestWith(new BcOpenPGPApi()); + performTestWith(new JcaOpenPGPApi(new BouncyCastleProvider())); + } + + public Date currentTimeRounded() + { + Date now = new Date(); + return new Date((now.getTime() / 1000) * 1000); // rounded to seconds + } + + protected abstract void performTestWith(OpenPGPApi api) + throws PGPException, IOException; +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java index 52f3f21512..997687e13f 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/DoubleBufferedInputStreamTest.java @@ -9,7 +9,6 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; public class DoubleBufferedInputStreamTest extends AbstractPacketTest diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java index 41838fdd53..17eb89e6b2 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPCertificateTest.java @@ -6,8 +6,6 @@ import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; @@ -20,8 +18,6 @@ import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.api.util.UTCUtil; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; @@ -32,7 +28,7 @@ import java.util.List; public class OpenPGPCertificateTest - extends AbstractPacketTest + extends APITest { @Override @@ -42,14 +38,7 @@ public String getName() } @Override - public void performTest() - throws Exception - { - performTestsWith(new BcOpenPGPApi()); - performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performTestsWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws IOException, PGPException { testOpenPGPv6Key(api); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java index 9c2000b850..5b9eb4632b 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPDetachedSignatureProcessorTest.java @@ -2,8 +2,6 @@ import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; @@ -16,8 +14,6 @@ import org.bouncycastle.openpgp.api.OpenPGPSignature; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import java.io.ByteArrayInputStream; @@ -28,7 +24,7 @@ import java.util.List; public class OpenPGPDetachedSignatureProcessorTest - extends AbstractPacketTest + extends APITest { @Override public String getName() @@ -36,15 +32,7 @@ public String getName() return "OpenPGPDetachedSignatureProcessorTest"; } - @Override - public void performTest() - throws Exception - { - performWith(new BcOpenPGPApi()); - performWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws PGPException, IOException { createVerifyV4Signature(api); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java index c70bf057d9..c095de2bc7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyEditorTest.java @@ -2,8 +2,6 @@ import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.sig.RevocationReasonTags; -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; @@ -12,14 +10,12 @@ import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import java.io.IOException; import java.util.Date; public class OpenPGPKeyEditorTest - extends AbstractPacketTest + extends APITest { @Override @@ -28,18 +24,7 @@ public String getName() return "OpenPGPKeyEditorTest"; } - @Override - public void performTest() - throws Exception - { - OpenPGPApi api = new BcOpenPGPApi(); - performTestWith(api); - - api = new JcaOpenPGPApi(new BouncyCastleProvider()); - performTestWith(api); - } - - private void performTestWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws PGPException, IOException { doNothingTest(api); @@ -94,8 +79,8 @@ private void softRevokeUserIdTest(OpenPGPApi api) { OpenPGPKey key = api.readKeyOrCertificate() .parseKey(OpenPGPTestKeys.ALICE_KEY); - Date now = new Date(); - Date oneHourAgo = new Date(new Date().getTime() - (1000 * 60 * 60)); + Date now = currentTimeRounded(); + Date oneHourAgo = new Date(now.getTime() - (1000 * 60 * 60)); OpenPGPCertificate.OpenPGPUserId userId = key.getPrimaryUserId(now); isNotNull(userId); isTrue(userId.isBound()); @@ -130,8 +115,8 @@ private void hardRevokeUserIdTest(OpenPGPApi api) { OpenPGPKey key = api.readKeyOrCertificate() .parseKey(OpenPGPTestKeys.ALICE_KEY); - Date now = new Date(); - Date oneHourAgo = new Date(new Date().getTime() - (1000 * 60 * 60)); + Date now = currentTimeRounded(); + Date oneHourAgo = new Date(now.getTime() - (1000 * 60 * 60)); OpenPGPCertificate.OpenPGPUserId userId = key.getPrimaryUserId(now); isNotNull(userId); isTrue(userId.isBound()); @@ -210,7 +195,7 @@ private void revokeSigningSubkeyTest(OpenPGPApi api) private void extendExpirationTimeTest(OpenPGPApi api) throws PGPException { - Date n0 = new Date((new Date().getTime() / 1000) * 1000); + Date n0 = currentTimeRounded(); OpenPGPKey key = api.generateKey(n0, false) .classicKey(null) .build(); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java index bdca1ea999..3795acdc42 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageGeneratorTest.java @@ -1,8 +1,6 @@ package org.bouncycastle.openpgp.api.test; import org.bouncycastle.bcpg.CompressionAlgorithmTags; -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPApi; @@ -10,8 +8,6 @@ import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; @@ -20,7 +16,7 @@ import java.nio.charset.StandardCharsets; public class OpenPGPMessageGeneratorTest - extends AbstractPacketTest + extends APITest { @Override @@ -29,15 +25,7 @@ public String getName() return "OpenPGPMessageGeneratorTest"; } - @Override - public void performTest() - throws Exception - { - performTestsWith(new BcOpenPGPApi()); - performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performTestsWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws PGPException, IOException { armoredLiteralDataPacket(api); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java index 03b3027904..346284976d 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPMessageProcessorTest.java @@ -3,8 +3,6 @@ import org.bouncycastle.bcpg.AEADAlgorithmTags; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle.bcpg.test.AbstractPacketTest; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.OpenPGPTestKeys; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSessionKey; @@ -17,8 +15,6 @@ import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor; import org.bouncycastle.openpgp.api.OpenPGPSignature; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.io.Streams; @@ -31,7 +27,7 @@ import java.util.List; public class OpenPGPMessageProcessorTest - extends AbstractPacketTest + extends APITest { private static final byte[] PLAINTEXT = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); @@ -43,16 +39,8 @@ public String getName() return "OpenPGPMessageProcessorTest"; } - @Override - public void performTest() - throws Exception - { - performTestsWith(new BcOpenPGPApi()); - performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performTestsWith(OpenPGPApi api) - throws Exception + protected void performTestWith(OpenPGPApi api) + throws PGPException, IOException { testVerificationOfSEIPD1MessageWithTamperedCiphertext(api); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java index 1f9bf07e40..ebd77c492c 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV4KeyGenerationTest.java @@ -3,7 +3,6 @@ import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; @@ -12,13 +11,10 @@ import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; -import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; public class OpenPGPV4KeyGenerationTest - extends AbstractPgpKeyPairTest + extends APITest { @Override public String getName() @@ -27,14 +23,7 @@ public String getName() } @Override - public void performTest() - throws Exception - { - performWith(new BcOpenPGPApi()); - performWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws PGPException { generateRSAKey(api); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 10ec4e6b6c..b75a6637c4 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -12,7 +12,6 @@ import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; @@ -29,17 +28,14 @@ import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator; import org.bouncycastle.openpgp.api.SignatureParameters; import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction; -import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; -import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPApi; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; -import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; public class OpenPGPV6KeyGeneratorTest - extends AbstractPgpKeyPairTest + extends APITest { @Override public String getName() @@ -48,17 +44,7 @@ public String getName() } @Override - public void performTest() - throws Exception - { - // Run tests using the BC implementation - performTestsWith(new BcOpenPGPApi()); - - // Run tests using the JCA/JCE implementation - performTestsWith(new JcaOpenPGPApi(new BouncyCastleProvider())); - } - - private void performTestsWith(OpenPGPApi api) + protected void performTestWith(OpenPGPApi api) throws PGPException, IOException { testGenerateCustomKey(api); From 37ef13838d9ae28302766c2265f29b0a950d785f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 12:23:12 +0100 Subject: [PATCH 112/154] Resolve some TODOs --- .../openpgp/api/OpenPGPCertificate.java | 17 ++++------------- .../openpgp/api/OpenPGPKeyReader.java | 3 --- .../openpgp/api/OpenPGPSignature.java | 5 +---- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index a9b1e7bde2..a47608a9f8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -6,7 +6,6 @@ import org.bouncycastle.bcpg.FingerprintUtil; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -310,8 +309,9 @@ else if (next instanceof PGPSecretKeyRing) else if (next instanceof PGPSignatureList) { - // assume there to be primary key (self) signatures - // TODO: Allow consumption of 3rd-party sigs + // parse and join delegations / revocations + // those are signatures of type DIRECT_KEY or KEY_REVOCATION issued either by the primary key itself + // (self-signatures) or by a 3rd party (delegations / delegation revocations) PGPSignatureList signatures = (PGPSignatureList) next; PGPPublicKeyRing publicKeys = certificate.getPGPPublicKeyRing(); @@ -1250,7 +1250,6 @@ public boolean isSigningKey() */ public boolean isSigningKey(Date evaluationTime) { - // TODO: Replace with https://github.com/bcgit/bc-java/pull/1857/files#diff-36f593d586240aec2546daad96d16b5debd3463202a3d5d82c0b2694572c8426R14-R30 if (!PublicKeyUtils.isSigningAlgorithm(rawPubkey.getAlgorithm())) { // Key is not signing-capable by algorithm @@ -1288,15 +1287,7 @@ public boolean isCertificationKey() */ public boolean isCertificationKey(Date evaluationTime) { - // TODO: Replace with https://github.com/bcgit/bc-java/pull/1857/files#diff-36f593d586240aec2546daad96d16b5debd3463202a3d5d82c0b2694572c8426R14-R30 - int alg = rawPubkey.getAlgorithm(); - if (alg != PublicKeyAlgorithmTags.RSA_GENERAL && - alg != PublicKeyAlgorithmTags.RSA_SIGN && - alg != PublicKeyAlgorithmTags.DSA && - alg != PublicKeyAlgorithmTags.ECDSA && - alg != PublicKeyAlgorithmTags.EDDSA_LEGACY && - alg != PublicKeyAlgorithmTags.Ed25519 && - alg != PublicKeyAlgorithmTags.Ed448) + if (!PublicKeyUtils.isSigningAlgorithm(rawPubkey.getAlgorithm())) { // Key is not signing-capable by algorithm return false; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java index bdd03a5d56..3db57919c3 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -133,9 +133,6 @@ public OpenPGPCertificate parseCertificateOrKey(byte[] bytes) PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); Object object = objectFactory.nextObject(); - // TODO: Is it dangerous, if we don't explicitly fail upon encountering secret key material here? - // Could it lead to a situation where we need to be cautious with the certificate API design to - // prevent the user from doing dangerous things like accidentally publishing their private key? while (object instanceof PGPMarker) { object = objectFactory.nextObject(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 41acec72ca..c0c6324a99 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -308,6 +308,7 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, switch (signature.getVersion()) { case SignaturePacket.VERSION_4: + case SignaturePacket.VERSION_5: if (hashed.getIssuerFingerprint() == null && unhashed.getIssuerFingerprint() == null && hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null && @@ -318,10 +319,6 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, } break; - case SignaturePacket.VERSION_5: - // TODO: Implement - break; - case SignaturePacket.VERSION_6: if (hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) != null) { From 0e0ddf51c27ba94a56c7222e221a0d6ba963f340 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 12:27:55 +0100 Subject: [PATCH 113/154] Add KeyIdentifier.toPrettyPrint() --- .../java/org/bouncycastle/bcpg/KeyIdentifier.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java b/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java index 8bbbe544fd..d95bdf1b5e 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/KeyIdentifier.java @@ -250,4 +250,17 @@ public String toString() // -DM Hex.toHexString return Hex.toHexString(fingerprint).toUpperCase(Locale.getDefault()); } + + public String toPrettyPrint() + { + if (isWildcard()) + { + return "*"; + } + if (fingerprint == null) + { + return "0x" + Long.toHexString(keyId).toUpperCase(Locale.getDefault()); + } + return FingerprintUtil.prettifyFingerprint(fingerprint); + } } From 074e209ad56541fc1edde96fb2fef76aaab9d5c3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 12:41:36 +0100 Subject: [PATCH 114/154] OpenPGPSignature.toAsciiArmoredString(): Add pretty-printed identifier --- .../org/bouncycastle/openpgp/api/OpenPGPSignature.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index c0c6324a99..662bb1e1b5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -423,9 +423,13 @@ public String toAsciiArmoredString() throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream aOut = ArmoredOutputStream.builder() - .clearHeaders() - .build(bOut); + ArmoredOutputStream.Builder aBuilder = ArmoredOutputStream.builder() + .clearHeaders(); + if (getKeyIdentifier() != null) + { + aBuilder.addSplitMultilineComment(getKeyIdentifier().toPrettyPrint()); + } + ArmoredOutputStream aOut = aBuilder.build(bOut); BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); getSignature().encode(pOut); pOut.close(); From 73ab6c3efaf0a6880f53374a085196587569f674 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 12:48:51 +0100 Subject: [PATCH 115/154] Add missing javadoc --- .../openpgp/api/OpenPGPSignature.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 662bb1e1b5..9b5dbebce9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -419,6 +419,14 @@ protected String getType() } } + /** + * Return an ASCII armored String representation of the signature. + * If the signature contains issuer information, the fingerprint or key-id of the issuer will be added + * to the ASCII armor as a comment header. + * + * @return ASCII armored signature + * @throws IOException if the signature cannot be encoded + */ public String toAsciiArmoredString() throws IOException { @@ -678,6 +686,14 @@ public boolean isValidAt(Date date, OpenPGPPolicy policy) issuer.isSigningKey(date); } + /** + * Check, if the creation time of the signature is within the interval + *
notBefore <= creationTime <= notAfter
+ * + * @param notBefore earliest accepted creation time + * @param notAfter latest accepted creation time + * @return true if sig was created in bounds, false otherwise + */ public boolean createdInBounds(Date notBefore, Date notAfter) { return !getCreationTime().before(notBefore) && !getCreationTime().after(notAfter); From 80994c39e9d4ba25d2876f4f496efc8982046c28 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 12:56:47 +0100 Subject: [PATCH 116/154] Add more missing javadoc --- .../openpgp/api/SignatureParameters.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index 667d934404..b7bfa46d8b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -26,9 +26,15 @@ private SignatureParameters(int... allowedSignatureTypes) /** * Create default signature parameters object for a direct-key signature. + * When issued as a self-signature, direct-key signatures can be used to store algorithm preferences + * on the key, which apply to the entire certificate (including all subkeys). + * When issued as a third-party signature, direct-key signatures act as delegations, with which for example the + * web-of-trust can be built. * * @param policy algorithm policy * @return parameters + * @see + * OpenPGP Web-of-Trust */ public static SignatureParameters directKeySignature(OpenPGPPolicy policy) { @@ -38,6 +44,16 @@ public static SignatureParameters directKeySignature(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create default signature parameters for a key revocation signature. + * When issued as a self-signature, key revocation signatures can be used to revoke an entire certificate. + * To revoke only individual subkeys, see {@link #subkeyRevocation(OpenPGPPolicy)} instead. + * When issued as a third-party signature, key revocation signatures are used to revoke earlier delegation + * signatures. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters keyRevocation(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.KEY_REVOCATION) @@ -51,6 +67,9 @@ public static SignatureParameters keyRevocation(OpenPGPPolicy policy) * The default signature type is {@link PGPSignature#POSITIVE_CERTIFICATION}, but can be changed to * {@link PGPSignature#DEFAULT_CERTIFICATION}, {@link PGPSignature#NO_CERTIFICATION}, * {@link PGPSignature#CASUAL_CERTIFICATION}. + * When issued as a self-signature, certifications can be used to bind user-ids to the certificate. + * When issued as third-party signatures, certificates act as a statement, expressing that the issuer + * is convinced that the user-id "belongs to" the certificate. * * @param policy algorithm policy * @return parameters @@ -81,6 +100,12 @@ public static SignatureParameters subkeyBinding(OpenPGPPolicy policy) .setSignatureCreationTime(new Date()); } + /** + * Create default signature parameters for a subkey revocation signature. + * + * @param policy algorithm policy + * @return parameters + */ public static SignatureParameters subkeyRevocation(OpenPGPPolicy policy) { return new SignatureParameters(PGPSignature.SUBKEY_REVOCATION) From 99d7e21dd4de253104d2bbef7551960c7c6a6b0a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 14:13:32 +0100 Subject: [PATCH 117/154] SignatureParameters: sanitize non-null creationTime --- .../org/bouncycastle/openpgp/api/SignatureParameters.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java index b7bfa46d8b..02408b62e0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureParameters.java @@ -5,9 +5,12 @@ import org.bouncycastle.util.Arrays; import java.util.Date; +import java.util.Objects; /** * Parameters for signature generation. + * Some signature builders allow the user to pass in a {@link Callback}, which can be used to modify + * {@link SignatureParameters} instances prior to signature generation. */ public class SignatureParameters { @@ -197,7 +200,8 @@ public int getSignatureType() */ public SignatureParameters setSignatureCreationTime(Date signatureCreationTime) { - this.signatureCreationTime = signatureCreationTime; + this.signatureCreationTime = Objects.requireNonNull(signatureCreationTime, + "Signature creation time cannot be null."); return this; } From 0e78b7fcbd550c437b3f8c83b3a2619b5fb97c78 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 27 Jan 2025 14:13:59 +0100 Subject: [PATCH 118/154] OpenPGPCertificate: Explicitly check primary-user-id sigs for preferences --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index a47608a9f8..be1e2245b4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -401,11 +401,11 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c OpenPGPSignatureChains chainsForComponent = getAllSignatureChainsFor(component); if (component == getPrimaryKey() && chainsForComponent.isEmpty()) { - // If cert has no direct-key signatures, consider UID bindings instead - // TODO: Only consider current primary user id? - for (OpenPGPIdentityComponent identity : getPrimaryKey().identityComponents) + // If cert has no direct-key signatures, consider primary UID bindings instead + OpenPGPUserId primaryUserId = getPrimaryUserId(evaluationDate); + if (primaryUserId != null) { - chainsForComponent.addAll(getAllSignatureChainsFor(identity)); + chainsForComponent.addAll(getAllSignatureChainsFor(primaryUserId)); } } From 965def5d8f6c87443205ffa43a35dbe31398827e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:27:57 +0100 Subject: [PATCH 119/154] Fix typo --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index be1e2245b4..ff48d00ca4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -79,7 +79,7 @@ public class OpenPGPCertificate private final Map componentSignatureChains; /** - * Instantiate an {@link OpenPGPCertificate} from a parksed {@link PGPKeyRing} using the default + * Instantiate an {@link OpenPGPCertificate} from a passed {@link PGPKeyRing} using the default * {@link OpenPGPImplementation} and its {@link OpenPGPPolicy}. * * @param keyRing key ring From 3c6cf053d6d833d5a268c8e7f05bd178b0a15a66 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 21:17:29 +0100 Subject: [PATCH 120/154] PGPKeyPairGenerator: Implement generation of NIST ECDH and ECDSA keys --- .../openpgp/operator/PGPKeyPairGenerator.java | 112 +++++++++ .../bc/BcPGPKeyPairGeneratorProvider.java | 33 ++- .../JcaPGPKeyPairGeneratorProvider.java | 39 +++ .../openpgp/test/PGPKeyPairGeneratorTest.java | 238 ++++++++++++++++++ 4 files changed, 421 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java index a4c5c4953e..2dc54f5c42 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java @@ -1,5 +1,7 @@ package org.bouncycastle.openpgp.operator; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; @@ -178,4 +180,114 @@ public abstract PGPKeyPair generateLegacyEd25519KeyPair() */ public abstract PGPKeyPair generateLegacyX25519KeyPair() throws PGPException; + + /** + * Generate an ECDH elliptic curve encryption key over the NIST p-256 curve. + * + * @return NIST p-256 ECDSA encryption key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP256ECDHKeyPair() + throws PGPException + { + return generateECDHKeyPair(SECObjectIdentifiers.secp256r1); + } + + /** + * Generate an ECDH elliptic curve encryption key over the NIST p-384 curve. + * + * @return NIST p-384 ECDSA encryption key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP384ECDHKeyPair() + throws PGPException + { + return generateECDHKeyPair(SECObjectIdentifiers.secp384r1); + } + + /** + * Generate an ECDH elliptic curve encryption key over the NIST p-521 curve. + * + * @return NIST p-521 ECDSA encryption key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP521ECDHKeyPair() + throws PGPException + { + return generateECDHKeyPair(SECObjectIdentifiers.secp521r1); + } + + /** + * Generate an ECDSA elliptic curve signing key over the NIST p-256 curve. + * + * @return NIST p-256 ECDSA signing key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP256ECDSAKeyPair() + throws PGPException + { + return generateECDSAKeyPair(SECObjectIdentifiers.secp256r1); + } + + /** + * Generate an ECDSA elliptic curve signing key over the NIST p-384 curve. + * + * @return NIST p-384 ECDSA signing key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP384ECDSAKeyPair() + throws PGPException + { + return generateECDSAKeyPair(SECObjectIdentifiers.secp384r1); + } + + /** + * Generate an ECDSA elliptic curve signing key over the NIST p-521 curve. + * + * @return NIST p-521 ECDSA signing key pair + * @throws PGPException if the key pair cannot be generated + * + * @see + * RFC6637 - Elliptic Curve Cryptography in OpenPGP + */ + public PGPKeyPair generateNistP521ECDSAKeyPair() + throws PGPException + { + return generateECDSAKeyPair(SECObjectIdentifiers.secp521r1); + } + + /** + * Generate an elliptic curve Diffie-Hellman encryption key pair over the curve identified by the given OID. + * + * @param curveOID OID of the elliptic curve + * @return PGP key pair + * @throws PGPException if the key pair cannot be generated + */ + public abstract PGPKeyPair generateECDHKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException; + + /** + * Generate an elliptic curve signing key over the curve identified by the given OID. + * + * @param curveOID OID of the elliptic curve + * @return PGP key pair + * @throws PGPException if the key pair cannot be generated + */ + public abstract PGPKeyPair generateECDSAKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java index 42e1eef9af..3ecc447a8e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java @@ -1,19 +1,24 @@ package org.bouncycastle.openpgp.operator.bc; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator; import org.bouncycastle.crypto.generators.Ed448KeyPairGenerator; import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; import org.bouncycastle.crypto.generators.X25519KeyPairGenerator; import org.bouncycastle.crypto.generators.X448KeyPairGenerator; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECNamedDomainParameters; import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters; import org.bouncycastle.crypto.params.Ed448KeyGenerationParameters; import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; import org.bouncycastle.crypto.params.X25519KeyGenerationParameters; import org.bouncycastle.crypto.params.X448KeyGenerationParameters; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; @@ -24,7 +29,7 @@ import java.util.Date; public class BcPGPKeyPairGeneratorProvider - extends PGPKeyPairGeneratorProvider + extends PGPKeyPairGeneratorProvider { private SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); @@ -128,5 +133,31 @@ public PGPKeyPair generateLegacyX25519KeyPair() AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.ECDH, keyPair, creationTime); } + + @Override + public PGPKeyPair generateECDHKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException + { + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters( + new ECNamedDomainParameters(curveOID, ECUtil.getNamedCurveByOid(curveOID)), + CryptoServicesRegistrar.getSecureRandom())); + + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.ECDH, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateECDSAKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException + { + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters( + new ECNamedDomainParameters(curveOID, ECUtil.getNamedCurveByOid(curveOID)), + CryptoServicesRegistrar.getSecureRandom())); + + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.ECDSA, keyPair, creationTime); + } } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java index 329bf9f052..bb69846cc0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java @@ -1,13 +1,16 @@ package org.bouncycastle.openpgp.operator.jcajce; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.jcajce.spec.EdDSAParameterSpec; import org.bouncycastle.jcajce.spec.XDHParameterSpec; import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; import org.bouncycastle.jcajce.util.NamedJcaJceHelper; import org.bouncycastle.jcajce.util.ProviderJcaJceHelper; +import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; @@ -212,5 +215,41 @@ public PGPKeyPair generateLegacyX25519KeyPair() throw new PGPException("Cannot generate LegacyX25519 key pair.", e); } } + + @Override + public PGPKeyPair generateECDHKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("ECDH"); + String curveName = ECUtil.getCurveName(curveOID); + gen.initialize(new ECNamedCurveGenParameterSpec(curveName)); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.ECDH, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate ECDH key pair.", e); + } + } + + @Override + public PGPKeyPair generateECDSAKeyPair(ASN1ObjectIdentifier curveOID) + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("ECDSA"); + String curveName = ECUtil.getCurveName(curveOID); + gen.initialize(new ECNamedCurveGenParameterSpec(curveName)); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.ECDSA, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate ECDSA key pair.", e); + } + } } } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java index 1c16c272d8..a0f71064f0 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java @@ -1,5 +1,8 @@ package org.bouncycastle.openpgp.test; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -70,6 +73,25 @@ private void performWith(Factory factory) testGenerateV6LegacyX25519KeyFails(factory); testGenerateV4LegacyX215519Key(factory); + + // NIST + testGenerateV4P256ECDHKey(factory); + testGenerateV6P256ECDHKey(factory); + + testGenerateV4P384ECDHKey(factory); + testGenerateV6P384ECDHKey(factory); + + testGenerateV4P521ECDHKey(factory); + testGenerateV6P521ECDHKey(factory); + + testGenerateV4P256ECDSAKey(factory); + testGenerateV6P256ECDSAKey(factory); + + testGenerateV4P384ECDSAKey(factory); + testGenerateV6P384ECDSAKey(factory); + + testGenerateV4P521ECDSAKey(factory); + testGenerateV6P521ECDSAKey(factory); } private void testGenerateV4RsaKey(Factory factory) @@ -318,6 +340,222 @@ private void testGenerateV4LegacyX215519Key(Factory factory) kp.getPublicKey().getCreationTime(), creationTime); } + private void testGenerateV4P256ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP256ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp256r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4P384ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP384ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp384r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4P521ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP521ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp521r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4P256ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP256ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp256r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4P384ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP384ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp384r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4P521ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + + PGPKeyPair kp = gen.generateNistP521ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp521r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P256ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP256ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp256r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P384ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP384ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp384r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P521ECDHKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP521ECDHKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + ECDHPublicBCPGKey k = (ECDHPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp521r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P256ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP256ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp256r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P384ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP384ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp384r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6P521ECDSAKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + + PGPKeyPair kp = gen.generateNistP521ECDSAKeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDSA); + ECDSAPublicBCPGKey k = (ECDSAPublicBCPGKey) kp.getPublicKey().getPublicKeyPacket().getKey(); + isEquals(SECObjectIdentifiers.secp521r1, k.getCurveOID()); + isEquals("Key creation time mismatch (" + gen.getClass().getName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + public static void main(String[] args) { runTest(new PGPKeyPairGeneratorTest()); From faf5134b44e674159269e93c6f2f40d17f4b7388 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 29 Jan 2025 12:07:31 +0100 Subject: [PATCH 121/154] PGPSecretKey: Properly pass public key to PGPSignatureGenerator --- pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java index b6c814ed2f..d805fbbb37 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java @@ -293,7 +293,7 @@ public PGPSecretKey( // // generate the certification // - PGPSignatureGenerator sGen = new PGPSignatureGenerator(certificationSignerBuilder); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(certificationSignerBuilder, masterKeyPair.getPublicKey()); sGen.init(PGPSignature.SUBKEY_BINDING, masterKeyPair.getPrivateKey()); @@ -302,7 +302,7 @@ public PGPSecretKey( { if (hashedPcks == null) { - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(certificationSignerBuilder); + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(certificationSignerBuilder, keyPair.getPublicKey()); signatureGenerator.init(PGPSignature.PRIMARYKEY_BINDING, keyPair.getPrivateKey()); @@ -382,7 +382,7 @@ private static PGPPublicKey certifiedPublicKey( try { - sGen = new PGPSignatureGenerator(certificationSignerBuilder); + sGen = new PGPSignatureGenerator(certificationSignerBuilder, keyPair.getPublicKey()); } catch (Exception e) { From a17d97d86fb8ed144d360331e151667ea2ecaaec Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 29 Jan 2025 13:00:07 +0100 Subject: [PATCH 122/154] Fix javadoc of OpenPGPSignature --- .../java/org/bouncycastle/openpgp/api/OpenPGPSignature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java index 9b5dbebce9..9a87147022 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -688,7 +688,7 @@ public boolean isValidAt(Date date, OpenPGPPolicy policy) /** * Check, if the creation time of the signature is within the interval - *
notBefore <= creationTime <= notAfter
+ *
notBefore <= creationTime <= notAfter
* * @param notBefore earliest accepted creation time * @param notAfter latest accepted creation time From 7a824e6b80c4ec1f7d9c03a4b0540fa046ca88eb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 29 Jan 2025 15:35:38 +0100 Subject: [PATCH 123/154] Add more missing methods --- .../openpgp/api/OpenPGPCertificate.java | 8 ++++++++ .../org/bouncycastle/openpgp/api/OpenPGPKey.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index ff48d00ca4..805e3e352e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -133,6 +133,14 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati } } + public Map getPublicKeys() + { + Map keys = new HashMap<>(); + keys.put(primaryKey.getKeyIdentifier(), primaryKey); + keys.putAll(subkeys); + return keys; + } + /** * Return the primary key of the certificate. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 5119d7eec6..45b59e7ebd 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -83,6 +83,16 @@ public OpenPGPKey(PGPSecretKeyRing keyRing, OpenPGPImplementation implementation } } + /** + * Return the {@link OpenPGPCertificate} of this {@link OpenPGPKey}. + * + * @return certificate + */ + public OpenPGPCertificate toCertificate() + { + return new OpenPGPCertificate(getPGPPublicKeyRing(), implementation, policy); + } + @Override public List getComponents() { @@ -147,6 +157,11 @@ public OpenPGPSecretKey getSecretKey(OpenPGPComponentKey key) @Override public PGPSecretKeyRing getPGPKeyRing() + { + return getPGPSecretKeyRing(); + } + + public PGPSecretKeyRing getPGPSecretKeyRing() { return (PGPSecretKeyRing) super.getPGPKeyRing(); } From 449205a13f43b62a7414dcdd8c34523e208783b5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 30 Jan 2025 11:44:08 +0100 Subject: [PATCH 124/154] Add PGPKeyRing.getKeyIdentifier() --- .../main/java/org/bouncycastle/openpgp/PGPKeyRing.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java index c9a7630d1d..8507988deb 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java @@ -93,6 +93,16 @@ static void readUserIDs( } } + /** + * Return the {@link KeyIdentifier} of this key rings primary key. + * + * @return primary key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return getPublicKey().getKeyIdentifier(); + } + /** * Return the first public key in the ring. In the case of a {@link PGPSecretKeyRing} * this is also the public key of the master key pair. From 4fefadf8c2be64dbb7226ef9dd9df50e48563dcf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 30 Jan 2025 11:44:52 +0100 Subject: [PATCH 125/154] OpenPGPSecretKey.unlock(): Return PGPKeyPair instead of PGPPrivateKey --- ...ractOpenPGPDocumentSignatureGenerator.java | 9 +++++++- .../bouncycastle/openpgp/api/OpenPGPKey.java | 22 +++++++++++++------ .../openpgp/api/OpenPGPKeyEditor.java | 17 +++++++------- .../openpgp/api/OpenPGPMessageProcessor.java | 11 +++++++--- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java index 368c30db22..48dc7f5e81 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java @@ -2,9 +2,11 @@ import org.bouncycastle.bcpg.sig.PreferredAlgorithms; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; +import org.bouncycastle.openpgp.api.exception.KeyPassphraseException; import java.util.ArrayList; import java.util.Arrays; @@ -248,7 +250,12 @@ protected PGPSignatureGenerator initSignatureGenerator( signingKey.getPGPPublicKey()); char[] passphrase = passphraseProvider.getKeyPassword(signingKey); - sigGen.init(parameters.getSignatureType(), signingKey.unlock(passphrase)); + PGPKeyPair unlockedKey = signingKey.unlock(passphrase); + if (unlockedKey == null) + { + throw new KeyPassphraseException(signingKey, new PGPException("Cannot unlock secret key.")); + } + sigGen.init(parameters.getSignatureType(), unlockedKey.getPrivateKey()); PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); hashedSubpackets.setIssuerFingerprint(true, signingKey.getPGPPublicKey()); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 45b59e7ebd..7e027655fe 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -8,6 +8,7 @@ import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyValidationException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; @@ -266,7 +267,7 @@ public boolean isLocked() return getPGPSecretKey().getS2KUsage() != SecretKeyPacket.USAGE_NONE; } - public PGPPrivateKey unlock(KeyPassphraseProvider passphraseProvider) + public PGPKeyPair unlock(KeyPassphraseProvider passphraseProvider) throws PGPException { if (!isLocked()) @@ -277,14 +278,14 @@ public PGPPrivateKey unlock(KeyPassphraseProvider passphraseProvider) } /** - * Access the {@link PGPPrivateKey} by unlocking the potentially locked secret key using the provided + * Access the {@link PGPKeyPair} by unlocking the potentially locked secret key using the provided * passphrase. Note: If the key is not locked, it is sufficient to pass null as passphrase. * * @param passphrase passphrase or null - * @return unlocked private key + * @return keypair containing unlocked private key * @throws PGPException if the key cannot be unlocked */ - public PGPPrivateKey unlock(char[] passphrase) + public PGPKeyPair unlock(char[] passphrase) throws PGPException { sanitizeProtectionMode(); @@ -295,7 +296,14 @@ public PGPPrivateKey unlock(char[] passphrase) { decryptor = decryptorBuilderProvider.provide().build(passphrase); } - return getPGPSecretKey().extractPrivateKey(decryptor); + + PGPPrivateKey privateKey = getPGPSecretKey().extractPrivateKey(decryptor); + if (privateKey == null) + { + return null; + } + + return new PGPKeyPair(getPGPSecretKey().getPublicKey(), privateKey); } catch (PGPException e) { @@ -340,8 +348,8 @@ public boolean isPassphraseCorrect(char[] passphrase) { try { - PGPPrivateKey privateKey = unlock(passphrase); - return privateKey != null; + PGPKeyPair unlocked = unlock(passphrase); + return unlocked != null; } catch (PGPException e) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index 69a87184ce..aedff09e31 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -6,7 +6,6 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyValidationException; -import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; @@ -27,7 +26,7 @@ public class OpenPGPKeyEditor private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; private OpenPGPKey key; - private final PGPPrivateKey privatePrimaryKey; + private final PGPKeyPair primaryKey; public OpenPGPKeyEditor(OpenPGPKey key, KeyPassphraseProvider passphraseProvider) throws PGPException @@ -50,7 +49,7 @@ public OpenPGPKeyEditor(OpenPGPKey key, throws PGPException { this.key = key; - this.privatePrimaryKey = key.getPrimarySecretKey().unlock(passphraseProvider); + this.primaryKey = key.getPrimarySecretKey().unlock(passphraseProvider); this.implementation = implementation; this.policy = policy; } @@ -73,7 +72,7 @@ public OpenPGPKeyEditor addDirectKeySignature(SignatureParameters.Callback signa publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - dkSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + dkSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -146,7 +145,7 @@ public OpenPGPKeyEditor addUserId(String userId, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - uidSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + uidSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -217,7 +216,7 @@ public OpenPGPKeyEditor revokeIdentity(OpenPGPCertificate.OpenPGPIdentityCompone publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - idSigGen.init(parameters.getSignatureType(), privatePrimaryKey); + idSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -300,7 +299,7 @@ public OpenPGPKeyEditor addEncryptionSubkey(PGPKeyPair encryptionSubkey, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - subKeySigGen.init(parameters.getSignatureType(), privatePrimaryKey); + subKeySigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -406,7 +405,7 @@ public OpenPGPKeyEditor addSigningSubkey(PGPKeyPair signingSubkey, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - subKeySigGen.init(parameters.getSignatureType(), privatePrimaryKey); + subKeySigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -490,7 +489,7 @@ public OpenPGPKeyEditor revokeComponentKey(OpenPGPCertificate.OpenPGPComponentKe publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - revGen.init(parameters.getSignatureType(), privatePrimaryKey); + revGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index c815148c5d..38d6f44c25 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -4,14 +4,15 @@ import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPBEEncryptedData; -import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.IntegrityProtectedInputStream; +import org.bouncycastle.openpgp.api.exception.KeyPassphraseException; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; @@ -356,11 +357,15 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) } char[] keyPassphrase = configuration.keyPassphraseProvider.getKeyPassword(decryptionKey); - PGPPrivateKey privateKey = decryptionKey.unlock(keyPassphrase); + PGPKeyPair unlockedKey = decryptionKey.unlock(keyPassphrase); + if (unlockedKey == null) + { + throw new KeyPassphraseException(decryptionKey, new PGPException("Cannot unlock secret key.")); + } // Decrypt the message session key using the private key PublicKeyDataDecryptorFactory pkDecryptorFactory = - implementation.publicKeyDataDecryptorFactory(privateKey); + implementation.publicKeyDataDecryptorFactory(unlockedKey.getPrivateKey()); PGPSessionKey decryptedSessionKey = pkesk.getSessionKey(pkDecryptorFactory); // Decrypt the message using the decrypted session key From cf816425e9173bc11c0a6f6ea115afef8da278f7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 30 Jan 2025 22:18:49 +0100 Subject: [PATCH 126/154] Fix initialization of keys with non-UTF8 user-ids --- .../main/java/org/bouncycastle/openpgp/PGPPublicKey.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java index 4f69293bcb..a69a7f6335 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java @@ -487,7 +487,14 @@ public Iterator getUserIDs() { if (ids.get(i) instanceof UserIDPacket) { - temp.add(((UserIDPacket)ids.get(i)).getID()); + try + { + temp.add(((UserIDPacket) ids.get(i)).getID()); + } + catch (IllegalArgumentException e) + { + // Skip non-UTF8 user-ids + } } } From c96744c8d532f8eb043e57cc4254f9d935f3fef7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 30 Jan 2025 22:48:00 +0100 Subject: [PATCH 127/154] Properly throw MissingIssuerCertException for third-party certifications --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 805e3e352e..21bb8be3e0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -2038,7 +2038,12 @@ public boolean isEffectiveAt(Date evaluationDate) public boolean isValid() throws PGPSignatureException { - OpenPGPCertificate cert = getRootKey().getCertificate(); + OpenPGPComponentKey rootKey = getRootKey(); + if (rootKey == null) + { + throw new MissingIssuerCertException(getRootLink().signature, "Missing issuer certificate."); + } + OpenPGPCertificate cert = rootKey.getCertificate(); return isValid(cert.implementation.pgpContentVerifierBuilderProvider(), cert.policy); } From 0fbacdb23fa8f429f011c51a3d87302ab0082757 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 31 Jan 2025 12:20:24 +0100 Subject: [PATCH 128/154] Add OpenPGPCertificate.getLastModificationDate() methods --- .../openpgp/api/OpenPGPCertificate.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 21bb8be3e0..b4e6fae7e2 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -285,6 +285,63 @@ public List getAllKeyIdentifiers() return identifiers; } + /** + * Return the last time, the key was modified (before right now). + * A modification is the addition of a new subkey, or key signature. + * + * @return last modification time + */ + public Date getLastModificationDate() + { + return getLastModificationDateAt(new Date()); + } + + /** + * Return the last time, the key was modified before or at the given evaluation time. + * + * @param evaluationTime evaluation time + * @return last modification time before or at evaluation time + */ + public Date getLastModificationDateAt(Date evaluationTime) + { + Date latestModification = null; + // Signature creation times + for (OpenPGPCertificateComponent component : getComponents()) + { + OpenPGPSignatureChains componentChains = componentSignatureChains.get(component); + if (componentChains == null) + { + continue; + } + componentChains = componentChains.getChainsAt(evaluationTime); + for (OpenPGPSignatureChain chain : componentChains) + { + for (OpenPGPSignatureChain.Link link : chain) + { + if (latestModification == null || link.since().after(latestModification)) + { + latestModification = link.since(); + } + } + } + } + + // Key creation times + for (OpenPGPComponentKey key : getKeys()) + { + if (key.getCreationTime().after(evaluationTime)) + { + continue; + } + + if (latestModification == null || key.getCreationTime().after(latestModification)) + { + latestModification = key.getCreationTime(); + } + } + return latestModification; + } + public static OpenPGPCertificate join(OpenPGPCertificate certificate, String armored) throws IOException, PGPException { From cf2050f086d0feff3bfed36cfc9060e3d9486147 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 31 Jan 2025 12:20:44 +0100 Subject: [PATCH 129/154] Properly compare OpenPGPSignatureChain objects to another --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index b4e6fae7e2..72d9b71324 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -2149,7 +2149,13 @@ public int compareTo(OpenPGPSignatureChain other) return 1; } - return -getSince().compareTo(other.getSince()); + int rootCompare = -getRootLink().since().compareTo(other.getRootLink().since()); + if (rootCompare != 0) + { + return rootCompare; + } + + return -getHeadLink().since().compareTo(other.getHeadLink().since()); } @Override From 0419ff4b1908e0c964456b6c48b9b43acf04a7a8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 3 Feb 2025 13:28:30 +0100 Subject: [PATCH 130/154] Add user-id convenience methods and fix validity checks of key signatures --- .../openpgp/api/OpenPGPCertificate.java | 243 +++++++++++++++--- .../bouncycastle/openpgp/api/OpenPGPKey.java | 7 + 2 files changed, 215 insertions(+), 35 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 72d9b71324..1ff46da810 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -133,6 +133,26 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati } } + public List getAllUserIds() + { + return getPrimaryKey().getUserIDs(); + } + + public List getValidUserIds() + { + return getValidUserIds(new Date()); + } + + public List getValidUserIds(Date evaluationTime) + { + return getPrimaryKey().getValidUserIDs(evaluationTime); + } + + /** + * Get a {@link Map} of all public {@link OpenPGPComponentKey component keys} keyed by their {@link KeyIdentifier}. + * + * @return all public keys + */ public Map getPublicKeys() { Map keys = new HashMap<>(); @@ -162,6 +182,19 @@ public Map getSubkeys() return new HashMap<>(subkeys); } + public List getComponentKeysWithFlag(Date evaluationTime, int... keyFlags) + { + List componentKeys = new ArrayList<>(); + for (OpenPGPComponentKey k : getKeys()) + { + if (k.isBoundAt(evaluationTime) && k.hasKeyFlags(evaluationTime, keyFlags)) + { + componentKeys.add(k); + } + } + return componentKeys; + } + /** * Return a {@link List} containing all {@link OpenPGPCertificateComponent components} of the certificate. * Components are primary key, subkeys and identities (user-ids, user attributes). @@ -188,6 +221,24 @@ public List getKeys() return keys; } + public List getValidKeys() + { + return getValidKeys(new Date()); + } + + public List getValidKeys(Date evaluationTime) + { + List validKeys = new ArrayList<>(); + for (OpenPGPComponentKey k : getKeys()) + { + if (k.isBoundAt(evaluationTime)) + { + validKeys.add(k); + } + } + return validKeys; + } + /** * Return the {@link OpenPGPComponentKey} identified by the passed in {@link KeyIdentifier}. * @@ -858,6 +909,33 @@ public OpenPGPSignatureChains getSignatureChains() return chains; } + public OpenPGPComponentSignature getCertification(Date evaluationTime) + { + OpenPGPSignatureChain certification = getSignatureChains().getCertificationAt(evaluationTime); + if (certification != null) + { + return certification.getSignature(); + } + return null; + } + + public OpenPGPComponentSignature getRevocation(Date evaluationTime) + { + OpenPGPSignatureChain revocation = getSignatureChains().getRevocationAt(evaluationTime); + if (revocation != null) + { + return revocation.getSignature(); + } + return null; + } + + public OpenPGPComponentSignature getLatestSelfSignature() + { + return getLatestSelfSignature(new Date()); + } + + public abstract OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime); + /** * Return the public {@link OpenPGPCertificateComponent} that belongs to this component. * For public components (pubkeys, identities...), that's simply this, while secret components @@ -1039,13 +1117,20 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi // Subkey binding signature else if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) { + // For signing-capable subkeys, check the embedded primary key binding signature + verifyEmbeddedPrimaryKeyBinding(contentVerifierBuilderProvider, policy); + + // Binding signature MUST NOT predate the subkey itself + if (((OpenPGPSubkey) target).getCreationTime().after(signature.getCreationTime())) + { + isCorrect = false; + throw new MalformedOpenPGPSignatureException(this, "Subkey binding signature predates subkey creation time."); + } + verifyKeySignature( issuer, (OpenPGPSubkey) target, contentVerifierBuilderProvider); - - // For signing-capable subkeys, check the embedded primary key binding signature - verifyEmbeddedPrimaryKeyBinding(contentVerifierBuilderProvider, policy); } // User-ID binding @@ -1262,6 +1347,17 @@ public Date getCreationTime() return rawPubkey.getCreationTime(); } + @Override + public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) + { + OpenPGPSignatureChain currentDKChain = getSignatureChains().getChainAt(evaluationTime); + if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) + { + return currentDKChain.getHeadLink().getSignature(); + } + return null; + } + /** * Return true, if the key is currently marked as encryption key. * @@ -1286,15 +1382,8 @@ public boolean isEncryptionKey(Date evaluationTime) return false; } - KeyFlags keyFlags = getKeyFlags(evaluationTime); - if (keyFlags == null) - { - return false; - } - - int flags = keyFlags.getFlags(); - return (flags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS || - (flags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE; + return hasKeyFlags(evaluationTime, KeyFlags.ENCRYPT_STORAGE) || + hasKeyFlags(evaluationTime, KeyFlags.ENCRYPT_COMMS); } /** @@ -1321,16 +1410,7 @@ public boolean isSigningKey(Date evaluationTime) return false; } - KeyFlags keyFlags = getKeyFlags(evaluationTime); - if (keyFlags == null) - { - // Key has no applicable key-flags - return false; - } - - // Check if key is marked as signing-capable by key-flags - int flags = keyFlags.getFlags(); - return (flags & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA; + return hasKeyFlags(evaluationTime, KeyFlags.SIGN_DATA); } /** @@ -1358,14 +1438,7 @@ public boolean isCertificationKey(Date evaluationTime) return false; } - KeyFlags keyFlags = getKeyFlags(evaluationTime); - if (keyFlags == null) - { - return false; - } - - int flags = keyFlags.getFlags(); - return (flags & KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER; + return hasKeyFlags(evaluationTime, KeyFlags.CERTIFY_OTHER); } /** @@ -1393,6 +1466,36 @@ public KeyFlags getKeyFlags(Date evaluationTime) return null; } + /** + * Return
true
, if the key has any of the given key flags. + *

+ * Note: To check if the key has EITHER flag A or B, call

hasKeyFlags(evalTime, A, B)
. + * To instead check, if the key has BOTH flags A AND B, call
hasKeyFlags(evalTime, A & B)
. + * + * @param evaluationTime evaluation time + * @param flags key flags (see {@link KeyFlags} for possible values) + * @return true if the key has ANY of the provided flags + */ + public boolean hasKeyFlags(Date evaluationTime, int... flags) + { + KeyFlags keyFlags = getKeyFlags(evaluationTime); + if (keyFlags == null) + { + // Key has no key-flags + return false; + } + + // Check if key has the desired key-flags + for (int f : flags) + { + if (((keyFlags.getFlags() & f) == f)) + { + return true; + } + } + return false; + } + /** * Return the {@link Features} signature subpacket that currently applies to the key. * @return feature signature subpacket @@ -1576,6 +1679,31 @@ public OpenPGPPrimaryKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) } } + @Override + public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) + { + List signatures = new ArrayList<>(); + OpenPGPSignatureChain currentDKChain = getSignatureChains().getChainAt(evaluationTime); + if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) + { + signatures.add(currentDKChain.getHeadLink().getSignature()); + } + for (OpenPGPIdentityComponent identity : getCertificate().getIdentities()) + { + signatures.add(identity.getLatestSelfSignature(evaluationTime)); + } + + OpenPGPComponentSignature latest = null; + for (OpenPGPComponentSignature signature : signatures) + { + if (latest == null || signature.getCreationTime().after(latest.getCreationTime())) + { + latest = signature; + } + } + return latest; + } + /** * Return all {@link OpenPGPUserId OpenPGPUserIds} on this key. * @@ -1594,6 +1722,24 @@ public List getUserIDs() return userIds; } + public List getValidUserIds() + { + return getValidUserIDs(new Date()); + } + + public List getValidUserIDs(Date evaluationTime) + { + List userIds = new ArrayList<>(); + for (OpenPGPIdentityComponent identity : identityComponents) + { + if (identity instanceof OpenPGPUserId && identity.isBoundAt(evaluationTime)) + { + userIds.add((OpenPGPUserId) identity); + } + } + return userIds; + } + /** * Return the {@link OpenPGPUserId}, which is - at evaluation time - explicitly marked as primary. * @@ -1838,6 +1984,17 @@ public OpenPGPPrimaryKey getPrimaryKey() return primaryKey; } + @Override + public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) + { + OpenPGPSignatureChain currentChain = getSignatureChains().getChainAt(evaluationTime); + if (currentChain != null && !currentChain.chainLinks.isEmpty()) + { + return currentChain.getHeadLink().getSignature(); + } + return null; + } + @Override public String toDetailString() { @@ -1960,6 +2117,11 @@ private OpenPGPSignatureChain(OpenPGPSignatureChain copy) this.chainLinks.addAll(copy.chainLinks); } + public OpenPGPComponentSignature getSignature() + { + return getHeadLink().getSignature(); + } + /** * Return an NEW instance of the {@link OpenPGPSignatureChain} with the new link appended. * @param sig signature @@ -2089,7 +2251,8 @@ public boolean isEffectiveAt(Date evaluationDate) } Date since = getSince(); Date until = getUntil(); - return !evaluationDate.before(since) && (until == null || evaluationDate.before(until)); + // since <= eval <= until + return !evaluationDate.before(since) && (until == null || !evaluationDate.after(until)); } public boolean isValid() @@ -2149,13 +2312,23 @@ public int compareTo(OpenPGPSignatureChain other) return 1; } - int rootCompare = -getRootLink().since().compareTo(other.getRootLink().since()); - if (rootCompare != 0) + int compare = -getRootLink().since().compareTo(other.getRootLink().since()); + if (compare != 0) + { + return compare; + } + + compare = -getHeadLink().since().compareTo(other.getHeadLink().since()); + if (compare != 0) { - return rootCompare; + return compare; } - return -getHeadLink().since().compareTo(other.getHeadLink().since()); + if (isRevocation()) + { + return -1; + } + return 1; } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 7e027655fe..110d2f5b7d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -19,6 +19,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -227,6 +228,12 @@ protected OpenPGPCertificateComponent getPublicComponent() return pubKey; } + @Override + public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) + { + return getPublicKey().getLatestSelfSignature(evaluationTime); + } + public OpenPGPKey getOpenPGPKey() { return (OpenPGPKey) getCertificate(); From cfc9904e2eacced38d6003dd6d44f1850a4c82f7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 4 Feb 2025 16:00:16 +0100 Subject: [PATCH 131/154] Fix ordering of OpenPGPSignatureChains --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 1ff46da810..0dc82dea46 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -2328,7 +2328,7 @@ public int compareTo(OpenPGPSignatureChain other) { return -1; } - return 1; + return -1; } @Override From 207916ac76802583ba1dd87ddeb0f4081f0f51c2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 4 Feb 2025 16:00:51 +0100 Subject: [PATCH 132/154] Only return latest key creation time if no sigs found --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 0dc82dea46..fb688a1c4d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -377,6 +377,11 @@ public Date getLastModificationDateAt(Date evaluationTime) } } + if (latestModification != null) + { + return latestModification; + } + // Key creation times for (OpenPGPComponentKey key : getKeys()) { From 45c1b9ea726a3294182e544cafd7455d880dff52 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 4 Feb 2025 16:01:11 +0100 Subject: [PATCH 133/154] Implement getKeyExpirationTime --- .../openpgp/api/OpenPGPCertificate.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index fb688a1c4d..b012d2971e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1092,6 +1092,29 @@ public OpenPGPComponentKey getTargetKeyComponent() throw new IllegalArgumentException("Unknown target type."); } + public Date getKeyExpirationTime() + { + PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); + if (hashed == null) + { + // v3 sigs have no expiration + return null; + } + long exp = hashed.getKeyExpirationTime(); + if (exp < 0) + { + throw new RuntimeException("Negative key expiration time"); + } + + if (exp == 0L) + { + // Explicit or implicit no expiration + return null; + } + + return new Date(getTargetKeyComponent().getCreationTime().getTime() + 1000 * exp); + } + /** * Verify this signature. * @@ -1624,25 +1647,7 @@ public Date getKeyExpirationDate() */ public Date getKeyExpirationDateAt(Date evaluationTime) { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = - getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.KEY_EXPIRE_TIME); - if (subpacket != null) - { - long expiresIn = ((KeyExpirationTime) subpacket.getSubpacket()).getTime(); - if (expiresIn == 0L) - { - // Explicit no expiry - return null; - } - - Date creationTime = getCreationTime(); - Date expirationTime = new Date(creationTime.getTime() + 1000 * expiresIn); - return expirationTime; - } - else - { - return null; // implicit no expiry - } + return getLatestSelfSignature(evaluationTime).getKeyExpirationTime(); } } From 443e50458b2e43375dca3ddee0de2fc43f7642ec Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 4 Feb 2025 17:16:43 +0100 Subject: [PATCH 134/154] Signature creation time collisions: certifications beat soft revocations --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index b012d2971e..dc0d1444b6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -2336,7 +2336,7 @@ public int compareTo(OpenPGPSignatureChain other) if (isRevocation()) { - return -1; + return 1; } return -1; } From 0f28bd94be4339f2296926a72ea513370e84ea02 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 4 Feb 2025 17:17:49 +0100 Subject: [PATCH 135/154] Fix sideeffect of accidental modification of OpenPGPSignatureChains instance --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index dc0d1444b6..156a909bb1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -359,7 +359,7 @@ public Date getLastModificationDateAt(Date evaluationTime) // Signature creation times for (OpenPGPCertificateComponent component : getComponents()) { - OpenPGPSignatureChains componentChains = componentSignatureChains.get(component); + OpenPGPSignatureChains componentChains = getAllSignatureChainsFor(component); if (componentChains == null) { continue; @@ -543,7 +543,9 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c private OpenPGPSignatureChains getAllSignatureChainsFor(OpenPGPCertificateComponent component) { - return componentSignatureChains.get(component.getPublicComponent()); + OpenPGPSignatureChains chains = new OpenPGPSignatureChains(component.getPublicComponent()); + chains.addAll(componentSignatureChains.get(component.getPublicComponent())); + return chains; } private void processPrimaryKey(OpenPGPPrimaryKey primaryKey) From 9d15fe1a03b656616b20c8cd15e94781fd49bd63 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 5 Feb 2025 12:09:18 +0100 Subject: [PATCH 136/154] OpenPGPCertificate, OpenPGPKey: Add isSecretKey() to avoid instanceof calls --- .../org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 5 +++++ .../main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 156a909bb1..50f7418647 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -133,6 +133,11 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati } } + public boolean isSecretKey() + { + return false; + } + public List getAllUserIds() { return getPrimaryKey().getUserIDs(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 110d2f5b7d..f0da9369a7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -85,6 +85,12 @@ public OpenPGPKey(PGPSecretKeyRing keyRing, OpenPGPImplementation implementation } } + @Override + public boolean isSecretKey() + { + return true; + } + /** * Return the {@link OpenPGPCertificate} of this {@link OpenPGPKey}. * From a7f6b7b4238a69f7b53d7f8b8d8106bdf2c916c5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 5 Feb 2025 12:09:43 +0100 Subject: [PATCH 137/154] OpenPGPComponentKey: Add equals() implementation --- .../openpgp/api/OpenPGPCertificate.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 50f7418647..2706ae6ea0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1656,6 +1656,29 @@ public Date getKeyExpirationDateAt(Date evaluationTime) { return getLatestSelfSignature(evaluationTime).getKeyExpirationTime(); } + + @Override + public int hashCode() { + return getPGPPublicKey().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + { + return false; + } + if (this == obj) + { + return true; + } + if (!(obj instanceof OpenPGPComponentKey)) + { + return false; + } + OpenPGPComponentKey other = (OpenPGPComponentKey) obj; + return getPGPPublicKey().equals(other.getPGPPublicKey()); + } } /** From 0df768fdf198e4cb61c7c119a3f2100fe40d5be5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 5 Feb 2025 12:32:44 +0100 Subject: [PATCH 138/154] Invalidate signing keys with expired primary-key binding (backsignature) --- .../openpgp/api/OpenPGPCertificate.java | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 2706ae6ea0..da73638621 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -10,7 +10,6 @@ import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; @@ -1153,7 +1152,7 @@ public void verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvi else if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) { // For signing-capable subkeys, check the embedded primary key binding signature - verifyEmbeddedPrimaryKeyBinding(contentVerifierBuilderProvider, policy); + verifyEmbeddedPrimaryKeyBinding(contentVerifierBuilderProvider, policy, getCreationTime()); // Binding signature MUST NOT predate the subkey itself if (((OpenPGPSubkey) target).getCreationTime().after(signature.getCreationTime())) @@ -1193,7 +1192,7 @@ else if (target instanceof OpenPGPUserAttribute) } private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, - OpenPGPPolicy policy) + OpenPGPPolicy policy, Date signatureCreationTime) throws PGPSignatureException { int keyFlags = signature.getHashedSubPackets().getKeyFlags(); @@ -1228,7 +1227,14 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c subkey, issuer); + if (!backSig.isEffectiveAt(signatureCreationTime)) + { + throw new PGPSignatureException("Embedded PrimaryKeyBinding signature is expired or not yet effective."); + } + backSig.sanitize(subkey, policy); + + // needs to be called last to prevent false positives backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); } @@ -2402,7 +2408,50 @@ public Date since() public Date until() { - return signature.getExpirationTime(); + Date backSigExpiration = getBackSigExpirationTime(); + if (backSigExpiration == null || signature.getExpirationTime().before(backSigExpiration)) + { + return signature.getExpirationTime(); + } + return backSigExpiration; + } + + private Date getBackSigExpirationTime() + { + if (signature.getSignature().getSignatureType() != PGPSignature.SUBKEY_BINDING) + { + return null; + } + + PGPSignatureSubpacketVector hashedSubpackets = signature.getSignature().getHashedSubPackets(); + if (hashedSubpackets == null) + { + return null; + } + + int keyFlags = signature.getSignature().getHashedSubPackets().getKeyFlags(); + if ((keyFlags & KeyFlags.SIGN_DATA) != KeyFlags.SIGN_DATA) + { + return null; + } + + try + { + PGPSignatureList embeddedSigs = hashedSubpackets.getEmbeddedSignatures(); + if (!embeddedSigs.isEmpty()) + { + OpenPGPComponentSignature backSig = new OpenPGPComponentSignature( + embeddedSigs.get(0), + getSignature().getTargetKeyComponent(), + getSignature().getIssuer()); + return backSig.getExpirationTime(); + } + return null; + } + catch (PGPException e) + { + return null; + } } public boolean verify(PGPContentVerifierBuilderProvider contentVerifierBuilderProvider, From 484fa8c282276c01c78349425c54010ea9ce65e0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 6 Feb 2025 13:11:40 +0100 Subject: [PATCH 139/154] Fix NPE in Link.until() --- .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index da73638621..ebaff53e7e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -2409,9 +2409,16 @@ public Date since() public Date until() { Date backSigExpiration = getBackSigExpirationTime(); - if (backSigExpiration == null || signature.getExpirationTime().before(backSigExpiration)) + Date expirationTime = signature.getExpirationTime(); + + if (expirationTime == null) + { + return backSigExpiration; + } + + if (backSigExpiration == null || expirationTime.before(backSigExpiration)) { - return signature.getExpirationTime(); + return expirationTime; } return backSigExpiration; } From 5f73cd4e0d4f4fd1da5c8ab2e63a4cabd6530b9a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Feb 2025 11:52:20 +0100 Subject: [PATCH 140/154] Check all primary key binding signatures --- .../openpgp/api/OpenPGPCertificate.java | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index ebaff53e7e..9f2b92a504 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1204,10 +1204,19 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c OpenPGPComponentKey subkey = getTargetKeyComponent(); // Signing subkey needs embedded primary key binding signature - PGPSignatureList embeddedSignatures; + List embeddedSignatures = new ArrayList<>(); try { - embeddedSignatures = signature.getHashedSubPackets().getEmbeddedSignatures(); + PGPSignatureList sigList = signature.getHashedSubPackets().getEmbeddedSignatures(); + for (PGPSignature pgpSignature : sigList) + { + embeddedSignatures.add(pgpSignature); + } + sigList = signature.getUnhashedSubPackets().getEmbeddedSignatures(); + for (PGPSignature pgpSignature : sigList) + { + embeddedSignatures.add(pgpSignature); + } } catch (PGPException e) { @@ -1220,22 +1229,47 @@ private void verifyEmbeddedPrimaryKeyBinding(PGPContentVerifierBuilderProvider c this, "Signing key SubkeyBindingSignature MUST contain embedded PrimaryKeyBindingSignature."); } - PGPSignature primaryKeyBinding = embeddedSignatures.get(0); - OpenPGPCertificate.OpenPGPComponentSignature backSig = - new OpenPGPCertificate.OpenPGPComponentSignature( - primaryKeyBinding, - subkey, - issuer); - if (!backSig.isEffectiveAt(signatureCreationTime)) + PGPSignatureException exception = null; + for (PGPSignature primaryKeyBinding : embeddedSignatures) { - throw new PGPSignatureException("Embedded PrimaryKeyBinding signature is expired or not yet effective."); - } + OpenPGPCertificate.OpenPGPComponentSignature backSig = + new OpenPGPCertificate.OpenPGPComponentSignature( + primaryKeyBinding, + subkey, + issuer); + + if (primaryKeyBinding.getSignatureType() != PGPSignature.PRIMARYKEY_BINDING) + { + exception = new PGPSignatureException("Unexpected embedded signature type: " + primaryKeyBinding.getSignatureType()); + continue; + } + + if (!backSig.isEffectiveAt(signatureCreationTime)) + { + exception = new PGPSignatureException("Embedded PrimaryKeyBinding signature is expired or not yet effective."); + continue; + } + + try + { + backSig.sanitize(subkey, policy); + + // needs to be called last to prevent false positives + backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); - backSig.sanitize(subkey, policy); + // valid -> return successfully + return; + } + catch (PGPSignatureException e) + { + exception = e; + continue; + } + } - // needs to be called last to prevent false positives - backSig.verifyKeySignature(subkey, issuer, contentVerifierBuilderProvider); + // if we end up here, it means we have only found invalid sigs + throw exception; } protected void verifyKeySignature( From 4d47090a0d630fa49cd4712facf550c48c2a7172 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 10 Feb 2025 14:30:35 +0100 Subject: [PATCH 141/154] Add OpenPGPKeyReader.parseKeysOrCertificates() --- .../openpgp/api/OpenPGPKeyReader.java | 65 +++++++++++++ .../api/test/OpenPGPKeyReaderTest.java | 91 +++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyReaderTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java index 3db57919c3..5f546c7b33 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -4,7 +4,9 @@ import org.bouncycastle.openpgp.PGPMarker; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.util.io.Streams; @@ -12,6 +14,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; /** * Reader for {@link OpenPGPKey OpenPGPKeys} or {@link OpenPGPCertificate OpenPGPCertificates}. @@ -205,4 +209,65 @@ public OpenPGPKey parseKey(byte[] bytes) PGPSecretKeyRing keyRing = (PGPSecretKeyRing) object; return new OpenPGPKey(keyRing, implementation, policy); } + + public List parseKeysOrCertificates(String armored) + throws IOException + { + return parseKeysOrCertificates(armored.getBytes(StandardCharsets.UTF_8)); + } + + public List parseKeysOrCertificates(InputStream inputStream) + throws IOException + { + return parseKeysOrCertificates(Streams.readAll(inputStream)); + } + + public List parseKeysOrCertificates(byte[] bytes) + throws IOException + { + List certsOrKeys = new ArrayList<>(); + + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + InputStream decoderStream = PGPUtil.getDecoderStream(bIn); + BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + Object object; + + while ((object = objectFactory.nextObject()) != null) + { + if (object instanceof PGPMarker) + { + continue; + } + if (object instanceof PGPSecretKeyRing) + { + certsOrKeys.add(new OpenPGPKey((PGPSecretKeyRing) object, implementation, policy)); + } + else if (object instanceof PGPPublicKeyRing) + { + certsOrKeys.add(new OpenPGPCertificate((PGPPublicKeyRing) object, implementation, policy)); + } + else if (object instanceof PGPSecretKeyRingCollection) + { + PGPSecretKeyRingCollection collection = (PGPSecretKeyRingCollection) object; + for (PGPSecretKeyRing k : collection) + { + certsOrKeys.add(new OpenPGPKey(k, implementation, policy)); + } + } + else if (object instanceof PGPPublicKeyRingCollection) + { + PGPPublicKeyRingCollection collection = (PGPPublicKeyRingCollection) object; + for (PGPPublicKeyRing k : collection) + { + certsOrKeys.add(new OpenPGPCertificate(k, implementation, policy)); + } + } + else + { + throw new IOException("Neither a certificate, nor secret key."); + } + } + return certsOrKeys; + } } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyReaderTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyReaderTest.java new file mode 100644 index 0000000000..80dd66a4b4 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPKeyReaderTest.java @@ -0,0 +1,91 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +public class OpenPGPKeyReaderTest + extends APITest +{ + @Override + public String getName() + { + return "OpenPGPKeyReaderTest"; + } + + @Override + protected void performTestWith(OpenPGPApi api) + throws PGPException, IOException + { + testParseEmptyCollection(api); + testParse2CertsCertificateCollection(api); + testParseCertAndKeyToCertificateCollection(api); + } + + private void testParseEmptyCollection(OpenPGPApi api) + throws IOException + { + byte[] empty = new byte[0]; + List certs = api.readKeyOrCertificate().parseKeysOrCertificates(empty); + isTrue(certs.isEmpty()); + } + + private void testParse2CertsCertificateCollection(OpenPGPApi api) + throws IOException + { + OpenPGPCertificate alice = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT); + OpenPGPCertificate bob = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.BOB_CERT); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = ArmoredOutputStream.builder().clearHeaders().build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + alice.getPGPPublicKeyRing().encode(pOut); + bob.getPGPPublicKeyRing().encode(pOut); + pOut.close(); + aOut.close(); + + List certs = api.readKeyOrCertificate().parseKeysOrCertificates(bOut.toByteArray()); + isEquals("Collection MUST contain both items", 2, certs.size()); + + isEquals(alice.getKeyIdentifier(), certs.get(0).getKeyIdentifier()); + isEquals(bob.getKeyIdentifier(), certs.get(1).getKeyIdentifier()); + } + + private void testParseCertAndKeyToCertificateCollection(OpenPGPApi api) + throws IOException + { + OpenPGPCertificate alice = api.readKeyOrCertificate().parseCertificate(OpenPGPTestKeys.ALICE_CERT); + OpenPGPKey bob = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.BOB_KEY); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = ArmoredOutputStream.builder().clearHeaders().build(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + alice.getPGPPublicKeyRing().encode(pOut); + bob.getPGPSecretKeyRing().encode(pOut); + pOut.close(); + aOut.close(); + + List certs = api.readKeyOrCertificate().parseKeysOrCertificates(bOut.toByteArray()); + isEquals("Collection MUST contain both items", 2, certs.size()); + + isEquals(alice.getKeyIdentifier(), certs.get(0).getKeyIdentifier()); + isFalse(certs.get(0).isSecretKey()); + + isEquals(bob.getKeyIdentifier(), certs.get(1).getKeyIdentifier()); + isTrue(certs.get(1).isSecretKey()); + } + + public static void main(String[] args) + { + runTest(new OpenPGPKeyReaderTest()); + } +} From dd8c3fda8a48e0940a6afcf5c140d0cff0e705c6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 10 Feb 2025 14:31:01 +0100 Subject: [PATCH 142/154] Improvements to OpenPGPCertificate, OpenPGPKey --- .../openpgp/api/OpenPGPCertificate.java | 108 +++++++++++++++++- .../bouncycastle/openpgp/api/OpenPGPKey.java | 5 + 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 9f2b92a504..8f7decc825 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -10,6 +10,7 @@ import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyExpirationTime; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; import org.bouncycastle.bcpg.sig.PreferredAlgorithms; @@ -340,6 +341,26 @@ public List getAllKeyIdentifiers() return identifiers; } + public OpenPGPComponentSignature getCertification() + { + return getCertification(new Date()); + } + + public OpenPGPComponentSignature getCertification(Date evaluationTime) + { + return primaryKey.getCertification(evaluationTime); + } + + public OpenPGPComponentSignature getRevocation() + { + return getRevocation(new Date()); + } + + public OpenPGPComponentSignature getRevocation(Date evaluationTime) + { + return primaryKey.getRevocation(evaluationTime); + } + /** * Return the last time, the key was modified (before right now). * A modification is the addition of a new subkey, or key signature. @@ -645,6 +666,23 @@ private boolean isBoundBy(OpenPGPCertificateComponent component, OpenPGPComponentKey root, Date evaluationTime) { + OpenPGPSignature.OpenPGPSignatureSubpacket keyExpiration = + component.getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.KEY_EXPIRE_TIME); + if (keyExpiration != null) + { + KeyExpirationTime kexp = (KeyExpirationTime) keyExpiration.getSubpacket(); + if (kexp.getTime() != 0) + { + OpenPGPComponentKey key = component.getKeyComponent(); + Date expirationDate = new Date(1000 * kexp.getTime() + key.getCreationTime().getTime()); + if (expirationDate.before(evaluationTime)) + { + // Key is expired. + return false; + } + } + } + try { OpenPGPSignatureChain chain = getSignatureChainFor(component, root, evaluationTime); @@ -961,6 +999,8 @@ protected OpenPGPCertificateComponent getPublicComponent() return this; } + protected abstract OpenPGPComponentKey getKeyComponent(); + /** * Return the {@link SignatureSubpacket} instance of the given subpacketType, which currently applies to * the key. Since subpackets from the Direct-Key signature apply to all subkeys of a certificate, @@ -1759,18 +1799,63 @@ public OpenPGPPrimaryKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) } } + public OpenPGPComponentSignature getLatestDirectKeySelfSignature() + { + return getLatestDirectKeySelfSignature(new Date()); + } + + public OpenPGPComponentSignature getLatestDirectKeySelfSignature(Date evaluationTime) + { + OpenPGPSignatureChain currentDKChain = getCertificate().getAllSignatureChainsFor(this) + .getCertificationAt(evaluationTime); + if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) + { + return currentDKChain.getHeadLink().getSignature(); + } + + return null; + } + + public OpenPGPComponentSignature getLatestKeyRevocationSignature() + { + return getLatestKeyRevocationSignature(new Date()); + } + + public OpenPGPComponentSignature getLatestKeyRevocationSignature(Date evaluationTime) + { + OpenPGPSignatureChain currentRevocationChain = getCertificate().getAllSignatureChainsFor(this) + .getRevocationAt(evaluationTime); + if (currentRevocationChain != null && !currentRevocationChain.chainLinks.isEmpty()) + { + return currentRevocationChain.getHeadLink().getSignature(); + } + return null; + } + @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { List signatures = new ArrayList<>(); - OpenPGPSignatureChain currentDKChain = getSignatureChains().getChainAt(evaluationTime); - if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) + + OpenPGPComponentSignature directKeySig = getLatestDirectKeySelfSignature(evaluationTime); + if (directKeySig != null) + { + signatures.add(directKeySig); + } + + OpenPGPComponentSignature keyRevocation = getLatestKeyRevocationSignature(evaluationTime); + if (keyRevocation != null) { - signatures.add(currentDKChain.getHeadLink().getSignature()); + signatures.add(keyRevocation); } + for (OpenPGPIdentityComponent identity : getCertificate().getIdentities()) { - signatures.add(identity.getLatestSelfSignature(evaluationTime)); + OpenPGPComponentSignature identitySig = identity.getLatestSelfSignature(evaluationTime); + if (identitySig != null) + { + signatures.add(identitySig); + } } OpenPGPComponentSignature latest = null; @@ -1784,6 +1869,11 @@ public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) return latest; } + @Override + protected OpenPGPComponentKey getKeyComponent() { + return this; + } + /** * Return all {@link OpenPGPUserId OpenPGPUserIds} on this key. * @@ -2012,6 +2102,11 @@ public String toDetailString() return "Subkey[" + getKeyIdentifier() + "] (" + UTCUtil.format(getCreationTime()) + ")"; } + @Override + protected OpenPGPComponentKey getKeyComponent() { + return this; + } + /** * Return all subkey-binding and -revocation signatures on the subkey. * @@ -2075,6 +2170,11 @@ public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) return null; } + @Override + protected OpenPGPComponentKey getKeyComponent() { + return primaryKey; + } + @Override public String toDetailString() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index f0da9369a7..8b3311949f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -234,6 +234,11 @@ protected OpenPGPCertificateComponent getPublicComponent() return pubKey; } + @Override + protected OpenPGPComponentKey getKeyComponent() { + return this; + } + @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { From 4e9fbb00cfd7d0fc7552190dd0c44d7f0a7384f0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Feb 2025 16:05:06 +0100 Subject: [PATCH 143/154] Fix NPE when extracting KeyIdentifier from v3 signatures --- .../java/org/bouncycastle/openpgp/PGPSignature.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java index 9617db1336..7d724cd725 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java @@ -637,8 +637,15 @@ public long getKeyID() public List getKeyIdentifiers() { List identifiers = new ArrayList(); - identifiers.addAll(getHashedKeyIdentifiers()); - identifiers.addAll(getUnhashedKeyIdentifiers()); + if (getVersion() <= SignaturePacket.VERSION_3) + { + identifiers.add(new KeyIdentifier(getKeyID())); + } + else + { + identifiers.addAll(getHashedKeyIdentifiers()); + identifiers.addAll(getUnhashedKeyIdentifiers()); + } return identifiers; } From d21346309ddbbb18ac3e9669382bcc51432bda63 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 13:04:26 +0100 Subject: [PATCH 144/154] Add isPrimaryKey() to OpenPGPComponentKey --- .../openpgp/api/OpenPGPCertificate.java | 19 +++++++++++++++++++ .../bouncycastle/openpgp/api/OpenPGPKey.java | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 8f7decc825..3238c0fcc6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1462,6 +1462,13 @@ public Date getCreationTime() return rawPubkey.getCreationTime(); } + /** + * Return true, if this {@link OpenPGPComponentKey} represents the primary key of an {@link OpenPGPCertificate}. + * + * @return true if primary, false if subkey + */ + public abstract boolean isPrimaryKey(); + @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { @@ -1799,6 +1806,12 @@ public OpenPGPPrimaryKey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) } } + @Override + public boolean isPrimaryKey() + { + return true; + } + public OpenPGPComponentSignature getLatestDirectKeySelfSignature() { return getLatestDirectKeySelfSignature(new Date()); @@ -2090,6 +2103,12 @@ public OpenPGPSubkey(PGPPublicKey rawPubkey, OpenPGPCertificate certificate) super(rawPubkey, certificate); } + @Override + public boolean isPrimaryKey() + { + return false; + } + @Override public String toString() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 8b3311949f..f40b065912 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -239,6 +239,12 @@ protected OpenPGPComponentKey getKeyComponent() { return this; } + @Override + public boolean isPrimaryKey() + { + return getPublicKey().isPrimaryKey(); + } + @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { From 4f501edb12ec52e3932f3f14cd1ff588728ffcc6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 13:05:04 +0100 Subject: [PATCH 145/154] Fix checkstyle issues --- .../openpgp/api/OpenPGPCertificate.java | 15 ++++++++++----- .../org/bouncycastle/openpgp/api/OpenPGPKey.java | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 3238c0fcc6..6f85b2a766 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1745,12 +1745,14 @@ public Date getKeyExpirationDateAt(Date evaluationTime) } @Override - public int hashCode() { + public int hashCode() + { return getPGPPublicKey().hashCode(); } @Override - public boolean equals(Object obj) { + public boolean equals(Object obj) + { if (obj == null) { return false; @@ -1883,7 +1885,8 @@ public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) } @Override - protected OpenPGPComponentKey getKeyComponent() { + protected OpenPGPComponentKey getKeyComponent() + { return this; } @@ -2122,7 +2125,8 @@ public String toDetailString() } @Override - protected OpenPGPComponentKey getKeyComponent() { + protected OpenPGPComponentKey getKeyComponent() + { return this; } @@ -2190,7 +2194,8 @@ public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) } @Override - protected OpenPGPComponentKey getKeyComponent() { + protected OpenPGPComponentKey getKeyComponent() + { return primaryKey; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index f40b065912..02009ae9ba 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -235,7 +235,8 @@ protected OpenPGPCertificateComponent getPublicComponent() } @Override - protected OpenPGPComponentKey getKeyComponent() { + protected OpenPGPComponentKey getKeyComponent() + { return this; } From a29e8e1deff45794330d02d5d12c731016305836 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 13:06:25 +0100 Subject: [PATCH 146/154] Introduce OpenPGPPrivateKey class --- ...ractOpenPGPDocumentSignatureGenerator.java | 2 +- .../bouncycastle/openpgp/api/OpenPGPKey.java | 111 +++++++++++++++++- .../openpgp/api/OpenPGPKeyEditor.java | 14 +-- .../openpgp/api/OpenPGPMessageProcessor.java | 2 +- 4 files changed, 115 insertions(+), 14 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java index 48dc7f5e81..64fdc096ff 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/AbstractOpenPGPDocumentSignatureGenerator.java @@ -250,7 +250,7 @@ protected PGPSignatureGenerator initSignatureGenerator( signingKey.getPGPPublicKey()); char[] passphrase = passphraseProvider.getKeyPassword(signingKey); - PGPKeyPair unlockedKey = signingKey.unlock(passphrase); + PGPKeyPair unlockedKey = signingKey.unlock(passphrase).getKeyPair(); if (unlockedKey == null) { throw new KeyPassphraseException(signingKey, new PGPException("Cannot unlock secret key.")); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 02009ae9ba..37a507ba6c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -2,6 +2,7 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PublicKeyPacket; @@ -16,6 +17,8 @@ import org.bouncycastle.openpgp.api.exception.KeyPassphraseException; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -292,7 +295,7 @@ public boolean isLocked() return getPGPSecretKey().getS2KUsage() != SecretKeyPacket.USAGE_NONE; } - public PGPKeyPair unlock(KeyPassphraseProvider passphraseProvider) + public OpenPGPPrivateKey unlock(KeyPassphraseProvider passphraseProvider) throws PGPException { if (!isLocked()) @@ -310,7 +313,7 @@ public PGPKeyPair unlock(KeyPassphraseProvider passphraseProvider) * @return keypair containing unlocked private key * @throws PGPException if the key cannot be unlocked */ - public PGPKeyPair unlock(char[] passphrase) + public OpenPGPPrivateKey unlock(char[] passphrase) throws PGPException { sanitizeProtectionMode(); @@ -328,7 +331,8 @@ public PGPKeyPair unlock(char[] passphrase) return null; } - return new PGPKeyPair(getPGPSecretKey().getPublicKey(), privateKey); + PGPKeyPair unlockedKey = new PGPKeyPair(getPGPSecretKey().getPublicKey(), privateKey); + return new OpenPGPPrivateKey(this, unlockedKey); } catch (PGPException e) { @@ -373,8 +377,8 @@ public boolean isPassphraseCorrect(char[] passphrase) { try { - PGPKeyPair unlocked = unlock(passphrase); - return unlocked != null; + OpenPGPPrivateKey privateKey = unlock(passphrase); + return privateKey.unlockedKey != null; } catch (PGPException e) { @@ -382,4 +386,101 @@ public boolean isPassphraseCorrect(char[] passphrase) } } } + + /** + * Unlocked {@link OpenPGPSecretKey}. + */ + public static class OpenPGPPrivateKey + { + private final OpenPGPSecretKey secretKey; + private final PGPKeyPair unlockedKey; + + public OpenPGPPrivateKey(OpenPGPSecretKey secretKey, PGPKeyPair unlockedKey) + { + this.secretKey = secretKey; + this.unlockedKey = unlockedKey; + } + + /** + * Return the {@link OpenPGPSecretKey} in its potentially locked form. + * + * @return secret key + */ + public OpenPGPSecretKey getSecretKey() + { + return secretKey; + } + + /** + * Return the unlocked {@link PGPKeyPair} containing the decrypted {@link PGPPrivateKey}. + * @return unlocked private key + */ + public PGPKeyPair getKeyPair() + { + return unlockedKey; + } + + private OpenPGPImplementation getImplementation() + { + return getSecretKey().getOpenPGPKey().implementation; + } + + public OpenPGPSecretKey changePassphrase(char[] newPassphrase) + throws PGPException + { + boolean useAead = !secretKey.isLocked() || + secretKey.getPGPSecretKey().getS2KUsage() == SecretKeyPacket.USAGE_AEAD; + + return changePassphrase(newPassphrase, getImplementation(), useAead); + } + + public OpenPGPSecretKey changePassphrase(char[] newPassphrase, + OpenPGPImplementation implementation, + boolean useAEAD) + throws PGPException + { + return changePassphrase(newPassphrase, implementation.pbeSecretKeyEncryptorFactory(useAEAD)); + } + + public OpenPGPSecretKey changePassphrase(char[] newPassphrase, + PBESecretKeyEncryptorFactory keyEncryptorFactory) + throws PGPException + { + PBESecretKeyEncryptor keyEncryptor; + if (newPassphrase == null || newPassphrase.length == 0) + { + keyEncryptor = null; + } + else + { + keyEncryptor = keyEncryptorFactory.build( + newPassphrase, + getKeyPair().getPublicKey().getPublicKeyPacket()); + } + + return changePassphrase(keyEncryptor); + } + + public OpenPGPSecretKey changePassphrase(PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + PGPSecretKey encrypted = new PGPSecretKey( + getKeyPair().getPrivateKey(), + getKeyPair().getPublicKey(), + getImplementation().pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1), + getSecretKey().isPrimaryKey(), + keyEncryptor); + + return new OpenPGPSecretKey( + getSecretKey().getPublicKey(), + encrypted, + getImplementation().pbeSecretKeyDecryptorBuilderProvider()); + } + + public OpenPGPSecretKey removePassphrase() + throws PGPException + { + return changePassphrase((PBESecretKeyEncryptor) null); + } + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index aedff09e31..066cd84166 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -26,7 +26,7 @@ public class OpenPGPKeyEditor private final OpenPGPImplementation implementation; private final OpenPGPPolicy policy; private OpenPGPKey key; - private final PGPKeyPair primaryKey; + private final OpenPGPKey.OpenPGPPrivateKey primaryKey; public OpenPGPKeyEditor(OpenPGPKey key, KeyPassphraseProvider passphraseProvider) throws PGPException @@ -72,7 +72,7 @@ public OpenPGPKeyEditor addDirectKeySignature(SignatureParameters.Callback signa publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - dkSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + dkSigGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -145,7 +145,7 @@ public OpenPGPKeyEditor addUserId(String userId, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - uidSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + uidSigGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -216,7 +216,7 @@ public OpenPGPKeyEditor revokeIdentity(OpenPGPCertificate.OpenPGPIdentityCompone publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - idSigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + idSigGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -299,7 +299,7 @@ public OpenPGPKeyEditor addEncryptionSubkey(PGPKeyPair encryptionSubkey, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - subKeySigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + subKeySigGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -405,7 +405,7 @@ public OpenPGPKeyEditor addSigningSubkey(PGPKeyPair signingSubkey, publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - subKeySigGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + subKeySigGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); @@ -489,7 +489,7 @@ public OpenPGPKeyEditor revokeComponentKey(OpenPGPCertificate.OpenPGPComponentKe publicPrimaryKey.getAlgorithm(), parameters.getSignatureHashAlgorithmId()), publicPrimaryKey); - revGen.init(parameters.getSignatureType(), primaryKey.getPrivateKey()); + revGen.init(parameters.getSignatureType(), primaryKey.getKeyPair().getPrivateKey()); // Hashed subpackets PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java index 38d6f44c25..ee8a4f9792 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPMessageProcessor.java @@ -357,7 +357,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList) } char[] keyPassphrase = configuration.keyPassphraseProvider.getKeyPassword(decryptionKey); - PGPKeyPair unlockedKey = decryptionKey.unlock(keyPassphrase); + PGPKeyPair unlockedKey = decryptionKey.unlock(keyPassphrase).getKeyPair(); if (unlockedKey == null) { throw new KeyPassphraseException(decryptionKey, new PGPException("Cannot unlock secret key.")); From c8441272217eaff7a9509d506812fd1e96d16fb1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 13:23:31 +0100 Subject: [PATCH 147/154] SecretKeyPacket: Properly pass newPacketFormat down to PublicKeyPacket --- pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java index d2f9f8873c..9b59fd4f5c 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java @@ -146,11 +146,11 @@ public class SecretKeyPacket if (this instanceof SecretSubkeyPacket) { - pubKeyPacket = new PublicSubkeyPacket(in); + pubKeyPacket = new PublicSubkeyPacket(in, newPacketFormat); } else { - pubKeyPacket = new PublicKeyPacket(in); + pubKeyPacket = new PublicKeyPacket(in, newPacketFormat); } int version = pubKeyPacket.getVersion(); @@ -342,7 +342,7 @@ public SecretKeyPacket( byte[] iv, byte[] secKeyData) { - super(keyTag); + super(keyTag, pubKeyPacket.hasNewPacketFormat()); this.pubKeyPacket = pubKeyPacket; this.encAlgorithm = encAlgorithm; From ac7c60122a9c55a94b0602106a80b1b81ca66e9c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 14:18:05 +0100 Subject: [PATCH 148/154] Add test for changing individual key passphrases --- .../api/test/ChangeKeyPassphraseTest.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java new file mode 100644 index 0000000000..bcf0d5ce59 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java @@ -0,0 +1,118 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.openpgp.OpenPGPTestKeys; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPKey; + +import java.io.IOException; + +public class ChangeKeyPassphraseTest + extends APITest +{ + @Override + protected void performTestWith(OpenPGPApi api) + throws PGPException, IOException + { + removeAEADPassphrase(api); + addAEADPassphrase(api); + changeAEADPassphrase(api); + + testChangingCFBPassphrase(api); + } + + private void removeAEADPassphrase(OpenPGPApi api) + throws IOException, PGPException { + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(OpenPGPTestKeys.V6_KEY_LOCKED); + + OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); + isTrue("Expect test key to be locked initially", secretKey.isLocked()); + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock(OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()); + OpenPGPKey.OpenPGPSecretKey unlocked = privateKey.removePassphrase(); + isFalse("Expect key to be unlocked after unlocking - duh", unlocked.isLocked()); + + OpenPGPKey expected = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + isEncodingEqual("Expect unlocked key encoding to equal the unprotected test vector", + expected.getPrimarySecretKey().getPGPSecretKey().getEncoded(), + unlocked.getPGPSecretKey().getEncoded()); + } + + private void addAEADPassphrase(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY); + OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); + isFalse("Expect unlocked test vector to be unlocked", secretKey.isLocked()); + + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock((char[]) null); + OpenPGPKey.OpenPGPSecretKey locked = privateKey.changePassphrase( + "sw0rdf1sh".toCharArray(), + api.getImplementation(), + true); + isTrue("Expect test key to be locked after locking", locked.isLocked()); + isEquals("Expect locked key to use AEAD", + SecretKeyPacket.USAGE_AEAD, locked.getPGPSecretKey().getS2KUsage()); + isTrue("Expect key to be unlockable with used passphrase", + locked.isPassphraseCorrect("sw0rdf1sh".toCharArray())); + } + + private void changeAEADPassphrase(OpenPGPApi api) + throws IOException, PGPException + { + OpenPGPKey key = api.readKeyOrCertificate() + .parseKey(OpenPGPTestKeys.V6_KEY_LOCKED); + + OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); + isTrue("Expect locked test vector to be locked initially", + secretKey.isLocked()); + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock(OpenPGPTestKeys.V6_KEY_LOCKED_PASSPHRASE.toCharArray()); + OpenPGPKey.OpenPGPSecretKey relocked = privateKey.changePassphrase("sw0rdf1sh".toCharArray()); + isTrue("Expect key to still be locked after changing passphrase", relocked.isLocked()); + isTrue("Expect key to be unlockable with used passphrase", + relocked.isPassphraseCorrect("sw0rdf1sh".toCharArray())); + isEquals("Expect re-locked key to use AEAD", + relocked.getPGPSecretKey().getS2KUsage(), SecretKeyPacket.USAGE_AEAD); + } + + private void testChangingCFBPassphrase(OpenPGPApi api) + throws PGPException, IOException + { + OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.ALICE_KEY); + + OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); + isFalse("Expect Alice' key to not be locked initially", secretKey.isLocked()); + + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock((char[]) null); + OpenPGPKey.OpenPGPSecretKey locked = privateKey.changePassphrase( + "sw0rdf1sh".toCharArray(), api.getImplementation(), false); + isTrue("Expect Alice' key to be locked after locking", locked.isLocked()); + isEquals("Expect CFB mode to be used for locking, since we did not use AEAD.", + locked.getPGPSecretKey().getS2KUsage(), SecretKeyPacket.USAGE_SHA1); + isTrue("Expect key to be unlockable with used passphrase", + locked.isPassphraseCorrect("sw0rdf1sh".toCharArray())); + + privateKey = locked.unlock("sw0rdf1sh".toCharArray()); + OpenPGPKey.OpenPGPSecretKey relocked = privateKey.changePassphrase("0r4ng3".toCharArray()); + isEquals("Expect CFB to be used after changing passphrase of CFB-protected key", + relocked.getPGPSecretKey().getS2KUsage(), SecretKeyPacket.USAGE_SHA1); + isTrue("Expect key to be unlockable with new passphrase", + relocked.isPassphraseCorrect("0r4ng3".toCharArray())); + + privateKey = relocked.unlock("0r4ng3".toCharArray()); + OpenPGPKey.OpenPGPSecretKey unlocked = privateKey.removePassphrase(); + isFalse("Expect key to be unlocked after removing passphrase", unlocked.isLocked()); + } + + @Override + public String getName() + { + return "ChangeKeyPassphraseTest"; + } + + public static void main(String[] args) + { + runTest(new ChangeKeyPassphraseTest()); + } +} From f64fee9a574e130724cdecd569a0a810dbf0bf98 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 14:38:04 +0100 Subject: [PATCH 149/154] Use new passphrase-change API in OpenPGPKeyEditor --- .../org/bouncycastle/openpgp/api/OpenPGPKey.java | 5 +++++ .../bouncycastle/openpgp/api/OpenPGPKeyEditor.java | 14 +++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 37a507ba6c..cb4f0c7831 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -166,6 +166,11 @@ public OpenPGPSecretKey getSecretKey(OpenPGPComponentKey key) return getSecretKey(key.getKeyIdentifier()); } + void replaceSecretKey(OpenPGPSecretKey secretKey) + { + secretKeys.put(secretKey.getKeyIdentifier(), secretKey); + } + @Override public PGPSecretKeyRing getPGPKeyRing() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java index 066cd84166..76ba096a55 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -560,18 +560,10 @@ public OpenPGPKeyEditor changePassphrase(OpenPGPCertificate.OpenPGPComponentKey " is missing from the key."); } - PGPSecretKeyRing secretKeys = key.getPGPKeyRing(); - PGPSecretKey reencrypted = PGPSecretKey.copyWithNewPassword( - secretKey.getPGPSecretKey(), - implementation.pbeSecretKeyDecryptorBuilderProvider().provide().build(oldPassphrase), - implementation.pbeSecretKeyEncryptorFactory(useAEAD) - .build( - newPassphrase, - secretKey.getPGPSecretKey().getPublicKey().getPublicKeyPacket()), - implementation.pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1)); - secretKeys = PGPSecretKeyRing.insertSecretKey(secretKeys, reencrypted); - key = new OpenPGPKey(secretKeys, implementation, policy); + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock(oldPassphrase); + secretKey = privateKey.changePassphrase(newPassphrase, implementation, useAEAD); + key.replaceSecretKey(secretKey); return this; } From 7e5bfe7c0c93a88207906a141f7a747061dd468b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Feb 2025 15:00:15 +0100 Subject: [PATCH 150/154] Make sure, that after locking, the key uses a proper protection method --- .../java/org/bouncycastle/openpgp/api/OpenPGPKey.java | 10 +++++++++- .../openpgp/api/test/ChangeKeyPassphraseTest.java | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index cb4f0c7831..2451f0dd73 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -300,6 +300,12 @@ public boolean isLocked() return getPGPSecretKey().getS2KUsage() != SecretKeyPacket.USAGE_NONE; } + public OpenPGPPrivateKey unlock() + throws PGPException + { + return unlock((char[]) null); + } + public OpenPGPPrivateKey unlock(KeyPassphraseProvider passphraseProvider) throws PGPException { @@ -476,10 +482,12 @@ public OpenPGPSecretKey changePassphrase(PBESecretKeyEncryptor keyEncryptor) getSecretKey().isPrimaryKey(), keyEncryptor); - return new OpenPGPSecretKey( + OpenPGPSecretKey sk = new OpenPGPSecretKey( getSecretKey().getPublicKey(), encrypted, getImplementation().pbeSecretKeyDecryptorBuilderProvider()); + sk.sanitizeProtectionMode(); + return sk; } public OpenPGPSecretKey removePassphrase() diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java index bcf0d5ce59..deeee69728 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/ChangeKeyPassphraseTest.java @@ -46,7 +46,7 @@ private void addAEADPassphrase(OpenPGPApi api) OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); isFalse("Expect unlocked test vector to be unlocked", secretKey.isLocked()); - OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock((char[]) null); + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock(); OpenPGPKey.OpenPGPSecretKey locked = privateKey.changePassphrase( "sw0rdf1sh".toCharArray(), api.getImplementation(), @@ -84,7 +84,7 @@ private void testChangingCFBPassphrase(OpenPGPApi api) OpenPGPKey.OpenPGPSecretKey secretKey = key.getPrimarySecretKey(); isFalse("Expect Alice' key to not be locked initially", secretKey.isLocked()); - OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock((char[]) null); + OpenPGPKey.OpenPGPPrivateKey privateKey = secretKey.unlock(); OpenPGPKey.OpenPGPSecretKey locked = privateKey.changePassphrase( "sw0rdf1sh".toCharArray(), api.getImplementation(), false); isTrue("Expect Alice' key to be locked after locking", locked.isLocked()); From 317c865f6749d7a724c7fa75a46ca232ad37578f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Feb 2025 12:24:55 +0100 Subject: [PATCH 151/154] OpenPGPCertificate: Store subkeys in LinkedHashMap to preserve ordering --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 6f85b2a766..625b8f8591 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -115,7 +115,7 @@ public OpenPGPCertificate(PGPKeyRing keyRing, OpenPGPImplementation implementati this.policy = policy; this.keyRing = keyRing; - this.subkeys = new HashMap<>(); + this.subkeys = new LinkedHashMap<>(); this.componentSignatureChains = new LinkedHashMap<>(); Iterator rawKeys = keyRing.getPublicKeys(); @@ -184,7 +184,7 @@ public OpenPGPPrimaryKey getPrimaryKey() */ public Map getSubkeys() { - return new HashMap<>(subkeys); + return new LinkedHashMap<>(subkeys); } public List getComponentKeysWithFlag(Date evaluationTime, int... keyFlags) From 7917ff84e40532633c7aed9db656a14b4c6d8337 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Feb 2025 13:54:11 +0100 Subject: [PATCH 152/154] Properly parse certs or keys from concatenated armored blocks --- .../org/bouncycastle/openpgp/api/OpenPGPKeyReader.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java index 5f546c7b33..a71c1e5420 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyReader.java @@ -229,8 +229,11 @@ public List parseKeysOrCertificates(byte[] bytes) ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); InputStream decoderStream = PGPUtil.getDecoderStream(bIn); - BCPGInputStream pIn = BCPGInputStream.wrap(decoderStream); - PGPObjectFactory objectFactory = implementation.pgpObjectFactory(pIn); + // Call getDecoderStream() twice, to make sure the stream is a BufferedInputStreamExt. + // This is necessary, so that for streams containing multiple concatenated armored blocks of keys, + // we parse all of them and do not quit after reading the first one. + decoderStream = PGPUtil.getDecoderStream(decoderStream); + PGPObjectFactory objectFactory = implementation.pgpObjectFactory(decoderStream); Object object; while ((object = objectFactory.nextObject()) != null) From 3fbddb777d0bb6cb818ea773554d9847eeb3b26b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 19 Feb 2025 15:35:28 +0100 Subject: [PATCH 153/154] OpenPGPPrivateKey: Add getPublicKey() method --- .../main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java index 2451f0dd73..2d41cd4333 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -412,6 +412,11 @@ public OpenPGPPrivateKey(OpenPGPSecretKey secretKey, PGPKeyPair unlockedKey) this.unlockedKey = unlockedKey; } + public OpenPGPComponentKey getPublicKey() + { + return secretKey.getPublicKey(); + } + /** * Return the {@link OpenPGPSecretKey} in its potentially locked form. * From a85eb1828db51a5cd3c7ca006da423d7aa9ddf82 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 24 Feb 2025 14:42:16 +0100 Subject: [PATCH 154/154] Move preferences getters from ComponentKey to CertificateComponent This allows getting preferences for UserIDs as well --- .../openpgp/api/OpenPGPCertificate.java | 377 +++++++++--------- 1 file changed, 196 insertions(+), 181 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java index 625b8f8591..cea68aef2d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1001,6 +1001,202 @@ protected OpenPGPCertificateComponent getPublicComponent() protected abstract OpenPGPComponentKey getKeyComponent(); + /** + * Return the {@link KeyFlags} signature subpacket that currently applies to the key. + * @return key flags subpacket + */ + public KeyFlags getKeyFlags() + { + return getKeyFlags(new Date()); + } + + /** + * Return the {@link KeyFlags} signature subpacket that - at evaluation time - applies to the key. + * @param evaluationTime evaluation time + * @return key flags subpacket + */ + public KeyFlags getKeyFlags(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket( + evaluationTime, SignatureSubpacketTags.KEY_FLAGS); + if (subpacket != null) + { + return (KeyFlags) subpacket.getSubpacket(); + } + return null; + } + + /** + * Return
true
, if the key has any of the given key flags. + *

+ * Note: To check if the key has EITHER flag A or B, call

hasKeyFlags(evalTime, A, B)
. + * To instead check, if the key has BOTH flags A AND B, call
hasKeyFlags(evalTime, A & B)
. + * + * @param evaluationTime evaluation time + * @param flags key flags (see {@link KeyFlags} for possible values) + * @return true if the key has ANY of the provided flags + */ + public boolean hasKeyFlags(Date evaluationTime, int... flags) + { + KeyFlags keyFlags = getKeyFlags(evaluationTime); + if (keyFlags == null) + { + // Key has no key-flags + return false; + } + + // Check if key has the desired key-flags + for (int f : flags) + { + if (((keyFlags.getFlags() & f) == f)) + { + return true; + } + } + return false; + } + + /** + * Return the {@link Features} signature subpacket that currently applies to the key. + * @return feature signature subpacket + */ + public Features getFeatures() + { + return getFeatures(new Date()); + } + + /** + * Return the {@link Features} signature subpacket that - at evaluation time - applies to the key. + * @param evaluationTime evaluation time + * @return features subpacket + */ + public Features getFeatures(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.FEATURES); + if (subpacket != null) + { + return (Features) subpacket.getSubpacket(); + } + return null; + } + + /** + * Return the {@link PreferredAEADCiphersuites} that apply to this (sub-)key. + * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. + * + * @return AEAD algorithm preferences + */ + public PreferredAEADCiphersuites getAEADCipherSuitePreferences() + { + return getAEADCipherSuitePreferences(new Date()); + } + + /** + * Return the {@link PreferredAEADCiphersuites} that - at evaluation time - apply to this (sub-)key. + * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. + * + * @param evaluationTime evaluation time + * @return AEAD algorithm preferences at evaluation time + */ + public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, + SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + if (subpacket != null) + { + return (PreferredAEADCiphersuites) subpacket.getSubpacket(); + } + return null; + } + + /** + * Return the current symmetric encryption algorithm preferences of this (sub-)key. + * + * @return current preferred symmetric-key algorithm preferences + */ + public PreferredAlgorithms getSymmetricCipherPreferences() + { + return getSymmetricCipherPreferences(new Date()); + } + + /** + * Return the symmetric encryption algorithm preferences of this (sub-)key at evaluation time. + * + * @param evaluationTime evaluation time + * @return current preferred symmetric-key algorithm preferences + */ + public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); + if (subpacket != null) + { + return (PreferredAlgorithms) subpacket.getSubpacket(); + } + return null; + } + + /** + * Return the current signature hash algorithm preferences of this (sub-)key. + * + * @return hash algorithm preferences + */ + public PreferredAlgorithms getHashAlgorithmPreferences() + { + return getHashAlgorithmPreferences(new Date()); + } + + /** + * Return the signature hash algorithm preferences of this (sub-)key at evaluation time. + * + * @param evaluationTime evaluation time + * @return hash algorithm preferences + */ + public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); + if (subpacket != null) + { + return (PreferredAlgorithms) subpacket.getSubpacket(); + } + return null; + } + + public PreferredAlgorithms getCompressionAlgorithmPreferences() + { + return getCompressionAlgorithmPreferences(new Date()); + } + + public PreferredAlgorithms getCompressionAlgorithmPreferences(Date evaluationTime) + { + OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_COMP_ALGS); + if (subpacket != null) + { + return (PreferredAlgorithms) subpacket.getSubpacket(); + } + return null; + } + + /** + * Return the {@link Date}, at which the key expires. + * + * @return key expiration time + */ + public Date getKeyExpirationDate() + { + return getKeyExpirationDateAt(new Date()); + } + + /** + * Return the {@link Date}, at which the key - at evaluation time - expires. + * + * @param evaluationTime evaluation time + * @return key expiration time + */ + public Date getKeyExpirationDateAt(Date evaluationTime) + { + return getLatestSelfSignature(evaluationTime).getKeyExpirationTime(); + } + /** * Return the {@link SignatureSubpacket} instance of the given subpacketType, which currently applies to * the key. Since subpackets from the Direct-Key signature apply to all subkeys of a certificate, @@ -1563,187 +1759,6 @@ public boolean isCertificationKey(Date evaluationTime) return hasKeyFlags(evaluationTime, KeyFlags.CERTIFY_OTHER); } - /** - * Return the {@link KeyFlags} signature subpacket that currently applies to the key. - * @return key flags subpacket - */ - public KeyFlags getKeyFlags() - { - return getKeyFlags(new Date()); - } - - /** - * Return the {@link KeyFlags} signature subpacket that - at evaluation time - applies to the key. - * @param evaluationTime evaluation time - * @return key flags subpacket - */ - public KeyFlags getKeyFlags(Date evaluationTime) - { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket( - evaluationTime, SignatureSubpacketTags.KEY_FLAGS); - if (subpacket != null) - { - return (KeyFlags) subpacket.getSubpacket(); - } - return null; - } - - /** - * Return
true
, if the key has any of the given key flags. - *

- * Note: To check if the key has EITHER flag A or B, call

hasKeyFlags(evalTime, A, B)
. - * To instead check, if the key has BOTH flags A AND B, call
hasKeyFlags(evalTime, A & B)
. - * - * @param evaluationTime evaluation time - * @param flags key flags (see {@link KeyFlags} for possible values) - * @return true if the key has ANY of the provided flags - */ - public boolean hasKeyFlags(Date evaluationTime, int... flags) - { - KeyFlags keyFlags = getKeyFlags(evaluationTime); - if (keyFlags == null) - { - // Key has no key-flags - return false; - } - - // Check if key has the desired key-flags - for (int f : flags) - { - if (((keyFlags.getFlags() & f) == f)) - { - return true; - } - } - return false; - } - - /** - * Return the {@link Features} signature subpacket that currently applies to the key. - * @return feature signature subpacket - */ - public Features getFeatures() - { - return getFeatures(new Date()); - } - - /** - * Return the {@link Features} signature subpacket that - at evaluation time - applies to the key. - * @param evaluationTime evaluation time - * @return features subpacket - */ - public Features getFeatures(Date evaluationTime) - { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.FEATURES); - if (subpacket != null) - { - return (Features) subpacket.getSubpacket(); - } - return null; - } - - /** - * Return the {@link PreferredAEADCiphersuites} that apply to this (sub-)key. - * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. - * - * @return AEAD algorithm preferences - */ - public PreferredAEADCiphersuites getAEADCipherSuitePreferences() - { - return getAEADCipherSuitePreferences(new Date()); - } - - /** - * Return the {@link PreferredAEADCiphersuites} that - at evaluation time - apply to this (sub-)key. - * Note: This refers to AEAD preferences as defined in rfc9580, NOT LibrePGP AEAD algorithms. - * - * @param evaluationTime evaluation time - * @return AEAD algorithm preferences at evaluation time - */ - public PreferredAEADCiphersuites getAEADCipherSuitePreferences(Date evaluationTime) - { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, - SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); - if (subpacket != null) - { - return (PreferredAEADCiphersuites) subpacket.getSubpacket(); - } - return null; - } - - /** - * Return the current symmetric encryption algorithm preferences of this (sub-)key. - * - * @return current preferred symmetric-key algorithm preferences - */ - public PreferredAlgorithms getSymmetricCipherPreferences() - { - return getSymmetricCipherPreferences(new Date()); - } - - /** - * Return the symmetric encryption algorithm preferences of this (sub-)key at evaluation time. - * - * @param evaluationTime evaluation time - * @return current preferred symmetric-key algorithm preferences - */ - public PreferredAlgorithms getSymmetricCipherPreferences(Date evaluationTime) - { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_SYM_ALGS); - if (subpacket != null) - { - return (PreferredAlgorithms) subpacket.getSubpacket(); - } - return null; - } - - /** - * Return the current signature hash algorithm preferences of this (sub-)key. - * - * @return hash algorithm preferences - */ - public PreferredAlgorithms getHashAlgorithmPreferences() - { - return getHashAlgorithmPreferences(new Date()); - } - - /** - * Return the signature hash algorithm preferences of this (sub-)key at evaluation time. - * - * @param evaluationTime evaluation time - * @return hash algorithm preferences - */ - public PreferredAlgorithms getHashAlgorithmPreferences(Date evaluationTime) - { - OpenPGPSignature.OpenPGPSignatureSubpacket subpacket = getApplyingSubpacket(evaluationTime, SignatureSubpacketTags.PREFERRED_HASH_ALGS); - if (subpacket != null) - { - return (PreferredAlgorithms) subpacket.getSubpacket(); - } - return null; - } - - /** - * Return the {@link Date}, at which the key expires. - * - * @return key expiration time - */ - public Date getKeyExpirationDate() - { - return getKeyExpirationDateAt(new Date()); - } - - /** - * Return the {@link Date}, at which the key - at evaluation time - expires. - * - * @param evaluationTime evaluation time - * @return key expiration time - */ - public Date getKeyExpirationDateAt(Date evaluationTime) - { - return getLatestSelfSignature(evaluationTime).getKeyExpirationTime(); - } - @Override public int hashCode() {