From af5d7981c7e20814bef09bee3aa1f8defee2d274 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 14:38:54 +0100 Subject: [PATCH 001/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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" + + "hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtYodkyXN78BfGjVQ63\n" + + "G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO6ay66dGrlTTYS2MT\n" + + "ivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZaJnMBh2wdQpGdOA5g\n" + + "jG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TGHiUtB1ZcMHOovIik\n" + + "swtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa7aKpHz2M2zXwtG7d\n" + + "+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37EUqm0CJztIlp7uAyv\n" + + "SFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXzh/M+xWFLmsdbGhn/\n" + + "XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGarHPovqCi2Z+19GACO\n" + + "LRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7GeiUucLDOucgrTh3AA\n" + + "CAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnmQlHBRdBcmQSJBoxy\n" + + "FUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0XyR00AEQEAAcLCPAQY\n" + + "AQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2Vx\n" + + "dW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLKg0h1eacBHzMCmwLA\n" + + "vKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + + "LnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXAeYvxUUglSsm7i864\n" + + "FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33xS+kUUeB043pbKcu\n" + + "AN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9QtVnEuSS1cc1wHu3/i\n" + + "jn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2UGbXKbJ0NbiBwvTj\n" + + "cVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmrfb1BbhhuvJg4NAq0\n" + + "WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH+XlIbcQovX4O0o7x\n" + + "5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8C7G1UJ2C3fYIFiEE\n" + + "4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P485p1carRzmQwkpl\n" + + "KpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJzsLtMUMPzRBd4vNYe\n" + + "yInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3iKdnfycia6sqH+/C\n" + + "RQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH+POXhrmcVVnS0ZZQ\n" + + "2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1fGiDTNkSzLBpLj00b\n" + + "SEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJFZDE+biSbwsI8BBgB\n" + + "CgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1\n" + + "b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3iGpJSc3akDgKbAsC8\n" + + "oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu\n" + + "c2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrzevsNklOMRBvvkqgW\n" + + "IQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMVyioFRy9XRH84PYWp\n" + + "VWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9PxnnGOhO+6r4Q85gnJUm\n" + + "3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjARp+rIAD5k6jOVLAwq\n" + + "bBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acjOk4QQjIW0JEe4RPV\n" + + "1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDUvYUJHatGlnoTaEyX\n" + + "QrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ4mKVnPPQcH4WIQTj\n" + + "LLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/zUD0XnX+eOGCf2HU\n" + + "J73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRNzedN9SSSsBaQgevU\n" + + "bMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcGptEklxx6/yZGJubn\n" + + "1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4b8zpiWu3wwtLlGYU\n" + + "yhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bcf1Ngef/DdEPqSBaB\n" + + "LjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/976sXYWB8=\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" + + "xcSYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" + + "cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" + + "3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" + + "Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" + + "hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" + + "bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" + + "i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" + + "1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" + + "fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" + + "fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" + + "LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" + + "+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" + + "hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" + + "WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" + + "MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" + + "mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" + + "YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" + + "he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" + + "zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" + + "NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" + + "t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qizSFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" + + "ZW5wZ3AuZXhhbXBsZT7CwQ4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" + + "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" + + "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" + + "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" + + "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" + + "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" + + "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" + + "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" + + "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" + + "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikbH\n" + + "xJgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" + + "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" + + "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" + + "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" + + "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" + + "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" + + "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" + + "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" + + "IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" + + "SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" + + "OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" + + "Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" + + "+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" + + "tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" + + "BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" + + "zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" + + "clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" + + "zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" + + "gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" + + "aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" + + "fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" + + "ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" + + "HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" + + "SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" + + "5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" + + "E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" + + "GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" + + "vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" + + "26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hrCwPYEGAEKACAWIQTRpm4aI7GCyZgP\n" + + "eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" + + "c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" + + "rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" + + "JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" + + "71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" + + "s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" + + "NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" + + "6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" + + "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\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" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsEOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx\n" + + "gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz\n" + + "XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO\n" + + "ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g\n" + + "9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF\n" + + "DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c\n" + + "ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1\n" + + "6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ\n" + + "ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo\n" + + "zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGzsDNBF2lnPIBDADW\n" + + "ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI\n" + + "DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+\n" + + "Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO\n" + + "baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT\n" + + "86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh\n" + + "827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6\n" + + "vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U\n" + + "qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A\n" + + "EQEAAcLA9gQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ\n" + + "EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS\n" + + "KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx\n" + + "cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i\n" + + "tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV\n" + + "dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w\n" + + "qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy\n" + + "jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj\n" + + "zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV\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" + + "xcQTBF3+CmgRDADZhdKTM3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0\n" + + "OJz2vh59nusbBLzgI//Y1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vh\n" + + "yVeJt0k/NnxvNhMd0587KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0Uj\n" + + "REWs5Jpj/XU9LhEoyXZkeJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcG\n" + + "zYgeMNOvdWJwn43dNhxoeuXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7\n" + + "MNuQx/ejIMZHl+Iaf7hG976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9\n" + + "+4dq6ybUM65tnozRyyN+1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpX\n" + + "duVd32MA33UVNH5/KXMVczVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0\n" + + "SFhlfnBEUj1my1sBAMOSO/I67BvBS3IPHZWXHjgclhs26mPzRlZLryAUWR2DDACH\n" + + "5fx+yUAdZ8Vu/2zWTHxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwS\n" + + "KJUBSA75HExbv0naWg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwp\n" + + "dr1ZwEbb3L6IGQ5i/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdP\n" + + "xGhM8w6a18+fdQr22f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV8\n" + + "2hP4K+rb9FwknYdV9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzo\n" + + "mYmaTO7mp6xFAu43yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4\n" + + "xwfOQ7pf3kC7r9fm8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnU\n" + + "yQs4ksAfIHTzTdLttRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL\n" + + "/0rUAxwegTNIG/5M612s2a45QvYK1turZ7spI1RGitJUIjBXUuR76jIsyqagIhBl\n" + + "5nEsQ4HLv8OQ3EgJ5T9gldLFpHNczLxBQnnNwfPoD2e0kC/iy0rfiNX8HWpTgQpb\n" + + "zAosLj5/E0iNlildynIhuqBosyRWFqGva0O6qioL90srlzlfKCloe9R9w3HizjCb\n" + + "f59yEspuJt9iHVNOPOW2Wj5ub0KTiJPp9vBmrFaB79/IlgojpQoYvQ77Hx5A9CJq\n" + + "paMCHGOW6Uz9euN1ozzETEkIPtL8XAxcogfpe2JKE1uS7ugxsKEGEDfxOQFKAGV0\n" + + "XFtIx50vFCr2vQro0WB858CGN47dCxChhNUxNtGc11JNEkNv/X7hKtRf/5VCmnaz\n" + + "GWwNK47cqZ7GJfEBnElD7s/tQvTC5Qp7lg9gEt47TUX0bjzUTCxNvLosuKL9+J1W\n" + + "ln1myRpff/5ZOAnZTPHR+AbX4bRB4sK5zijQe4139Dn2oRYK+EIYoBAxFxSOzehP\n" + + "IQAA/2BCN5HryGjVff2t7Q6fVrQQS9hsMisszZl5rWwUOO6zETHCigQfEQgAPAUC\n" + + "Xf4KaQMLCQoJEJunidx21oSaBBUKCQgCFgECF4ACGwMCHgEWIQRx/9oARAnl3bDD\n" + + "6PGbp4ncdtaEmgAAYoUA/1VpxdR2wYT/pC8FrKsbmIxLJRLDNlED3ihivWp/B2e/\n" + + "AQCT2oi9zqbjprCKAnzoIYTGTil4yFfmeey8GjMOxUHz4M0mQ2Fyb2wgT2xkc3R5\n" + + "bGUgPGNhcm9sQG9wZW5wZ3AuZXhhbXBsZT7CigQTEQgAPAUCXf4KaQMLCQoJEJun\n" + + "idx21oSaBBUKCQgCFgECF4ACGwMCHgEWIQRx/9oARAnl3bDD6PGbp4ncdtaEmgAA\n" + + "UEwA/2TFwL0mymjCSaQH8KdQuygI+itpNggM+Y8FF8hn9fo1AP9ogDIl9V3C8t59\n" + + "C/Mrc4HvP1ABR2nwZeK5+A5lLoH4Y8fD8QRd/gpoEAwA2YXSkzN5rN16V50JHvNx\n" + + "YGiAbT9YNaoaqQn4OdFoj0tJI4jAtDic9r4efZ7rGwS84CP/2NVTISnyFmG6jHCG\n" + + "PpVm7Hh45edq6lugGidEx+DYFbe74clXibdJPzZ8bzYTHdOfOyl5n6Q8a8AanP5e\n" + + "XFQfqdKy/L7PJMaIx1wIuVd5KDNFI0RFrOSaY/11PS4RKMl2ZHiQv6XrNbulCqBW\n" + + "J+3RSD+PSpHdZG/tWzX3T2LQNCaXBs2IHjDTr3VicJ+N3TYcaHrl35gBIQPC3c09\n" + + "AtDvu2pFzilq34VyfDEwarz4FmWMezDbkMf3oyDGR5fiGn+4Rve+iCx/jQhoipIY\n" + + "nXfRiLgP1rXh4kG1y8n4kOJ/D9dqvfuHausm1DOubZ6M0csjftZt61Nmv/i8tyQo\n" + + "eE3jtu8PnMTFpGnh8k0GiVTGzGw6V3blXd9jAN91FTR+fylzFXM1YuWrFY7ig0qI\n" + + "yQ1dUMF/Is2TZdbfgCNC922pQmm1dEhYZX5wRFI9ZstbDACH5fx+yUAdZ8Vu/2zW\n" + + "THxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwSKJUBSA75HExbv0na\n" + + "Wg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwpdr1ZwEbb3L6IGQ5i\n" + + "/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdPxGhM8w6a18+fdQr2\n" + + "2f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV82hP4K+rb9FwknYdV\n" + + "9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzomYmaTO7mp6xFAu43\n" + + "yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4xwfOQ7pf3kC7r9fm\n" + + "8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnUyQs4ksAfIHTzTdLt\n" + + "tRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL/jEGmn1tLhxfjfDA\n" + + "5vFFj73+FXdFCdFKSI0VpdoU1fgR5DX72ZQUYYUCKYTYikXv1mqdH/5VthptrktC\n" + + "oAco4zVxM04sK7Xthl+uTOhei8/Dd9ZLdSIoNcRjrr/uh5sUzUfIC9iuT3SXiZ/D\n" + + "0yVq0Uu/gWPB3ZIG/sFacxOXAr6RYhvz9MqnwXS1sVT5TyO3XIQ5JseIgIRyV/Sf\n" + + "4F/4Qui9wMzzSajTwCsttMGKf67k228AaJVv+IpFoo+OtCa7wbJukqfNQN3m2ojf\n" + + "V5CcoCzsoRsoTInhrpQmM+gGoQBXBArT1xk3KK3VdZibYfMoxeIGXw0MoNJzFuGK\n" + + "+PcnhV3ETFMNcszd0Pb9s86g7hYtpRmE12Jlai2MzPSmyztlsRP9tcZwYy7JdPZf\n" + + "xXQP24XWat7eP2qWxTnkEP4/wKYb81m7CZ4RvUO/nd1aA5c9IBYknbgmCAAKvHVD\n" + + "iTY61E5GbC9aTiI4WIwjItroikukUJE+p77rpjxfw/1U51BnmQAA/ih5jIthn2ZE\n" + + "r1YoOsUs8CBhylTsRZK6VS4ZCErcyl2tD2LCigQYEQgAPAUCXf4KaQMLCQoJEJun\n" + + "idx21oSaBBUKCQgCFgECF4ACGwwCHgEWIQRx/9oARAnl3bDD6PGbp4ncdtaEmgAA\n" + + "QSkA/3WEWqZxvZmpVxpEMxJWaGQRwUhGake8OhC1WfywCtarAQCLwfBsyEv5jBEi\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" + + "xsPuBF3+CmgRDADZhdKTM3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0\n" + + "OJz2vh59nusbBLzgI//Y1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vh\n" + + "yVeJt0k/NnxvNhMd0587KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0Uj\n" + + "REWs5Jpj/XU9LhEoyXZkeJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcG\n" + + "zYgeMNOvdWJwn43dNhxoeuXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7\n" + + "MNuQx/ejIMZHl+Iaf7hG976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9\n" + + "+4dq6ybUM65tnozRyyN+1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpX\n" + + "duVd32MA33UVNH5/KXMVczVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0\n" + + "SFhlfnBEUj1my1sBAMOSO/I67BvBS3IPHZWXHjgclhs26mPzRlZLryAUWR2DDACH\n" + + "5fx+yUAdZ8Vu/2zWTHxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwS\n" + + "KJUBSA75HExbv0naWg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwp\n" + + "dr1ZwEbb3L6IGQ5i/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdP\n" + + "xGhM8w6a18+fdQr22f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV8\n" + + "2hP4K+rb9FwknYdV9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzo\n" + + "mYmaTO7mp6xFAu43yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4\n" + + "xwfOQ7pf3kC7r9fm8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnU\n" + + "yQs4ksAfIHTzTdLttRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL\n" + + "/0rUAxwegTNIG/5M612s2a45QvYK1turZ7spI1RGitJUIjBXUuR76jIsyqagIhBl\n" + + "5nEsQ4HLv8OQ3EgJ5T9gldLFpHNczLxBQnnNwfPoD2e0kC/iy0rfiNX8HWpTgQpb\n" + + "zAosLj5/E0iNlildynIhuqBosyRWFqGva0O6qioL90srlzlfKCloe9R9w3HizjCb\n" + + "f59yEspuJt9iHVNOPOW2Wj5ub0KTiJPp9vBmrFaB79/IlgojpQoYvQ77Hx5A9CJq\n" + + "paMCHGOW6Uz9euN1ozzETEkIPtL8XAxcogfpe2JKE1uS7ugxsKEGEDfxOQFKAGV0\n" + + "XFtIx50vFCr2vQro0WB858CGN47dCxChhNUxNtGc11JNEkNv/X7hKtRf/5VCmnaz\n" + + "GWwNK47cqZ7GJfEBnElD7s/tQvTC5Qp7lg9gEt47TUX0bjzUTCxNvLosuKL9+J1W\n" + + "ln1myRpff/5ZOAnZTPHR+AbX4bRB4sK5zijQe4139Dn2oRYK+EIYoBAxFxSOzehP\n" + + "IcKKBB8RCAA8BQJd/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYh\n" + + "BHH/2gBECeXdsMPo8Zunidx21oSaAABihQD/VWnF1HbBhP+kLwWsqxuYjEslEsM2\n" + + "UQPeKGK9an8HZ78BAJPaiL3OpuOmsIoCfOghhMZOKXjIV+Z57LwaMw7FQfPgzSZD\n" + + "YXJvbCBPbGRzdHlsZSA8Y2Fyb2xAb3BlbnBncC5leGFtcGxlPsKKBBMRCAA8BQJd\n" + + "/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYhBHH/2gBECeXdsMPo\n" + + "8Zunidx21oSaAABQTAD/ZMXAvSbKaMJJpAfwp1C7KAj6K2k2CAz5jwUXyGf1+jUA\n" + + "/2iAMiX1XcLy3n0L8ytzge8/UAFHafBl4rn4DmUugfhjzsPMBF3+CmgQDADZhdKT\n" + + "M3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0OJz2vh59nusbBLzgI//Y\n" + + "1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vhyVeJt0k/NnxvNhMd0587\n" + + "KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0UjREWs5Jpj/XU9LhEoyXZk\n" + + "eJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcGzYgeMNOvdWJwn43dNhxo\n" + + "euXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7MNuQx/ejIMZHl+Iaf7hG\n" + + "976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9+4dq6ybUM65tnozRyyN+\n" + + "1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpXduVd32MA33UVNH5/KXMV\n" + + "czVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0SFhlfnBEUj1my1sMAIfl\n" + + "/H7JQB1nxW7/bNZMfHBYn9fqAZMupr0KZ8OrlQOpgUXO5bA3gcn6vI65qTUIbBIo\n" + + "lQFIDvkcTFu/SdpaD6y7L6kQO8XRUAs9T1VSRJC0fJHXRg7YVY57cAS2ltgNHCl2\n" + + "vVnARtvcvogZDmL/gI0dsna7fJR5ewM0C+ulVIRwiMDTVE8I4qZ/nxINmnjIN0/E\n" + + "aEzzDprXz591CvbZ/ZwnTGB8+VvMVs74VSwSAq+fpBMuFtpjDjOzut1AN6NYdXza\n" + + "E/gr6tv0XCSdh1X26jibvsyAaVT7jK8mcYRhovePCMjdsf1qig06Xpdu9UDM3OiZ\n" + + "iZpM7uanrEUC7jfK4bJ30r7UTiTsJBNE7FNn5F21CNX3mFKwSYyDv3adC8NIFbjH\n" + + "B85Dul/eQLuv1+by72cGUQ3XYextDxi+7H+V3mrlFoiUPX2PN9VHr6EnNuPZmdTJ\n" + + "CziSwB8gdPNN0u21HFL2VNFORXHa9tSehIHLpNgXWZ/qdE+lKbWuJnGeRHj4FAv+\n" + + "MQaafW0uHF+N8MDm8UWPvf4Vd0UJ0UpIjRWl2hTV+BHkNfvZlBRhhQIphNiKRe/W\n" + + "ap0f/lW2Gm2uS0KgByjjNXEzTiwrte2GX65M6F6Lz8N31kt1Iig1xGOuv+6HmxTN\n" + + "R8gL2K5PdJeJn8PTJWrRS7+BY8Hdkgb+wVpzE5cCvpFiG/P0yqfBdLWxVPlPI7dc\n" + + "hDkmx4iAhHJX9J/gX/hC6L3AzPNJqNPAKy20wYp/ruTbbwBolW/4ikWij460JrvB\n" + + "sm6Sp81A3ebaiN9XkJygLOyhGyhMieGulCYz6AahAFcECtPXGTcordV1mJth8yjF\n" + + "4gZfDQyg0nMW4Yr49yeFXcRMUw1yzN3Q9v2zzqDuFi2lGYTXYmVqLYzM9KbLO2Wx\n" + + "E/21xnBjLsl09l/FdA/bhdZq3t4/apbFOeQQ/j/AphvzWbsJnhG9Q7+d3VoDlz0g\n" + + "FiSduCYIAAq8dUOJNjrUTkZsL1pOIjhYjCMi2uiKS6RQkT6nvuumPF/D/VTnUGeZ\n" + + "wooEGBEIADwFAl3+CmkDCwkKCRCbp4ncdtaEmgQVCgkIAhYBAheAAhsMAh4BFiEE\n" + + "cf/aAEQJ5d2ww+jxm6eJ3HbWhJoAAEEpAP91hFqmcb2ZqVcaRDMSVmhkEcFIRmpH\n" + + "vDoQtVn8sArWqwEAi8HwbMhL+YwRItRZDknpC4vFjTHVMd1zMrz/JyeuT9k=\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" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwMQEHwEKAHgFgl4L4QAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z4csZe1ah1tj2AjxfdDMsH2wvSEwZjb/73ICKnm7BySQAhUKApsDAh4BFiEE4yy2\n" + + "2oICkbfnbbGoCK1RyuRw8AYAAGYFCACiKnCb2NBZa/Jj1aJe4R2rxPZj2ERXWe3b\n" + + "JKNPKT7K0rVDkTw1JRiTfCsuAY2lY9sKJdhQZl+azXm64vvTc6hEGRQ/+XssDlE2\n" + + "DIn8C34HDc495ZnryHNB8Dd5l1HdjqxfGIY6HBPJUdx0dedwP42Oisg9t5KsC8zl\n" + + "d/+MIRgzkp+Dg0LXJVnDuwWEPoo2N6WhAr5ReLvXxALX5ht9Lb3lP0DASZvAKy9B\n" + + "O/wRCr294J8dg/CowAfloyf0Ko+JjyjanmZn3acy5CGkVN2mc+PFUekGZDDy5ooY\n" + + "kgXO/CmApuTNvabct+A7IVVdWWM5SWb90JvaV9SWji6nQphVm7StwsDEBB8BCgB4\n" + + "BYJaSXoACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lh\n" + + "LXBncC5vcmfVZdjLYZxDX2hvy3aGrsE4i0avLDMzf3e9kVHmaD6PAgIVCgKbAwIe\n" + + "ARYhBOMsttqCApG3522xqAitUcrkcPAGAABQYwgArfIRxq95npUKAOPXs25nZlvy\n" + + "+xQbrmsTxHhAYW8eGFcz82QwumoqrR8VfrojxM+eCZdTI85nM5kzznYDU2+cMhsZ\n" + + "Vm5+VhGZy3e3QH4J/E31D7t1opCvj5g1eRJ4LgywB+cYGcZBYp/bQT9SUYuhZH2O\n" + + "XCR04qSbpVUCIApnhBHxKNtOlqjAkHeaOdW/8XePsbfvrtVOLGYgrZXfY7Nqy3+W\n" + + "zbdm8UvVPFXH+uHEzTgyvYbnJBYkjORmCqUKs860PL8ekeg+sL4PHSRj1UUfwcQD\n" + + "55q0m3Vtew2KiIUi4wKi5LceDtprjoO5utU/1YfEAiNMeSQHXKq83dpazvjrUs0S\n" + + "anVsaWV0QGV4YW1wbGUub3JnwsDEBBMBCgB4BYJaSXoACRAIrVHK5HDwBkcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmc6Rix7CeIfWwnaQjk3\n" + + "bBrkAiY7jS9N+shuRdHZ0gKKsgIVCgKbAwIeARYhBOMsttqCApG3522xqAitUcrk\n" + + "cPAGAACf9QgAsxtfAbyGbtofjrXTs9lsKEWvGgk02fSYyKjPbyaRqh72MlIlUXwq\n" + + "q1ih2TJc3vwF8aNVDrcb9DnBabdt2M1vI3PUaeG31BmakC/XZCNCrbbJkyd/vdML\n" + + "qw7prLrp0auVNNhLYxOK9usXbClNxluo4i/lSFVo5B9ai+ne1kKKiplzqy2qqhde\n" + + "plomcwGHbB1CkZ04DmCMbSSFAGxYqUC/bBm0bolCebw/KIz9sEojNKt6mvsFN67/\n" + + "hMYeJS0HVlwwc6i8iKSzC2D53iywhtvkdiKECXQeXDf9zNXAn1wpK01SLJ0iig7c\n" + + "DFrtoqkfPYzbNfC0bt34fNx9iz3w9aEH8c7ATQRaSsuAAQgAu5yau9psltmWiUn7\n" + + "fsRSqbQInO0iWnu4DK9IXB3ghNYMcii3JJEjHzgIxGf3GiJEjzubyRQaX5J/p7yB\n" + + "1fOH8z7FYUuax1saGf9c1/b02N9gyXNlHam31hNaaL3ffFczI95p7MNrTtroTt5o\n" + + "Zqsc+i+oKLZn7X0YAI4tEYwhSnUQYB/F7YqkkI4eV+7CxZPA8pBhXiAOK/zn416P\n" + + "sZ6JS5wsM65yCtOHcAAIBnKDnC+bQi+f1WZesSocy/rXx3QEQmodDu3ojhS+VxcY\n" + + "GeZCUcFF0FyZBIkGjHIVQLyOfjP3FRJ4qFXMz9/YIVoM4Y6guTERMTEj/KDG4BP7\n" + + "RfJHTQARAQABwsI8BBgBCgHwBYJeC+EACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0\n" + + "QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfcAa1ZPWTtg60w3Oo4dt4Fa8cKFYbZ\n" + + "YsqDSHV5pwEfMwKbAsC8oAQZAQoAbwWCXgvhAAkQEPy8/w6Op5FHFAAAAAAAHgAg\n" + + "c2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnL6I2+VyN5T1FoVgj3cdnMLYC\n" + + "pcB5i/FRSCVKybuLzrgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAArk8H/AhjM9lq\n" + + "bffFL6RRR4HTjelspy4A3nyTicCljrDuXDUh23GfLvajTR5h16ZBqAF7cpb9rrlz\n" + + "1C1WcS5JLVxzXAe7f+KOfXu+eyLhpTzZ8VT3pK3hHGaYwlVlXrBZP0JXgL8hm6hD\n" + + "SXZQZtcpsnQ1uIHC9ONxUB4liNFhTqQCQYdQJFiFs1umUbo/C4KdzlDI08bM3CqE\n" + + "Kat9vUFuGG68mDg0CrRZEWt946L5i8kZmBUkSShIm2k5e2qE/muYeM6qKQNsxlx3\n" + + "VIf5eUhtxCi9fg7SjvHkdUSFstYcxAdaohWCFCEsDJI12hzcKQazSjvtKF4BNBKg\n" + + "X/wLsbVQnYLd9ggWIQTjLLbaggKRt+dtsagIrVHK5HDwBgAANjMH/1MY7DJyxkiT\n" + + "jc/jzmnVxqtHOZDCSmUqk0eh/6BHs+ostWqkGC6+7dfxDnptwcqandYey4KF2ajt\n" + + "4nOwu0xQw/NEF3i81h7IiewY7G+YT69DUd+DvVUQemfKNYVOrMqoH7QU5o4YojdJ\n" + + "iDeIp2d/JyJrqyof78JFAHnNZgHC2T2zo9E54dnOTY9VNUNCOUct5Rby0GXjTIUR\n" + + "O0f485eGuZxVWdLRllDYOiCrQHPSHhrxHVXVMbYJoroPy+IyaJanVoAWgyipBmmI\n" + + "DV8aINM2RLMsGkuPTRtITI2ZlGOQN7xgy4LqWzjPnrzMXfwBEDx/nrwdG6zEGMK8\n" + + "AkVkMT5uJJvCwjwEGAEKAfAFglro/4AJEAitUcrkcPAGRxQAAAAAAB4AIHNhbHRA\n" + + "bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ/Q0Z6WDH2+8/F1xEEuiApsjnn2lGNZ2\n" + + "DeIaklJzdqQOApsCwLygBBkBCgBvBYJa6P+ACRAQ/Lz/Do6nkUcUAAAAAAAeACBz\n" + + "YWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfrVATyX3tgcM2z41fqYquxVhJR\n" + + "avN6+w2SU4xEG++SqBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAABGVggAsB8M2KI5\n" + + "cxXKKgVHL1dEfzg9halVavktfcT6ZVC/+aDp94tvBCL16Guhq4ccN7DATrWx430/\n" + + "GecY6E77qvhDzmCclSbdLbiZmsrVX9kCmTfrJzFQ64KfvIS5GgbL21+ZJ+pKW2HO\n" + + "MBGn6sgAPmTqM5UsDCpsEKDt5CJcJr3sTc8D9NhEnc0dKsQ91+n9ms3W5tyyE6r9\n" + + "pyM6ThBCMhbQkR7hE9XWAQeO1ILSFGnie0aFcTU0Oo0wL1MaiSyA/8XpKq23xfx1\n" + + "kNS9hQkdq0aWehNoTJdCt1Nq1cWABy2rQR0x+qhGWowfsAjnBautxvet28t2kPCA\n" + + "IMniYpWc89BwfhYhBOMsttqCApG3522xqAitUcrkcPAGAACq1gf/Q7H9Re5SWk+U\n" + + "On/NQPRedf544YJ/YdQnve/hSaPGL33cUzf4yxzFILnK19Ird5f8/mTT1pg99L3i\n" + + "xE3N5031JJKwFpCB69Rsysg88ZLDL2VLc3xdsAQdUbVaCqeRHKwtMtpBvbAFvF9p\n" + + "lwam0SSXHHr/JkYm5ufXN6I8ib/nwr1bFbf/Se0Wuk9RG4ne9JUBCrGxakyVd+Og\n" + + "LLhvzOmJa7fDC0uUZhTKFbjMxLhaas4HFYiRbfz2T0xz9gyDytDWsEFM+XoKHlEH\n" + + "8Fx/U2B5/8N0Q+pIFoEuOmBO+5EPvPIlxNByHgiaNIuKt1Mu+UAb2Spl6D5zbDfX\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" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwMQEHwEKAHgFgl4L4QAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z4csZe1ah1tj2AjxfdDMsH2wvSEwZjb/73ICKnm7BySQAhUKApsDAh4BFiEE4yy2\n" + + "2oICkbfnbbGoCK1RyuRw8AYAAGYFCACiKnCb2NBZa/Jj1aJe4R2rxPZj2ERXWe3b\n" + + "JKNPKT7K0rVDkTw1JRiTfCsuAY2lY9sKJdhQZl+azXm64vvTc6hEGRQ/+XssDlE2\n" + + "DIn8C34HDc495ZnryHNB8Dd5l1HdjqxfGIY6HBPJUdx0dedwP42Oisg9t5KsC8zl\n" + + "d/+MIRgzkp+Dg0LXJVnDuwWEPoo2N6WhAr5ReLvXxALX5ht9Lb3lP0DASZvAKy9B\n" + + "O/wRCr294J8dg/CowAfloyf0Ko+JjyjanmZn3acy5CGkVN2mc+PFUekGZDDy5ooY\n" + + "kgXO/CmApuTNvabct+A7IVVdWWM5SWb90JvaV9SWji6nQphVm7StwsDEBB8BCgB4\n" + + "BYJaSXoACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lh\n" + + "LXBncC5vcmfVZdjLYZxDX2hvy3aGrsE4i0avLDMzf3e9kVHmaD6PAgIVCgKbAwIe\n" + + "ARYhBOMsttqCApG3522xqAitUcrkcPAGAABQYwgArfIRxq95npUKAOPXs25nZlvy\n" + + "+xQbrmsTxHhAYW8eGFcz82QwumoqrR8VfrojxM+eCZdTI85nM5kzznYDU2+cMhsZ\n" + + "Vm5+VhGZy3e3QH4J/E31D7t1opCvj5g1eRJ4LgywB+cYGcZBYp/bQT9SUYuhZH2O\n" + + "XCR04qSbpVUCIApnhBHxKNtOlqjAkHeaOdW/8XePsbfvrtVOLGYgrZXfY7Nqy3+W\n" + + "zbdm8UvVPFXH+uHEzTgyvYbnJBYkjORmCqUKs860PL8ekeg+sL4PHSRj1UUfwcQD\n" + + "55q0m3Vtew2KiIUi4wKi5LceDtprjoO5utU/1YfEAiNMeSQHXKq83dpazvjrUs0S\n" + + "anVsaWV0QGV4YW1wbGUub3JnwsDEBBMBCgB4BYJaSXoACRAIrVHK5HDwBkcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmc6Rix7CeIfWwnaQjk3\n" + + "bBrkAiY7jS9N+shuRdHZ0gKKsgIVCgKbAwIeARYhBOMsttqCApG3522xqAitUcrk\n" + + "cPAGAACf9QgAsxtfAbyGbtofjrXTs9lsKEWvGgk02fSYyKjPbyaRqh72MlIlUXwq\n" + + "q1ih2TJc3vwF8aNVDrcb9DnBabdt2M1vI3PUaeG31BmakC/XZCNCrbbJkyd/vdML\n" + + "qw7prLrp0auVNNhLYxOK9usXbClNxluo4i/lSFVo5B9ai+ne1kKKiplzqy2qqhde\n" + + "plomcwGHbB1CkZ04DmCMbSSFAGxYqUC/bBm0bolCebw/KIz9sEojNKt6mvsFN67/\n" + + "hMYeJS0HVlwwc6i8iKSzC2D53iywhtvkdiKECXQeXDf9zNXAn1wpK01SLJ0iig7c\n" + + "DFrtoqkfPYzbNfC0bt34fNx9iz3w9aEH8c7ATQRaSsuAAQgAu5yau9psltmWiUn7\n" + + "fsRSqbQInO0iWnu4DK9IXB3ghNYMcii3JJEjHzgIxGf3GiJEjzubyRQaX5J/p7yB\n" + + "1fOH8z7FYUuax1saGf9c1/b02N9gyXNlHam31hNaaL3ffFczI95p7MNrTtroTt5o\n" + + "Zqsc+i+oKLZn7X0YAI4tEYwhSnUQYB/F7YqkkI4eV+7CxZPA8pBhXiAOK/zn416P\n" + + "sZ6JS5wsM65yCtOHcAAIBnKDnC+bQi+f1WZesSocy/rXx3QEQmodDu3ojhS+VxcY\n" + + "GeZCUcFF0FyZBIkGjHIVQLyOfjP3FRJ4qFXMz9/YIVoM4Y6guTERMTEj/KDG4BP7\n" + + "RfJHTQARAQABwsI8BBgBCgHwBYJeC+EACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0\n" + + "QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfcAa1ZPWTtg60w3Oo4dt4Fa8cKFYbZ\n" + + "YsqDSHV5pwEfMwKbAsC8oAQZAQoAbwWCXgvhAAkQEPy8/w6Op5FHFAAAAAAAHgAg\n" + + "c2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnL6I2+VyN5T1FoVgj3cdnMLYC\n" + + "pcB5i/FRSCVKybuLzrgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAArk8H/AhjM9lq\n" + + "bffFL6RRR4HTjelspy4A3nyTicCljrDuXDUh23GfLvajTR5h16ZBqAF7cpb9rrlz\n" + + "1C1WcS5JLVxzXAe7f+KOfXu+eyLhpTzZ8VT3pK3hHGaYwlVlXrBZP0JXgL8hm6hD\n" + + "SXZQZtcpsnQ1uIHC9ONxUB4liNFhTqQCQYdQJFiFs1umUbo/C4KdzlDI08bM3CqE\n" + + "Kat9vUFuGG68mDg0CrRZEWt946L5i8kZmBUkSShIm2k5e2qE/muYeM6qKQNsxlx3\n" + + "VIf5eUhtxCi9fg7SjvHkdUSFstYcxAdaohWCFCEsDJI12hzcKQazSjvtKF4BNBKg\n" + + "X/wLsbVQnYLd9ggWIQTjLLbaggKRt+dtsagIrVHK5HDwBgAANjMH/1MY7DJyxkiT\n" + + "jc/jzmnVxqtHOZDCSmUqk0eh/6BHs+ostWqkGC6+7dfxDnptwcqandYey4KF2ajt\n" + + "4nOwu0xQw/NEF3i81h7IiewY7G+YT69DUd+DvVUQemfKNYVOrMqoH7QU5o4YojdJ\n" + + "iDeIp2d/JyJrqyof78JFAHnNZgHC2T2zo9E54dnOTY9VNUNCOUct5Rby0GXjTIUR\n" + + "O0f485eGuZxVWdLRllDYOiCrQHPSHhrxHVXVMbYJoroPy+IyaJanVoAWgyipBmmI\n" + + "DV8aINM2RLMsGkuPTRtITI2ZlGOQN7xgy4LqWzjPnrzMXfwBEDx/nrwdG6zEGMK8\n" + + "AkVkMT5uJJvCwjwEGAEKAfAFglro/4AJEAitUcrkcPAGRxQAAAAAAB4AIHNhbHRA\n" + + "bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ/Q0Z6WDH2+8/F1xEEuiApsjnn2lGNZ2\n" + + "DeIaklJzdqQOApsCwLygBBkBCgBvBYJa6P+ACRAQ/Lz/Do6nkUcUAAAAAAAeACBz\n" + + "YWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfrVATyX3tgcM2z41fqYquxVhJR\n" + + "avN6+w2SU4xEG++SqBYhBM6mENCE+lHyEeHEbBD8vP8OjqeRAABGVggAsB8M2KI5\n" + + "cxXKKgVHL1dEfzg9halVavktfcT6ZVC/+aDp94tvBCL16Guhq4ccN7DATrWx430/\n" + + "GecY6E77qvhDzmCclSbdLbiZmsrVX9kCmTfrJzFQ64KfvIS5GgbL21+ZJ+pKW2HO\n" + + "MBGn6sgAPmTqM5UsDCpsEKDt5CJcJr3sTc8D9NhEnc0dKsQ91+n9ms3W5tyyE6r9\n" + + "pyM6ThBCMhbQkR7hE9XWAQeO1ILSFGnie0aFcTU0Oo0wL1MaiSyA/8XpKq23xfx1\n" + + "kNS9hQkdq0aWehNoTJdCt1Nq1cWABy2rQR0x+qhGWowfsAjnBautxvet28t2kPCA\n" + + "IMniYpWc89BwfhYhBOMsttqCApG3522xqAitUcrkcPAGAACq1gf/Q7H9Re5SWk+U\n" + + "On/NQPRedf544YJ/YdQnve/hSaPGL33cUzf4yxzFILnK19Ird5f8/mTT1pg99L3i\n" + + "xE3N5031JJKwFpCB69Rsysg88ZLDL2VLc3xdsAQdUbVaCqeRHKwtMtpBvbAFvF9p\n" + + "lwam0SSXHHr/JkYm5ufXN6I8ib/nwr1bFbf/Se0Wuk9RG4ne9JUBCrGxakyVd+Og\n" + + "LLhvzOmJa7fDC0uUZhTKFbjMxLhaas4HFYiRbfz2T0xz9gyDytDWsEFM+XoKHlEH\n" + + "8Fx/U2B5/8N0Q+pIFoEuOmBO+5EPvPIlxNByHgiaNIuKt1Mu+UAb2Spl6D5zbDfX\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" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwLsEIAEKAG8FglwqrYAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z4KjdWVHTHye8HeUynibpgE5TYfFnnBt9bbOj99oplaTFiEE4yy22oICkbfnbbGo\n" + + "CK1RyuRw8AYAAMxeB/4+QAncX1+678HeO1fweQ0Zkf4O6+Ew6EgCp4I2UZu+a5H8\n" + + "ryI3B4WNShCDoV3CfOcUtUSUA8EOyrpYSW/3jPVfb01uxDNsZpf9piZG7DelIAef\n" + + "wvQaZHJeytchv5+Wo+Jo6qg26BgvUlXW2x5NNcScGvCZt1RQ712PRDAfUnppRXBj\n" + + "+IXWzOs52uYGFDFzJSLEUy6dtTdNCJk78EMoHsOwC7g5uUyHbjSfrdQncxgMwikl\n" + + "C2LFSS7xYZwDgkkb70AT10Ot2jL6rLIT/1ChQZ0oRGJLBHiz3FUpanDQIDD49+dp\n" + + "6FUmUUsubwwFkxBHyCbQ8cdbfBILNiD1pEo31dPTwsDEBB8BCgB4BYJeC+EACRAI\n" + + "rVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeH\n" + + "LGXtWodbY9gI8X3QzLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqC\n" + + "ApG3522xqAitUcrkcPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySj\n" + + "Tyk+ytK1Q5E8NSUYk3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ\n" + + "/At+Bw3OPeWZ68hzQfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/\n" + + "jCEYM5Kfg4NC1yVZw7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8\n" + + "EQq9veCfHYPwqMAH5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIF\n" + + "zvwpgKbkzb2m3LfgOyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWC\n" + + "Wkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w\n" + + "Z3Aub3Jn1WXYy2GcQ19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEW\n" + + "IQTjLLbaggKRt+dtsagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsU\n" + + "G65rE8R4QGFvHhhXM/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZu\n" + + "flYRmct3t0B+CfxN9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwk\n" + + "dOKkm6VVAiAKZ4QR8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23\n" + + "ZvFL1TxVx/rhxM04Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+ea\n" + + "tJt1bXsNioiFIuMCouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1\n" + + "bGlldEBleGFtcGxlLm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAA\n" + + "HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa\n" + + "5AImO40vTfrIbkXR2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDw\n" + + "BgAAn/UIALMbXwG8hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtY\n" + + "odkyXN78BfGjVQ63G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO\n" + + "6ay66dGrlTTYS2MTivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZa\n" + + "JnMBh2wdQpGdOA5gjG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TG\n" + + "HiUtB1ZcMHOovIikswtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa\n" + + "7aKpHz2M2zXwtG7d+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37E\n" + + "Uqm0CJztIlp7uAyvSFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXz\n" + + "h/M+xWFLmsdbGhn/XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGar\n" + + "HPovqCi2Z+19GACOLRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7Ge\n" + + "iUucLDOucgrTh3AACAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnm\n" + + "QlHBRdBcmQSJBoxyFUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0Xy\n" + + "R00AEQEAAcLCPAQYAQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBu\n" + + "b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLK\n" + + "g0h1eacBHzMCmwLAvKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNh\n" + + "bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXA\n" + + "eYvxUUglSsm7i864FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33\n" + + "xS+kUUeB043pbKcuAN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9Qt\n" + + "VnEuSS1cc1wHu3/ijn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2\n" + + "UGbXKbJ0NbiBwvTjcVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmr\n" + + "fb1BbhhuvJg4NAq0WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH\n" + + "+XlIbcQovX4O0o7x5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8\n" + + "C7G1UJ2C3fYIFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P\n" + + "485p1carRzmQwkplKpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJz\n" + + "sLtMUMPzRBd4vNYeyInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3\n" + + "iKdnfycia6sqH+/CRQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH\n" + + "+POXhrmcVVnS0ZZQ2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1f\n" + + "GiDTNkSzLBpLj00bSEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJF\n" + + "ZDE+biSbwsI8BBgBCgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3i\n" + + "GpJSc3akDgKbAsC8oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrz\n" + + "evsNklOMRBvvkqgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMV\n" + + "yioFRy9XRH84PYWpVWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9Pxnn\n" + + "GOhO+6r4Q85gnJUm3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjAR\n" + + "p+rIAD5k6jOVLAwqbBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acj\n" + + "Ok4QQjIW0JEe4RPV1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDU\n" + + "vYUJHatGlnoTaEyXQrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ\n" + + "4mKVnPPQcH4WIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/\n" + + "zUD0XnX+eOGCf2HUJ73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRN\n" + + "zedN9SSSsBaQgevUbMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcG\n" + + "ptEklxx6/yZGJubn1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4\n" + + "b8zpiWu3wwtLlGYUyhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bc\n" + + "f1Ngef/DdEPqSBaBLjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/97\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" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwLsEIAEKAG8FglwqrYAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z4KjdWVHTHye8HeUynibpgE5TYfFnnBt9bbOj99oplaTFiEE4yy22oICkbfnbbGo\n" + + "CK1RyuRw8AYAAMxeB/4+QAncX1+678HeO1fweQ0Zkf4O6+Ew6EgCp4I2UZu+a5H8\n" + + "ryI3B4WNShCDoV3CfOcUtUSUA8EOyrpYSW/3jPVfb01uxDNsZpf9piZG7DelIAef\n" + + "wvQaZHJeytchv5+Wo+Jo6qg26BgvUlXW2x5NNcScGvCZt1RQ712PRDAfUnppRXBj\n" + + "+IXWzOs52uYGFDFzJSLEUy6dtTdNCJk78EMoHsOwC7g5uUyHbjSfrdQncxgMwikl\n" + + "C2LFSS7xYZwDgkkb70AT10Ot2jL6rLIT/1ChQZ0oRGJLBHiz3FUpanDQIDD49+dp\n" + + "6FUmUUsubwwFkxBHyCbQ8cdbfBILNiD1pEo31dPTwsDEBB8BCgB4BYJeC+EACRAI\n" + + "rVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeH\n" + + "LGXtWodbY9gI8X3QzLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqC\n" + + "ApG3522xqAitUcrkcPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySj\n" + + "Tyk+ytK1Q5E8NSUYk3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ\n" + + "/At+Bw3OPeWZ68hzQfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/\n" + + "jCEYM5Kfg4NC1yVZw7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8\n" + + "EQq9veCfHYPwqMAH5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIF\n" + + "zvwpgKbkzb2m3LfgOyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWC\n" + + "Wkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w\n" + + "Z3Aub3Jn1WXYy2GcQ19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEW\n" + + "IQTjLLbaggKRt+dtsagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsU\n" + + "G65rE8R4QGFvHhhXM/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZu\n" + + "flYRmct3t0B+CfxN9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwk\n" + + "dOKkm6VVAiAKZ4QR8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23\n" + + "ZvFL1TxVx/rhxM04Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+ea\n" + + "tJt1bXsNioiFIuMCouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1\n" + + "bGlldEBleGFtcGxlLm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAA\n" + + "HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa\n" + + "5AImO40vTfrIbkXR2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDw\n" + + "BgAAn/UIALMbXwG8hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtY\n" + + "odkyXN78BfGjVQ63G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO\n" + + "6ay66dGrlTTYS2MTivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZa\n" + + "JnMBh2wdQpGdOA5gjG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TG\n" + + "HiUtB1ZcMHOovIikswtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa\n" + + "7aKpHz2M2zXwtG7d+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37E\n" + + "Uqm0CJztIlp7uAyvSFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXz\n" + + "h/M+xWFLmsdbGhn/XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGar\n" + + "HPovqCi2Z+19GACOLRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7Ge\n" + + "iUucLDOucgrTh3AACAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnm\n" + + "QlHBRdBcmQSJBoxyFUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0Xy\n" + + "R00AEQEAAcLCPAQYAQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBu\n" + + "b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLK\n" + + "g0h1eacBHzMCmwLAvKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNh\n" + + "bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXA\n" + + "eYvxUUglSsm7i864FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33\n" + + "xS+kUUeB043pbKcuAN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9Qt\n" + + "VnEuSS1cc1wHu3/ijn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2\n" + + "UGbXKbJ0NbiBwvTjcVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmr\n" + + "fb1BbhhuvJg4NAq0WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH\n" + + "+XlIbcQovX4O0o7x5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8\n" + + "C7G1UJ2C3fYIFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P\n" + + "485p1carRzmQwkplKpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJz\n" + + "sLtMUMPzRBd4vNYeyInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3\n" + + "iKdnfycia6sqH+/CRQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH\n" + + "+POXhrmcVVnS0ZZQ2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1f\n" + + "GiDTNkSzLBpLj00bSEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJF\n" + + "ZDE+biSbwsI8BBgBCgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3i\n" + + "GpJSc3akDgKbAsC8oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrz\n" + + "evsNklOMRBvvkqgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMV\n" + + "yioFRy9XRH84PYWpVWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9Pxnn\n" + + "GOhO+6r4Q85gnJUm3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjAR\n" + + "p+rIAD5k6jOVLAwqbBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acj\n" + + "Ok4QQjIW0JEe4RPV1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDU\n" + + "vYUJHatGlnoTaEyXQrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ\n" + + "4mKVnPPQcH4WIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/\n" + + "zUD0XnX+eOGCf2HUJ73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRN\n" + + "zedN9SSSsBaQgevUbMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcG\n" + + "ptEklxx6/yZGJubn1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4\n" + + "b8zpiWu3wwtLlGYUyhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bc\n" + + "f1Ngef/DdEPqSBaBLjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/97\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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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" + + "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + + "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + + "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + + "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + + "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + + "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwLsEIAEKAG8FglwqrYAJ\n" + + "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z4KjdWVHTHye8HeUynibpgE5TYfFnnBt9bbOj99oplaTFiEE4yy22oICkbfnbbGo\n" + + "CK1RyuRw8AYAAMxeB/4+QAncX1+678HeO1fweQ0Zkf4O6+Ew6EgCp4I2UZu+a5H8\n" + + "ryI3B4WNShCDoV3CfOcUtUSUA8EOyrpYSW/3jPVfb01uxDNsZpf9piZG7DelIAef\n" + + "wvQaZHJeytchv5+Wo+Jo6qg26BgvUlXW2x5NNcScGvCZt1RQ712PRDAfUnppRXBj\n" + + "+IXWzOs52uYGFDFzJSLEUy6dtTdNCJk78EMoHsOwC7g5uUyHbjSfrdQncxgMwikl\n" + + "C2LFSS7xYZwDgkkb70AT10Ot2jL6rLIT/1ChQZ0oRGJLBHiz3FUpanDQIDD49+dp\n" + + "6FUmUUsubwwFkxBHyCbQ8cdbfBILNiD1pEo31dPTwsDEBB8BCgB4BYJeC+EACRAI\n" + + "rVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeH\n" + + "LGXtWodbY9gI8X3QzLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqC\n" + + "ApG3522xqAitUcrkcPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySj\n" + + "Tyk+ytK1Q5E8NSUYk3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ\n" + + "/At+Bw3OPeWZ68hzQfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/\n" + + "jCEYM5Kfg4NC1yVZw7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8\n" + + "EQq9veCfHYPwqMAH5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIF\n" + + "zvwpgKbkzb2m3LfgOyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWC\n" + + "Wkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w\n" + + "Z3Aub3Jn1WXYy2GcQ19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEW\n" + + "IQTjLLbaggKRt+dtsagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsU\n" + + "G65rE8R4QGFvHhhXM/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZu\n" + + "flYRmct3t0B+CfxN9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwk\n" + + "dOKkm6VVAiAKZ4QR8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23\n" + + "ZvFL1TxVx/rhxM04Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+ea\n" + + "tJt1bXsNioiFIuMCouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1\n" + + "bGlldEBleGFtcGxlLm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAA\n" + + "HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa\n" + + "5AImO40vTfrIbkXR2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDw\n" + + "BgAAn/UIALMbXwG8hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtY\n" + + "odkyXN78BfGjVQ63G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO\n" + + "6ay66dGrlTTYS2MTivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZa\n" + + "JnMBh2wdQpGdOA5gjG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TG\n" + + "HiUtB1ZcMHOovIikswtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa\n" + + "7aKpHz2M2zXwtG7d+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37E\n" + + "Uqm0CJztIlp7uAyvSFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXz\n" + + "h/M+xWFLmsdbGhn/XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGar\n" + + "HPovqCi2Z+19GACOLRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7Ge\n" + + "iUucLDOucgrTh3AACAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnm\n" + + "QlHBRdBcmQSJBoxyFUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0Xy\n" + + "R00AEQEAAcLCPAQYAQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBu\n" + + "b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLK\n" + + "g0h1eacBHzMCmwLAvKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNh\n" + + "bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXA\n" + + "eYvxUUglSsm7i864FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33\n" + + "xS+kUUeB043pbKcuAN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9Qt\n" + + "VnEuSS1cc1wHu3/ijn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2\n" + + "UGbXKbJ0NbiBwvTjcVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmr\n" + + "fb1BbhhuvJg4NAq0WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH\n" + + "+XlIbcQovX4O0o7x5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8\n" + + "C7G1UJ2C3fYIFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P\n" + + "485p1carRzmQwkplKpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJz\n" + + "sLtMUMPzRBd4vNYeyInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3\n" + + "iKdnfycia6sqH+/CRQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH\n" + + "+POXhrmcVVnS0ZZQ2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1f\n" + + "GiDTNkSzLBpLj00bSEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJF\n" + + "ZDE+biSbwsI8BBgBCgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3i\n" + + "GpJSc3akDgKbAsC8oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrz\n" + + "evsNklOMRBvvkqgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMV\n" + + "yioFRy9XRH84PYWpVWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9Pxnn\n" + + "GOhO+6r4Q85gnJUm3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjAR\n" + + "p+rIAD5k6jOVLAwqbBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acj\n" + + "Ok4QQjIW0JEe4RPV1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDU\n" + + "vYUJHatGlnoTaEyXQrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ\n" + + "4mKVnPPQcH4WIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/\n" + + "zUD0XnX+eOGCf2HUJ73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRN\n" + + "zedN9SSSsBaQgevUbMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcG\n" + + "ptEklxx6/yZGJubn1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4\n" + + "b8zpiWu3wwtLlGYUyhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bc\n" + + "f1Ngef/DdEPqSBaBLjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/97\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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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" + - "xsBNBFpJegABCACzr1V+GxVkrtfDjihYK+HtyEIcO52uw7O2kd7JbduYp4RK17jy\n" + - "75N3EnsgmiIkSxXCWr+rTtonNs1zCJeUa/gwnNfs7mVgjL2rMOZU/KZ4MP0yOYU5\n" + - "u5FjNPWz8hpFQ9GKqfdj0Op61h1pCQO45IjUQ3dCDj9Rfn44zHMB1ZrbmIH9nTR1\n" + - "YIGHWmdm0LItb2WxIkwzWBAJ5acTlsmLyZZEQ1+8NDqktyzwFoQqTJvLU4StY2k6\n" + - "h18ZKZdPyrdLoEyOuWkvjxmbhDk1Gt5KiS/yy7mrzIPLr0dmJe4vc8WLV+bXoyNE\n" + - "x3H8o9CFcYehLfyqsy40lg92d6Kp96ww8dZ5ABEBAAHCwLsEIAEKAG8FglwqrYAJ\n" + - "EAitUcrkcPAGRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + - "Z4KjdWVHTHye8HeUynibpgE5TYfFnnBt9bbOj99oplaTFiEE4yy22oICkbfnbbGo\n" + - "CK1RyuRw8AYAAMxeB/4+QAncX1+678HeO1fweQ0Zkf4O6+Ew6EgCp4I2UZu+a5H8\n" + - "ryI3B4WNShCDoV3CfOcUtUSUA8EOyrpYSW/3jPVfb01uxDNsZpf9piZG7DelIAef\n" + - "wvQaZHJeytchv5+Wo+Jo6qg26BgvUlXW2x5NNcScGvCZt1RQ712PRDAfUnppRXBj\n" + - "+IXWzOs52uYGFDFzJSLEUy6dtTdNCJk78EMoHsOwC7g5uUyHbjSfrdQncxgMwikl\n" + - "C2LFSS7xYZwDgkkb70AT10Ot2jL6rLIT/1ChQZ0oRGJLBHiz3FUpanDQIDD49+dp\n" + - "6FUmUUsubwwFkxBHyCbQ8cdbfBILNiD1pEo31dPTwsDEBB8BCgB4BYJeC+EACRAI\n" + - "rVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeH\n" + - "LGXtWodbY9gI8X3QzLB9sL0hMGY2/+9yAip5uwckkAIVCgKbAwIeARYhBOMsttqC\n" + - "ApG3522xqAitUcrkcPAGAABmBQgAoipwm9jQWWvyY9WiXuEdq8T2Y9hEV1nt2ySj\n" + - "Tyk+ytK1Q5E8NSUYk3wrLgGNpWPbCiXYUGZfms15uuL703OoRBkUP/l7LA5RNgyJ\n" + - "/At+Bw3OPeWZ68hzQfA3eZdR3Y6sXxiGOhwTyVHcdHXncD+NjorIPbeSrAvM5Xf/\n" + - "jCEYM5Kfg4NC1yVZw7sFhD6KNjeloQK+UXi718QC1+YbfS295T9AwEmbwCsvQTv8\n" + - "EQq9veCfHYPwqMAH5aMn9CqPiY8o2p5mZ92nMuQhpFTdpnPjxVHpBmQw8uaKGJIF\n" + - "zvwpgKbkzb2m3LfgOyFVXVljOUlm/dCb2lfUlo4up0KYVZu0rcLAxAQfAQoAeAWC\n" + - "Wkl6AAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w\n" + - "Z3Aub3Jn1WXYy2GcQ19ob8t2hq7BOItGrywzM393vZFR5mg+jwICFQoCmwMCHgEW\n" + - "IQTjLLbaggKRt+dtsagIrVHK5HDwBgAAUGMIAK3yEcaveZ6VCgDj17NuZ2Zb8vsU\n" + - "G65rE8R4QGFvHhhXM/NkMLpqKq0fFX66I8TPngmXUyPOZzOZM852A1NvnDIbGVZu\n" + - "flYRmct3t0B+CfxN9Q+7daKQr4+YNXkSeC4MsAfnGBnGQWKf20E/UlGLoWR9jlwk\n" + - "dOKkm6VVAiAKZ4QR8SjbTpaowJB3mjnVv/F3j7G3767VTixmIK2V32Ozast/ls23\n" + - "ZvFL1TxVx/rhxM04Mr2G5yQWJIzkZgqlCrPOtDy/HpHoPrC+Dx0kY9VFH8HEA+ea\n" + - "tJt1bXsNioiFIuMCouS3Hg7aa46DubrVP9WHxAIjTHkkB1yqvN3aWs7461LNEmp1\n" + - "bGlldEBleGFtcGxlLm9yZ8LAxAQTAQoAeAWCWkl6AAkQCK1RyuRw8AZHFAAAAAAA\n" + - "HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnOkYsewniH1sJ2kI5N2wa\n" + - "5AImO40vTfrIbkXR2dICirICFQoCmwMCHgEWIQTjLLbaggKRt+dtsagIrVHK5HDw\n" + - "BgAAn/UIALMbXwG8hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtY\n" + - "odkyXN78BfGjVQ63G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO\n" + - "6ay66dGrlTTYS2MTivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZa\n" + - "JnMBh2wdQpGdOA5gjG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TG\n" + - "HiUtB1ZcMHOovIikswtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa\n" + - "7aKpHz2M2zXwtG7d+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37E\n" + - "Uqm0CJztIlp7uAyvSFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXz\n" + - "h/M+xWFLmsdbGhn/XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGar\n" + - "HPovqCi2Z+19GACOLRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7Ge\n" + - "iUucLDOucgrTh3AACAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnm\n" + - "QlHBRdBcmQSJBoxyFUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0Xy\n" + - "R00AEQEAAcLCPAQYAQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBu\n" + - "b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLK\n" + - "g0h1eacBHzMCmwLAvKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNh\n" + - "bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXA\n" + - "eYvxUUglSsm7i864FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33\n" + - "xS+kUUeB043pbKcuAN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9Qt\n" + - "VnEuSS1cc1wHu3/ijn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2\n" + - "UGbXKbJ0NbiBwvTjcVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmr\n" + - "fb1BbhhuvJg4NAq0WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH\n" + - "+XlIbcQovX4O0o7x5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8\n" + - "C7G1UJ2C3fYIFiEE4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P\n" + - "485p1carRzmQwkplKpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJz\n" + - "sLtMUMPzRBd4vNYeyInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3\n" + - "iKdnfycia6sqH+/CRQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH\n" + - "+POXhrmcVVnS0ZZQ2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1f\n" + - "GiDTNkSzLBpLj00bSEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJF\n" + - "ZDE+biSbwsI8BBgBCgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5v\n" + - "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3i\n" + - "GpJSc3akDgKbAsC8oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2Fs\n" + - "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrz\n" + - "evsNklOMRBvvkqgWIQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMV\n" + - "yioFRy9XRH84PYWpVWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9Pxnn\n" + - "GOhO+6r4Q85gnJUm3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjAR\n" + - "p+rIAD5k6jOVLAwqbBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acj\n" + - "Ok4QQjIW0JEe4RPV1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDU\n" + - "vYUJHatGlnoTaEyXQrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ\n" + - "4mKVnPPQcH4WIQTjLLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/\n" + - "zUD0XnX+eOGCf2HUJ73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRN\n" + - "zedN9SSSsBaQgevUbMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcG\n" + - "ptEklxx6/yZGJubn1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4\n" + - "b8zpiWu3wwtLlGYUyhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bc\n" + - "f1Ngef/DdEPqSBaBLjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/97\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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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" + - "hm7aH46107PZbChFrxoJNNn0mMioz28mkaoe9jJSJVF8KqtYodkyXN78BfGjVQ63\n" + - "G/Q5wWm3bdjNbyNz1Gnht9QZmpAv12QjQq22yZMnf73TC6sO6ay66dGrlTTYS2MT\n" + - "ivbrF2wpTcZbqOIv5UhVaOQfWovp3tZCioqZc6stqqoXXqZaJnMBh2wdQpGdOA5g\n" + - "jG0khQBsWKlAv2wZtG6JQnm8PyiM/bBKIzSrepr7BTeu/4TGHiUtB1ZcMHOovIik\n" + - "swtg+d4ssIbb5HYihAl0Hlw3/czVwJ9cKStNUiydIooO3Axa7aKpHz2M2zXwtG7d\n" + - "+HzcfYs98PWhB/HOwE0EWkrLgAEIALucmrvabJbZlolJ+37EUqm0CJztIlp7uAyv\n" + - "SFwd4ITWDHIotySRIx84CMRn9xoiRI87m8kUGl+Sf6e8gdXzh/M+xWFLmsdbGhn/\n" + - "XNf29NjfYMlzZR2pt9YTWmi933xXMyPeaezDa07a6E7eaGarHPovqCi2Z+19GACO\n" + - "LRGMIUp1EGAfxe2KpJCOHlfuwsWTwPKQYV4gDiv85+Nej7GeiUucLDOucgrTh3AA\n" + - "CAZyg5wvm0Ivn9VmXrEqHMv618d0BEJqHQ7t6I4UvlcXGBnmQlHBRdBcmQSJBoxy\n" + - "FUC8jn4z9xUSeKhVzM/f2CFaDOGOoLkxETExI/ygxuAT+0XyR00AEQEAAcLCPAQY\n" + - "AQoB8AWCXgvhAAkQCK1RyuRw8AZHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2Vx\n" + - "dW9pYS1wZ3Aub3Jn3AGtWT1k7YOtMNzqOHbeBWvHChWG2WLKg0h1eacBHzMCmwLA\n" + - "vKAEGQEKAG8Fgl4L4QAJEBD8vP8OjqeRRxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + - "LnNlcXVvaWEtcGdwLm9yZy+iNvlcjeU9RaFYI93HZzC2AqXAeYvxUUglSsm7i864\n" + - "FiEEzqYQ0IT6UfIR4cRsEPy8/w6Op5EAAK5PB/wIYzPZam33xS+kUUeB043pbKcu\n" + - "AN58k4nApY6w7lw1Idtxny72o00eYdemQagBe3KW/a65c9QtVnEuSS1cc1wHu3/i\n" + - "jn17vnsi4aU82fFU96St4RxmmMJVZV6wWT9CV4C/IZuoQ0l2UGbXKbJ0NbiBwvTj\n" + - "cVAeJYjRYU6kAkGHUCRYhbNbplG6PwuCnc5QyNPGzNwqhCmrfb1BbhhuvJg4NAq0\n" + - "WRFrfeOi+YvJGZgVJEkoSJtpOXtqhP5rmHjOqikDbMZcd1SH+XlIbcQovX4O0o7x\n" + - "5HVEhbLWHMQHWqIVghQhLAySNdoc3CkGs0o77SheATQSoF/8C7G1UJ2C3fYIFiEE\n" + - "4yy22oICkbfnbbGoCK1RyuRw8AYAADYzB/9TGOwycsZIk43P485p1carRzmQwkpl\n" + - "KpNHof+gR7PqLLVqpBguvu3X8Q56bcHKmp3WHsuChdmo7eJzsLtMUMPzRBd4vNYe\n" + - "yInsGOxvmE+vQ1Hfg71VEHpnyjWFTqzKqB+0FOaOGKI3SYg3iKdnfycia6sqH+/C\n" + - "RQB5zWYBwtk9s6PROeHZzk2PVTVDQjlHLeUW8tBl40yFETtH+POXhrmcVVnS0ZZQ\n" + - "2Dogq0Bz0h4a8R1V1TG2CaK6D8viMmiWp1aAFoMoqQZpiA1fGiDTNkSzLBpLj00b\n" + - "SEyNmZRjkDe8YMuC6ls4z568zF38ARA8f568HRusxBjCvAJFZDE+biSbwsI8BBgB\n" + - "CgHwBYJa6P+ACRAIrVHK5HDwBkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1\n" + - "b2lhLXBncC5vcmf0NGelgx9vvPxdcRBLogKbI559pRjWdg3iGpJSc3akDgKbAsC8\n" + - "oAQZAQoAbwWCWuj/gAkQEPy8/w6Op5FHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu\n" + - "c2VxdW9pYS1wZ3Aub3Jn61QE8l97YHDNs+NX6mKrsVYSUWrzevsNklOMRBvvkqgW\n" + - "IQTOphDQhPpR8hHhxGwQ/Lz/Do6nkQAARlYIALAfDNiiOXMVyioFRy9XRH84PYWp\n" + - "VWr5LX3E+mVQv/mg6feLbwQi9ehroauHHDewwE61seN9PxnnGOhO+6r4Q85gnJUm\n" + - "3S24mZrK1V/ZApk36ycxUOuCn7yEuRoGy9tfmSfqSlthzjARp+rIAD5k6jOVLAwq\n" + - "bBCg7eQiXCa97E3PA/TYRJ3NHSrEPdfp/ZrN1ubcshOq/acjOk4QQjIW0JEe4RPV\n" + - "1gEHjtSC0hRp4ntGhXE1NDqNMC9TGoksgP/F6Sqtt8X8dZDUvYUJHatGlnoTaEyX\n" + - "QrdTatXFgActq0EdMfqoRlqMH7AI5wWrrcb3rdvLdpDwgCDJ4mKVnPPQcH4WIQTj\n" + - "LLbaggKRt+dtsagIrVHK5HDwBgAAqtYH/0Ox/UXuUlpPlDp/zUD0XnX+eOGCf2HU\n" + - "J73v4Umjxi993FM3+MscxSC5ytfSK3eX/P5k09aYPfS94sRNzedN9SSSsBaQgevU\n" + - "bMrIPPGSwy9lS3N8XbAEHVG1WgqnkRysLTLaQb2wBbxfaZcGptEklxx6/yZGJubn\n" + - "1zeiPIm/58K9WxW3/0ntFrpPURuJ3vSVAQqxsWpMlXfjoCy4b8zpiWu3wwtLlGYU\n" + - "yhW4zMS4WmrOBxWIkW389k9Mc/YMg8rQ1rBBTPl6Ch5RB/Bcf1Ngef/DdEPqSBaB\n" + - "LjpgTvuRD7zyJcTQch4ImjSLirdTLvlAG9kqZeg+c2w31/976sXYWB8=\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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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/165] 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() { From 482751208f07908aaf44f9744d0d6920e3e73b74 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Mar 2025 12:07:07 +0100 Subject: [PATCH 155/165] Add OpenPGPComponentKey.getAlgorithm() shortcut method --- .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 11 +++++++++++ 1 file 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 cea68aef2d..5de2604900 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1638,6 +1638,17 @@ public KeyIdentifier getKeyIdentifier() return rawPubkey.getKeyIdentifier(); } + /** + * Return the public key algorithm. + * + * @see org.bouncycastle.bcpg.PublicKeyAlgorithmTags + * @return public key algorithm id + */ + public int getAlgorithm() + { + return getPGPPublicKey().getAlgorithm(); + } + /** * Return the public key version. * From 84fec84c679be35569d973c2dd152ba0fa9fc584 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Mar 2025 12:21:59 +0100 Subject: [PATCH 156/165] Add OpenPGPCertificate.getCertificationKeys() --- .../openpgp/api/OpenPGPCertificate.java | 39 +++++++++++++++++++ 1 file changed, 39 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 5de2604900..b802831416 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -787,6 +787,45 @@ public List getSigningKeys(Date evaluationTime) return signingKeys; } + /** + * Return a {@link List} containing all currently valid marked certification keys. + * + * @return list of certification keys + */ + public List getCertificationKeys() + { + return getCertificationKeys(new Date()); + } + + /** + * Return a list of all keys that - at evaluation time - are validly marked as certification keys. + * + * @param evaluationTime evaluation time + * @return list of certification keys + */ + public List getCertificationKeys(Date evaluationTime) + { + List certificationKeys = new ArrayList<>(); + + for (OpenPGPComponentKey key : getKeys()) + { + if (!isBound(key, evaluationTime)) + { + // Key is not bound + continue; + } + + if (!key.isCertificationKey(evaluationTime)) + { + continue; + } + + certificationKeys.add(key); + } + + return certificationKeys; + } + /** * Return {@link OpenPGPSignatureChains} that contain preference information. * From 74bda91ecd821bb93368cd061e90235814e037df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 13:11:12 +0100 Subject: [PATCH 157/165] Add OpenPGPKeyEditor.addSubkey() method --- .../openpgp/api/OpenPGPKeyEditor.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) 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 76ba096a55..94900d2520 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -449,6 +449,118 @@ public OpenPGPKeyEditor addSigningSubkey(PGPKeyPair signingSubkey, return this; } + /** + * Add a component key to the certificate. + * The bindingSigCallback can be used to modify the subkey binding signature. + * If it is null, no subkey binding signature will be generated. + * The backSigCallback can be used to modify the embedded primary key binding signature. + * If it is null, no primary key binding signature will be generated. + * You MUST only pass a non-null value here, if the subkey is capable of creating signatures. + * + * @param subkey component key + * @param bindingSigCallback callback to modify the subkey binding signature + * @param backSigCallback callback to modify the embedded primary key binding signature + * @return this + * @throws PGPException + */ + public OpenPGPKeyEditor addSubkey(PGPKeyPair subkey, + SignatureParameters.Callback bindingSigCallback, + SignatureParameters.Callback backSigCallback) + throws PGPException + { + if (PublicKeyUtils.isSigningAlgorithm(subkey.getPublicKey().getAlgorithm()) + && backSigCallback != null) + { + throw new PGPKeyValidationException("Provided subkey is not signing-capable, so we cannot create a back-signature."); + } + + PGPPublicKey publicSubKey = subkey.getPublicKey(); + + 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(subkey.getPublicKey().getAlgorithm(), + backSigParameters.getSignatureHashAlgorithmId()), + subkey.getPublicKey()); + backSigGen.init(backSigParameters.getSignatureType(), subkey.getPrivateKey()); + + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, subkey.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, subkey.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(), primaryKey.getKeyPair().getPrivateKey()); + + // Hashed subpackets + PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator(); + hashedSubpackets.setIssuerFingerprint(true, publicPrimaryKey); + hashedSubpackets.setSignatureCreationTime(parameters.getSignatureCreationTime()); + + 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 + PGPSignature subKeySig = subKeySigGen.generateCertification(publicPrimaryKey, publicSubKey); + publicSubKey = PGPPublicKey.addCertification(publicSubKey, subKeySig); + } + + PGPSecretKey secretSubkey = new PGPSecretKey( + subkey.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 { From 7e5be614539ef215757e6c679d6816245f858bf2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 13:13:54 +0100 Subject: [PATCH 158/165] Change API of OpenPGPKeyEditor.changePassphrase() to accept KeyIdentifier instead of OpenPGPComponentKey --- .../org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java | 9 +++++---- .../openpgp/api/test/OpenPGPKeyEditorTest.java | 4 ++-- .../openpgp/api/test/OpenPGPV6KeyGeneratorTest.java | 4 ++-- 3 files changed, 9 insertions(+), 8 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 94900d2520..dd34ec42cd 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyEditor.java @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.openpgp.PGPException; @@ -651,7 +652,7 @@ public OpenPGPKeyEditor revokeKey(SignatureParameters.Callback revocationSignatu /** * Change the passphrase of the given component key. * - * @param componentKey component key, whose passphrase shall be changed + * @param componentKeyIdentifier identifier of the 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 @@ -659,16 +660,16 @@ public OpenPGPKeyEditor revokeKey(SignatureParameters.Callback revocationSignatu * @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, + public OpenPGPKeyEditor changePassphrase(KeyIdentifier componentKeyIdentifier, char[] oldPassphrase, char[] newPassphrase, boolean useAEAD) throws OpenPGPKeyException, PGPException { - OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(componentKey); + OpenPGPKey.OpenPGPSecretKey secretKey = key.getSecretKey(componentKeyIdentifier); if (secretKey == null) { - throw new OpenPGPKeyException(componentKey, "Secret component key " + componentKey.getKeyIdentifier() + + throw new OpenPGPKeyException(key, "Secret component key " + componentKeyIdentifier + " is missing from the key."); } 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 c095de2bc7..f6c77ebd0c 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 @@ -247,7 +247,7 @@ private void changePassphraseUnprotectedToCFBTest(OpenPGPApi api) isFalse(key.getPrimarySecretKey().isLocked()); key = api.editKey(key) - .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), false) + .changePassphrase(key.getPrimaryKey().getKeyIdentifier(), null, "sw0rdf1sh".toCharArray(), false) .done(); isTrue("Expect key to be locked", key.getPrimarySecretKey().isLocked()); isTrue("Expect sw0rdf1sh to be the correct passphrase", @@ -263,7 +263,7 @@ private void changePassphraseUnprotectedToAEADTest(OpenPGPApi api) isFalse("Expect key to be unprotected", key.getPrimarySecretKey().isLocked()); key = api.editKey(key) - .changePassphrase(key.getPrimaryKey(), null, "sw0rdf1sh".toCharArray(), true) + .changePassphrase(key.getPrimaryKey().getKeyIdentifier(), null, "sw0rdf1sh".toCharArray(), true) .done(); isTrue("Expect key to be locked after changing passphrase", key.getPrimarySecretKey().isLocked()); 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 b75a6637c4..d11287f22a 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 @@ -388,11 +388,11 @@ public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getEncryptionKeys().get(0); OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0); key = api.editKey(key, "primary-key-passphrase".toCharArray()) - .changePassphrase(encryptionKey, + .changePassphrase(encryptionKey.getKeyIdentifier(), "primary-key-passphrase".toCharArray(), "encryption-key-passphrase".toCharArray(), false) - .changePassphrase(signingKey, + .changePassphrase(signingKey.getKeyIdentifier(), "primary-key-passphrase".toCharArray(), "signing-key-passphrase".toCharArray(), false) From 8a4b61eb5c2d598c227909a73aeeddbb1b0cfa16 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 13 Mar 2025 15:35:58 +0100 Subject: [PATCH 159/165] PBE secret key protection: Use relaxed default params, allow customization --- .../openpgp/api/OpenPGPImplementation.java | 2 ++ .../api/bc/BcOpenPGPImplementation.java | 9 ++++++++- .../api/jcajce/JcaOpenPGPImplementation.java | 19 ++++++++++++++++++- .../bc/BcCFBSecretKeyEncryptorFactory.java | 13 +++++++++++-- .../JcaCFBSecretKeyEncryptorFactory.java | 10 +++++++--- 5 files changed, 46 insertions(+), 7 deletions(-) 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 4153b53c7b..a216058a2c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPImplementation.java @@ -205,4 +205,6 @@ public abstract PGPDigestCalculatorProvider pgpDigestCalculatorProvider() public abstract KeyFingerPrintCalculator keyFingerPrintCalculator(); public abstract PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) throws PGPException; + + public abstract PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead, int symmetricKeyAlgorithm, int iterationCount) throws PGPException; } 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 b603d83403..966f085c06 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 @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.api.bc; import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPrivateKey; @@ -141,6 +142,12 @@ public KeyFingerPrintCalculator keyFingerPrintCalculator() @Override public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) + { + return pbeSecretKeyEncryptorFactory(aead, SymmetricKeyAlgorithmTags.AES_128, 0x60); + } + + @Override + public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead, int symmetricKeyAlgorithm, int iterationCount) { if (aead) { @@ -148,7 +155,7 @@ public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) } else { - return new BcCFBSecretKeyEncryptorFactory(); + return new BcCFBSecretKeyEncryptorFactory(symmetricKeyAlgorithm, iterationCount); } } } 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 00adce71e5..b8dca8f588 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 @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.api.jcajce; import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; @@ -205,7 +206,23 @@ public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead) } else { - return new JcaCFBSecretKeyEncryptorFactory() + return new JcaCFBSecretKeyEncryptorFactory(SymmetricKeyAlgorithmTags.AES_128, 0x60) + .setProvider(provider); + } + } + + @Override + public PBESecretKeyEncryptorFactory pbeSecretKeyEncryptorFactory(boolean aead, int symmetricKeyAlgorithm, int iterationCount) + throws PGPException + { + if (aead) + { + return new JcaAEADSecretKeyEncryptorFactory() + .setProvider(provider); + } + else + { + return new JcaCFBSecretKeyEncryptorFactory(symmetricKeyAlgorithm, iterationCount) .setProvider(provider); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java index 93bc4a3650..e950d84eda 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java @@ -19,6 +19,15 @@ public class BcCFBSecretKeyEncryptorFactory implements PBESecretKeyEncryptorFactory { + private final int symmetricKeyAlgorithm; + private final int iterationCount; + + public BcCFBSecretKeyEncryptorFactory(int symmetricKeyAlgorithm, + int iterationCount) { + this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; + this.iterationCount = iterationCount; + } + @Override public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) { @@ -38,9 +47,9 @@ public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPack } return new BcPBESecretKeyEncryptorBuilder( - SymmetricKeyAlgorithmTags.AES_256, + symmetricKeyAlgorithm, checksumCalc, - 0xff) // MAX iteration count + iterationCount) // MAX iteration count .build(passphrase); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java index b7fca675f2..c8a576b7fe 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java @@ -12,13 +12,17 @@ public class JcaCFBSecretKeyEncryptorFactory implements PBESecretKeyEncryptorFactory { + private final int symmetricKeyAlgorithm; + private final int iterationCount; private JcaPGPDigestCalculatorProviderBuilder digestCalcProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); private JcePBESecretKeyEncryptorBuilder encBuilder; - public JcaCFBSecretKeyEncryptorFactory() + public JcaCFBSecretKeyEncryptorFactory(int symmetricKeyAlgorithm, int iterationCount) throws PGPException { + this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; + this.iterationCount = iterationCount; encBuilder = builder(); } @@ -34,9 +38,9 @@ private JcePBESecretKeyEncryptorBuilder builder() throws PGPException { return new JcePBESecretKeyEncryptorBuilder( - SymmetricKeyAlgorithmTags.AES_256, + symmetricKeyAlgorithm, digestCalcProviderBuilder.build().get(HashAlgorithmTags.SHA1), - 0x60 + iterationCount ); } From 457d7cef132854de330d909945353f309f37c32d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 19 Mar 2025 13:47:06 +0100 Subject: [PATCH 160/165] Fix checkstyle issues --- .../openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java index e950d84eda..ccde1f85fa 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java @@ -2,7 +2,6 @@ import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; -import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; @@ -23,7 +22,8 @@ public class BcCFBSecretKeyEncryptorFactory private final int iterationCount; public BcCFBSecretKeyEncryptorFactory(int symmetricKeyAlgorithm, - int iterationCount) { + int iterationCount) + { this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; this.iterationCount = iterationCount; } From 7ae4a8cf3e79ead44dd821e078eed34c43f5f1f9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 19 Mar 2025 13:48:58 +0100 Subject: [PATCH 161/165] Fix bug in changePassphrase() method Before, we only called replaceSecretKey() on the OpenPGPKey class, but we did NOT replace the secret key on the underlying PGPSecretKeyRing. Consequentially, calling toAsciiArmoredString() would return the old secret key, protected with the old passphrase. --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 2 +- pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java | 1 + 2 files changed, 2 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 b802831416..2eb8024bce 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -69,7 +69,7 @@ public class OpenPGPCertificate final OpenPGPImplementation implementation; final OpenPGPPolicy policy; - private final PGPKeyRing keyRing; + protected PGPKeyRing keyRing; private final OpenPGPPrimaryKey primaryKey; private final Map subkeys; 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 2d41cd4333..e2c5df6e19 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -168,6 +168,7 @@ public OpenPGPSecretKey getSecretKey(OpenPGPComponentKey key) void replaceSecretKey(OpenPGPSecretKey secretKey) { + keyRing = PGPSecretKeyRing.insertSecretKey((PGPSecretKeyRing) keyRing, secretKey.rawSecKey); secretKeys.put(secretKey.getKeyIdentifier(), secretKey); } From 562430a813d44742c2dab142e236f82154ee68cd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 19 Mar 2025 13:50:48 +0100 Subject: [PATCH 162/165] Add getEncoded(PacketFormat) to OpenPGPKey, OpenPGPCertificate --- .../openpgp/api/OpenPGPCertificate.java | 20 ++++++++++++++++--- .../bouncycastle/openpgp/api/OpenPGPKey.java | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 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 2eb8024bce..8f70711d2b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -523,7 +523,22 @@ public String toAsciiArmoredString() } ArmoredOutputStream aOut = armorBuilder.build(bOut); - BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + aOut.write(getEncoded()); + aOut.close(); + return bOut.toString(); + } + + public byte[] getEncoded() + throws IOException + { + return getEncoded(PacketFormat.ROUNDTRIP); + } + + public byte[] getEncoded(PacketFormat format) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, format); // Make sure we export a TPK List list = new ArrayList<>(); @@ -535,8 +550,7 @@ public String toAsciiArmoredString() publicKeys.encode(pOut, true); pOut.close(); - aOut.close(); - return bOut.toString(); + return bOut.toByteArray(); } private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent component, 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 e2c5df6e19..24d5431bd5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -207,6 +207,16 @@ public String toAsciiArmoredString() return bOut.toString(); } + public byte[] getEncoded(PacketFormat format) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, format); + getPGPSecretKeyRing().encode(pOut); + pOut.close(); + return bOut.toByteArray(); + } + /** * Secret key component of a {@link org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPPrimaryKey} or * {@link org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPSubkey}. From 21c37a5d501272421172e81778cdeb7f8135b2d5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 19 Mar 2025 14:12:50 +0100 Subject: [PATCH 163/165] Allow use of custom PacketFormat in toAsciiArmoredString(), reuse common code --- .../openpgp/api/OpenPGPCertificate.java | 10 +++++-- .../bouncycastle/openpgp/api/OpenPGPKey.java | 28 ++----------------- 2 files changed, 10 insertions(+), 28 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 8f70711d2b..01a4aa834c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -501,13 +501,19 @@ public String getPrettyFingerprint() return FingerprintUtil.prettifyFingerprint(getFingerprint()); } + public String toAsciiArmoredString() + throws IOException + { + return toAsciiArmoredString(PacketFormat.ROUNDTRIP); + } + /** * Return an ASCII armored {@link String} containing the certificate. * * @return armored certificate * @throws IOException if the cert cannot be encoded */ - public String toAsciiArmoredString() + public String toAsciiArmoredString(PacketFormat packetFormat) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); @@ -523,7 +529,7 @@ public String toAsciiArmoredString() } ArmoredOutputStream aOut = armorBuilder.build(bOut); - aOut.write(getEncoded()); + aOut.write(getEncoded(packetFormat)); aOut.close(); return bOut.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 24d5431bd5..a92a2f5371 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKey.java @@ -183,35 +183,11 @@ public PGPSecretKeyRing getPGPSecretKeyRing() return (PGPSecretKeyRing) super.getPGPKeyRing(); } - @Override - public String toAsciiArmoredString() - throws IOException - { - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream.Builder armorBuilder = ArmoredOutputStream.builder() - .clearHeaders(); - - armorBuilder.addSplitMultilineComment(getPrettyFingerprint()); - - 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(); - } - - public byte[] getEncoded(PacketFormat format) + public byte[] getEncoded(PacketFormat packetFormat) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - BCPGOutputStream pOut = new BCPGOutputStream(bOut, format); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, packetFormat); getPGPSecretKeyRing().encode(pOut); pOut.close(); return bOut.toByteArray(); From e32ebc25f7a940d941a34bd46ac70d08f370ceef Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 24 Mar 2025 10:13:04 +0100 Subject: [PATCH 164/165] v4 Subkey- and PrimaryKeyBindingSignatures do NOT require issuer key-id/fingerprint subpackets necessarily --- .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 12 +++++++++++- .../bouncycastle/openpgp/api/OpenPGPSignature.java | 8 ++++++-- 2 files changed, 17 insertions(+), 3 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 01a4aa834c..de49f9f51d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -269,6 +269,15 @@ public OpenPGPComponentKey getKey(KeyIdentifier identifier) public OpenPGPComponentKey getSigningKeyFor(PGPSignature signature) { List keyIdentifiers = signature.getKeyIdentifiers(); + + // Subkey binding signatures do not require issuer + int type = signature.getSignatureType(); + if (type == PGPSignature.SUBKEY_BINDING || + type == PGPSignature.SUBKEY_REVOCATION) + { + return primaryKey; + } + // issuer is primary key if (KeyIdentifier.matches(keyIdentifiers, getPrimaryKey().getKeyIdentifier(), true)) { @@ -565,7 +574,8 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c { // Check if there are signatures at all for the component OpenPGPSignatureChains chainsForComponent = getAllSignatureChainsFor(component); - if (component == getPrimaryKey() && chainsForComponent.isEmpty()) + boolean isPrimaryKey = component == getPrimaryKey(); + if (isPrimaryKey && chainsForComponent.getCertificationAt(evaluationDate) == null) { // If cert has no direct-key signatures, consider primary UID bindings instead OpenPGPUserId primaryUserId = getPrimaryUserId(evaluationDate); 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 9a87147022..d399e0a75e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -314,8 +314,12 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, hashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null && unhashed.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID) == null) { - throw new MalformedOpenPGPSignatureException( - this, "Missing IssuerKeyID and IssuerFingerprint subpacket."); + int type = signature.getSignatureType(); + if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.PRIMARYKEY_BINDING) + { + throw new MalformedOpenPGPSignatureException( + this, "Missing IssuerKeyID and IssuerFingerprint subpacket."); + } } break; From 85da8725a86961ffe8bb711a87b8567887b9cc00 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 24 Mar 2025 10:13:45 +0100 Subject: [PATCH 165/165] Fix NPE for v3 signature verification due to missing hashed subpacket area --- .../org/bouncycastle/openpgp/api/OpenPGPSignature.java | 10 ++++++++++ 1 file changed, 10 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 d399e0a75e..7ab5d29c8f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPSignature.java @@ -264,6 +264,16 @@ void sanitize(OpenPGPCertificate.OpenPGPComponentKey issuer, throw new PGPSignatureException("Unacceptable hash algorithm: " + signature.getHashAlgorithm()); } + if (signature.getVersion() < SignaturePacket.VERSION_4) + { + if (signature.getCreationTime().before(issuer.getCreationTime())) + { + throw new MalformedOpenPGPSignatureException( + this, "Signature predates issuer key creation time."); + } + return; + } + PGPSignatureSubpacketVector hashed = signature.getHashedSubPackets(); if (hashed == null) {