From d536d3c542938f58631ca3ca13027a409872fd7e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 10 Mar 2026 12:20:05 +0100 Subject: [PATCH 1/7] Add OpenPGPComponentSignature.verify() 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 4e14325127..0750cab17d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1722,6 +1722,17 @@ public Date getKeyExpirationTime() return new Date(getTargetKeyComponent().getCreationTime().getTime() + 1000 * exp); } + /** + * Verify this signature. + * + * @throws PGPSignatureException if the signature cannot be verified successfully + */ + public void verify() + throws PGPSignatureException + { + verify(getIssuerCertificate().implementation); + } + /** * Verify this signature. * From 35b3fd768757454894136c455e40c1a63f29fe64 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 10 Mar 2026 21:13:00 +0100 Subject: [PATCH 2/7] Rework methods for accessing self-signatures --- .../openpgp/api/OpenPGPCertificate.java | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 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 0750cab17d..86ef14e40c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -736,6 +736,11 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c return fromOrigin.getChainAt(evaluationDate); } + private OpenPGPSignatureChains getSelfSignatureChainsFor(OpenPGPCertificateComponent component) + { + return getAllSignatureChainsFor(component).fromOrigin(getPrimaryKey()); + } + /** * Return all {@link OpenPGPSignatureChain OpenPGPSignatureChains} binding the given * {@link OpenPGPCertificateComponent}. @@ -979,8 +984,7 @@ public boolean test(OpenPGPComponentKey key, Date time) */ private OpenPGPSignatureChain getPreferenceSignature(Date evaluationTime) { - OpenPGPSignatureChain directKeyBinding = getPrimaryKey().getSignatureChains() - .fromOrigin(getPrimaryKey()) + OpenPGPSignatureChain directKeyBinding = getPrimaryKey().getSelfSignatureChains() .getCertificationAt(evaluationTime); if (directKeyBinding != null) @@ -991,8 +995,8 @@ private OpenPGPSignatureChain getPreferenceSignature(Date evaluationTime) List uidBindings = new ArrayList(); for (Iterator it = getPrimaryKey().getUserIDs().iterator(); it.hasNext(); ) { - OpenPGPSignatureChain uidBinding = getAllSignatureChainsFor(it.next()) - .fromOrigin(getPrimaryKey()).getCertificationAt(evaluationTime); + OpenPGPSignatureChain uidBinding = getSelfSignatureChainsFor(it.next()) + .getCertificationAt(evaluationTime); if (uidBinding != null) { @@ -1225,6 +1229,16 @@ public OpenPGPSignatureChains getSignatureChains() return chains; } + /** + * Return all self-signature chains on components of this certificate. + * + * @return self-signature chains + */ + protected OpenPGPSignatureChains getSelfSignatureChains() + { + return getSignatureChains().fromOrigin(getCertificate().getPrimaryKey()); + } + /** * Return the (at evaluation time) latest self certification signature binding this component. * @@ -1233,8 +1247,7 @@ public OpenPGPSignatureChains getSignatureChains() */ public OpenPGPComponentSignature getCertification(Date evaluationTime) { - OpenPGPSignatureChain certification = getSignatureChains() - .fromOrigin(getCertificate().getPrimaryKey()) + OpenPGPSignatureChain certification = getSelfSignatureChains() .getCertificationAt(evaluationTime); if (certification != null) { @@ -1251,8 +1264,7 @@ public OpenPGPComponentSignature getCertification(Date evaluationTime) */ public OpenPGPComponentSignature getRevocation(Date evaluationTime) { - OpenPGPSignatureChain revocation = getSignatureChains() - .fromOrigin(getCertificate().getPrimaryKey()) + OpenPGPSignatureChain revocation = getSelfSignatureChains() .getRevocationAt(evaluationTime); if (revocation != null) { @@ -1533,8 +1545,7 @@ public Date getKeyExpirationDateAt(Date evaluationTime) */ protected OpenPGPSignature.OpenPGPSignatureSubpacket getApplyingSubpacket(Date evaluationTime, int subpacketType) { - OpenPGPSignatureChain binding = getSignatureChains() - .fromOrigin(getCertificate().getPrimaryKey()) + OpenPGPSignatureChain binding = getSelfSignatureChains() .getCertificationAt(evaluationTime); if (binding == null) { @@ -2149,8 +2160,7 @@ public Date getCreationTime() @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { - OpenPGPSignatureChain currentDKChain = getSignatureChains() - .fromOrigin(getCertificate().getPrimaryKey()) + OpenPGPSignatureChain currentDKChain = getSelfSignatureChains() .getChainAt(evaluationTime); if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) { @@ -2338,8 +2348,7 @@ public OpenPGPComponentSignature getLatestDirectKeySelfSignature() public OpenPGPComponentSignature getLatestDirectKeySelfSignature(Date evaluationTime) { OpenPGPSignatureChain currentDKChain = getCertificate() - .getAllSignatureChainsFor(this) - .fromOrigin(this) + .getSelfSignatureChainsFor(this) .getCertificationAt(evaluationTime); if (currentDKChain != null && !currentDKChain.chainLinks.isEmpty()) { @@ -2368,8 +2377,7 @@ public OpenPGPComponentSignature getLatestKeyRevocationSelfSignature() public OpenPGPComponentSignature getLatestKeyRevocationSelfSignature(Date evaluationTime) { OpenPGPSignatureChain currentRevocationChain = getCertificate() - .getAllSignatureChainsFor(this) - .fromOrigin(this) + .getSelfSignatureChainsFor(this) .getRevocationAt(evaluationTime); if (currentRevocationChain != null && !currentRevocationChain.chainLinks.isEmpty()) { @@ -2718,8 +2726,7 @@ public OpenPGPPrimaryKey getPrimaryKey() @Override public OpenPGPComponentSignature getLatestSelfSignature(Date evaluationTime) { - OpenPGPSignatureChain currentChain = getSignatureChains() - .fromOrigin(getPrimaryKey()) + OpenPGPSignatureChain currentChain = getSelfSignatureChains() .getChainAt(evaluationTime); if (currentChain != null && !currentChain.chainLinks.isEmpty()) { From 4bcefcfb90d6eb3ae46cd480b0570d4909330d57 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 10 Mar 2026 21:14:35 +0100 Subject: [PATCH 3/7] getSignatureChainFor(): Constrain chains to origin --- .../bouncycastle/openpgp/api/OpenPGPCertificate.java | 11 +++++------ 1 file changed, 5 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 86ef14e40c..285cdc4bfe 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -713,7 +713,8 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c Date evaluationDate) { // Check if there are signatures at all for the component - OpenPGPSignatureChains chainsForComponent = getAllSignatureChainsFor(component); + OpenPGPSignatureChains chainsForComponent = getAllSignatureChainsFor(component) + .fromOrigin(origin); boolean isPrimaryKey = component == getPrimaryKey(); if (isPrimaryKey && chainsForComponent.getCertificationAt(evaluationDate) == null) { @@ -721,19 +722,17 @@ private OpenPGPSignatureChain getSignatureChainFor(OpenPGPCertificateComponent c OpenPGPUserId primaryUserId = getPrimaryUserId(evaluationDate); if (primaryUserId != null) { - chainsForComponent.addAll(getAllSignatureChainsFor(primaryUserId)); + chainsForComponent.addAll(getAllSignatureChainsFor(primaryUserId).fromOrigin(origin)); } } - // Isolate chains which originate from the passed origin key component - OpenPGPSignatureChains fromOrigin = chainsForComponent.fromOrigin(origin); - if (fromOrigin == null) + if (chainsForComponent == null) { return null; } // Return chain that currently takes precedence - return fromOrigin.getChainAt(evaluationDate); + return chainsForComponent.getChainAt(evaluationDate); } private OpenPGPSignatureChains getSelfSignatureChainsFor(OpenPGPCertificateComponent component) From 5a32a0f659d55e3c1f6936e1a1b4f55c9b5c2aca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 10 Mar 2026 21:16:42 +0100 Subject: [PATCH 4/7] Add methods for accessing third-party signatures for web-of-trust implementations --- .../openpgp/api/OpenPGPCertificate.java | 212 +++++++++++++++--- 1 file changed, 183 insertions(+), 29 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 285cdc4bfe..b13de6bfd9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1098,6 +1098,29 @@ public Date getExpirationTime(Date evaluationTime) return getPrimaryKey().getKeyExpirationDateAt(evaluationTime); } + public List getAllThirdPartySignatures() + { + List signatures = new ArrayList<>(); + signatures.addAll(getPrimaryKey().getThirdPartyCertifications()); + signatures.addAll(getPrimaryKey().getThirdPartyRevocations()); + return signatures; + } + + /** + * Return all {@link OpenPGPSignatureChains} that represent delegations by the given + * third-party {@link OpenPGPCertificate} on this certificates primary key. + * A delegation is a signature of type {@link PGPSignature#DIRECT_KEY}, which represents a + * delegation of trust and can be used to mark the certificate as a trusted introducer. + * + * @param thirdPartyCertificate third-party certificate which issued the delegation signatures + * @return all delegations by the third-party certificate over this certificate + */ + public OpenPGPSignatureChains getDelegationsBy(OpenPGPCertificate thirdPartyCertificate) + { + return getPrimaryKey().getThirdPartySignatureChainsBy(thirdPartyCertificate) + .getCertifications(); + } + /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which represents a delegation of trust. @@ -1124,11 +1147,23 @@ public OpenPGPSignatureChain getDelegationBy( OpenPGPCertificate thirdPartyCertificate, Date evaluationTime) { - OpenPGPSignatureChains chainsBy = getPrimaryKey(). - getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate, evaluationTime); - return chainsBy.getCertificationAt(evaluationTime); + return getDelegationsBy(thirdPartyCertificate).getCertificationAt(evaluationTime); } + /** + * Return all {@link OpenPGPSignatureChains} that represent revocations of delegations by the given + * third-party {@link OpenPGPCertificate} on this certificates primary key. + * A delegation revocation is a signature of type {@link PGPSignature#KEY_REVOCATION}, which revokes an earlier + * delegation of trust. + * + * @param thirdPartyCertificate third-party certificate which issued the revocation signatures + * @return all revocations by the third-party certificate over this certificate + */ + public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCertificate) + { + return getPrimaryKey().getThirdPartySignatureChainsBy(thirdPartyCertificate) + .getRevocations(); + } /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which represents a revocation of trust. @@ -1153,9 +1188,8 @@ public OpenPGPSignatureChain getRevocationBy( OpenPGPCertificate thirdPartyCertificate, Date evaluationTime) { - OpenPGPSignatureChains chainsBy = getPrimaryKey() - .getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate, evaluationTime); - return chainsBy.getRevocationAt(evaluationTime); + return getRevocationsBy(thirdPartyCertificate) + .getRevocationAt(evaluationTime); } /** @@ -1238,6 +1272,28 @@ protected OpenPGPSignatureChains getSelfSignatureChains() return getSignatureChains().fromOrigin(getCertificate().getPrimaryKey()); } + /** + * Return all third-party signature chains originating from the given certificate on this certificate. + * + * @param thirdPartyCertificate third-party certificate + * @return all signature chains over this component which originate at the given third-party certificate. + */ + protected OpenPGPSignatureChains getThirdPartySignatureChainsBy( + OpenPGPCertificate thirdPartyCertificate) + { + return getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate); + } + + /** + * Return the latest self-signature certification binding this component. + * + * @return latest component certification signature + */ + public OpenPGPComponentSignature getCertification() + { + return getCertification(new Date()); + } + /** * Return the (at evaluation time) latest self certification signature binding this component. * @@ -1255,6 +1311,16 @@ public OpenPGPComponentSignature getCertification(Date evaluationTime) return null; } + /** + * Return the latest revocation signature on this component. + * + * @return latest revocation + */ + public OpenPGPComponentSignature getRevocation() + { + return getRevocation(new Date()); + } + /** * Return the (at evaluation time) latest self revocation signature revoking this component. * @@ -1272,6 +1338,46 @@ public OpenPGPComponentSignature getRevocation(Date evaluationTime) return null; } + /** + * Return a list of all third-party-issued certifying + * {@link OpenPGPComponentSignature OpenPGPComponentSignatures} on this component. + * + * @return all third-party issued certification signatures on this component. + */ + public List getThirdPartyCertifications() + { + OpenPGPSignatureChains allChains = getSignatureChains(); + List thirdPartyCertifications = new ArrayList<>(); + for (OpenPGPSignatureChain chain : allChains) + { + if (chain.isCertification() && chain.getRootLinkIssuer() == null) + { + thirdPartyCertifications.add(chain.getSignature()); + } + } + return thirdPartyCertifications; + } + + /** + * Return a list of all third-party-issued revoking + * {@link OpenPGPComponentSignature OpenPGPComponentSignatures} on this component. + * + * @return all third-party issued revocation signatures on this component. + */ + public List getThirdPartyRevocations() + { + OpenPGPSignatureChains allChains = getSignatureChains(); + List thirdPartyRevocations = new ArrayList<>(); + for (OpenPGPSignatureChain chain : allChains) + { + if (chain.isRevocation() && chain.getRootLinkIssuer() == null) + { + thirdPartyRevocations.add(chain.getSignature()); + } + } + return thirdPartyRevocations; + } + /** * Return the latest self-signature on the component. * That might either be a certification signature, or a revocation. @@ -1596,36 +1702,35 @@ protected OpenPGPSignature.OpenPGPSignatureSubpacket getApplyingSubpacket(Date e } /** - * Iterate over signatures issued over this component by the given 3rd-party certificate, - * merge them with the (at evaluation time) valid self-certification chain and return the - * results. - * - * @param thirdPartyCertificate certificate of a 3rd party - * @param evaluationTime reference time - * @return all 3rd party signatures on this component, merged with their issuer chains + * Find all third-party {@link OpenPGPComponentSignature component signatures} on this component, which + * were issued by the given third-party certificate. Find the {@link OpenPGPSignatureChains} binding the + * issuing component key and append the third-party signatures as leaf links. Return all such chains. + * This is a utility method for third-party signature chain evaluation. + * @param thirdPartyCertificate third-party certificate + * @return dangling external signatures with issuer signature chains prepended */ protected OpenPGPSignatureChains getMergedDanglingExternalSignatureChainEndsFrom( - OpenPGPCertificate thirdPartyCertificate, - Date evaluationTime) + OpenPGPCertificate thirdPartyCertificate) { OpenPGPSignatureChains chainsBy = new OpenPGPSignatureChains(this); - OpenPGPSignatureChains allChains = getCertificate().getAllSignatureChainsFor(this) - .getChainsAt(evaluationTime); + OpenPGPSignatureChains allChains = getCertificate().getAllSignatureChainsFor(this); + for (Iterator it = allChains.iterator(); it.hasNext(); ) { OpenPGPSignatureChain.Link rootLink = it.next().getRootLink(); - for (Iterator it2 = thirdPartyCertificate.getKeys().iterator(); it2.hasNext(); ) + for (Iterator thirdPartyKeys = thirdPartyCertificate.getKeys().iterator(); thirdPartyKeys.hasNext(); ) { - OpenPGPComponentKey issuerKey = it2.next(); + OpenPGPComponentKey issuerKey = thirdPartyKeys.next(); if (KeyIdentifier.matches( - rootLink.getSignature().getKeyIdentifiers(), - issuerKey.getKeyIdentifier(), - true)) + rootLink.getSignature().getKeyIdentifiers(), + issuerKey.getKeyIdentifier(), + true)) { - OpenPGPSignatureChain externalChain = issuerKey.getSignatureChains().getChainAt(evaluationTime); + OpenPGPSignatureChain externalChain = issuerKey.getSelfSignatureChains() + .getChainAt(rootLink.getSignature().getCreationTime()); externalChain = externalChain.plus( - new OpenPGPComponentSignature(rootLink.signature.getSignature(), issuerKey, this)); + new OpenPGPComponentSignature(rootLink.signature.getSignature(), issuerKey, this)); chainsBy.add(externalChain); } } @@ -2740,6 +2845,11 @@ protected OpenPGPComponentKey getKeyComponent() return primaryKey; } + public OpenPGPSignatureChains getCertificationsBy(OpenPGPCertificate thirdPartyCertificate) + { + return getThirdPartySignatureChainsBy(thirdPartyCertificate) + .getCertifications(); + } /** * Return the latest {@link OpenPGPSignatureChain} containing a certification issued by the given * 3rd-party certificate over this identity component. @@ -2764,8 +2874,13 @@ public OpenPGPSignatureChain getCertificationBy( OpenPGPCertificate thirdPartyCertificate, Date evaluationTime) { - OpenPGPSignatureChains chainsBy = getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate, evaluationTime); - return chainsBy.getCertificationAt(evaluationTime); + return getCertificationsBy(thirdPartyCertificate).getCertificationAt(evaluationTime); + } + + public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCertificate) + { + return getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate) + .getRevocations(); } /** @@ -2792,8 +2907,7 @@ public OpenPGPSignatureChain getRevocationBy( OpenPGPCertificate thirdPartyCertificate, Date evaluationTime) { - OpenPGPSignatureChains chainsBy = getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate, evaluationTime); - return chainsBy.getRevocationAt(evaluationTime); + return getRevocationsBy(thirdPartyCertificate).getRevocationAt(evaluationTime); } @Override @@ -2894,10 +3008,12 @@ public String 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. + * is bound to a 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. + * Another example is a third-party certification over a user-id or certificates primary key. Here, the chain + * originates from the certifiers primary key and ends at the user-id or primary key of the signee. * 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. @@ -3539,6 +3655,44 @@ public boolean isEmpty() return chains.isEmpty(); } + /** + * Filter for certifications. + * + * @return certification signature chains + */ + public OpenPGPSignatureChains getCertifications() + { + OpenPGPSignatureChains certifications = new OpenPGPSignatureChains(targetComponent); + for (Iterator it = chains.iterator(); it.hasNext(); ) + { + OpenPGPSignatureChain chain = it.next(); + if (chain.isCertification()) + { + certifications.add(chain); + } + } + return certifications; + } + + /** + * Filter for revocations. + * + * @return revocation signature chains + */ + public OpenPGPSignatureChains getRevocations() + { + OpenPGPSignatureChains revocations = new OpenPGPSignatureChains(targetComponent); + for (Iterator it = chains.iterator(); it.hasNext(); ) + { + OpenPGPSignatureChain chain = it.next(); + if (chain.isRevocation()) + { + revocations.add(chain); + } + } + return revocations; + } + /** * Return a positive certification chain for the component for the given evaluationTime. * From eae0d865d0118f50309c0b64661ca516985407ef Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Mar 2026 00:06:00 +0100 Subject: [PATCH 5/7] Add documentation for OpenPGPSignatureChain --- .../openpgp/api/OpenPGPCertificate.java | 47 +++++++++++++++++++ 1 file changed, 47 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 b13de6bfd9..4e2ab7e0c6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -3017,6 +3017,53 @@ public String toString() * 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. + *

+ * Example: + *

+     *           [PRIMARY KEY 0xAAAA]<--------_
+     *            /         \       \         |
+     *           /           \       \__(DIRECT KEY SIG #0)
+     *   (USERID BINDING #1) |
+     *          |    (SUBKEY BINDING #2)
+     *          v           |
+     *  [USERID Alice]      v
+     *                  [SUBKEY 0xAABB]
+     * 
+ * Here, the certificates components are bound like follows: + *
    + *
  • Primary Key {@code 0xAAAA}: {@code [0xAAAA->#0->0xAAAA]}
  • + *
  • UserID {@code Alice}: {@code [0xAAAA->#0->#1->Alice]}
  • + *
  • Subkey {@code 0xAABB}: {@code [0xAAAA->#0->#2->0xAABB]}
  • + *
+ * Note: If the certificate does not carry a direct-key self-signature, the primary user-id + * binding would be used as root link in its place. + * A binding can also be a revocation. In such case, the whole chain represents a revocation. + *

+ * In Web-of-Trust scenarios, signature chains can span multiple certificates. + *

+ * Example: + *

+     *                               ________________
+     *                              v               |
+     *               [PRIMARY KEY 0xAAAA]---(DIRECT KEY SIG #0)
+     *               /         |     \
+     *  (USERID BINDING #1)    |      \
+     *        |                |  (DELEGATION #2)-->[PRIMARY KEY 0xBBBB]
+     *        |                \                            |
+     *        v                 \                  (USERID BINDING #3)
+     * [USERID Alice]      (CERTIFICATION #4)               |
+     *                             \                        v
+     *                              \________________>[USERID Bob]
+     * 
+ * Here, Alice delegated trust to Bob by issuing a direct key delegation signature ({@code #0}) + * over Bobs primary key, as well as a 3rd-party certification signature for UserID Bob. + * Now there are the following paths of trust from Alice's certificate down to Bobs certificate: + *
    + *
  • Bobs Certificate (primary key {@code 0xBBBB}): {@code [0xAAAA->#0->#2->0xBBBB}
  • + *
  • Bobs UserID {@code Bob}: {@code [0xAAAA->#0->#4->Bob}
  • + *
+ * Note, that certificate {@code 0xAAAA} holds signatures {@code #0,#1}, while + * certificate {@code 0xBBBB} holds signatures {@code #2,#3,#4}. */ public static class OpenPGPSignatureChain implements Comparable, Iterable From ec903e29ab535bbec4a24b586ff15fa2f87993c5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Mar 2026 00:35:14 +0100 Subject: [PATCH 6/7] Improve documentation of certification/delegation/revocation getters --- .../openpgp/api/OpenPGPCertificate.java | 113 ++++++++++++++---- 1 file changed, 88 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 4e2ab7e0c6..f3fb0946e4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -1098,7 +1098,14 @@ public Date getExpirationTime(Date evaluationTime) return getPrimaryKey().getKeyExpirationDateAt(evaluationTime); } - public List getAllThirdPartySignatures() + /** + * Return a list containing all third-party issued key signatures on the primary key (delegations + * and revocations). + * Note: This list DOES NOT contain third-party certifications over user-ids. + * + * @return third-party key signatures + */ + public List getAllThirdPartyKeySignatures() { List signatures = new ArrayList<>(); signatures.addAll(getPrimaryKey().getThirdPartyCertifications()); @@ -1111,9 +1118,13 @@ public List getAllThirdPartySignatures() * third-party {@link OpenPGPCertificate} on this certificates primary key. * A delegation is a signature of type {@link PGPSignature#DIRECT_KEY}, which represents a * delegation of trust and can be used to mark the certificate as a trusted introducer. + * Each delegation is returned as an {@link OpenPGPSignatureChain} where the delegation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at delegation signature creation time. * * @param thirdPartyCertificate third-party certificate which issued the delegation signatures - * @return all delegations by the third-party certificate over this certificate + * @return full signature chains of all delegations by the third-party certificate over this + * certificate */ public OpenPGPSignatureChains getDelegationsBy(OpenPGPCertificate thirdPartyCertificate) { @@ -1124,10 +1135,13 @@ public OpenPGPSignatureChains getDelegationsBy(OpenPGPCertificate thirdPartyCert /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which represents a delegation of trust. + * The delegation is returned as an {@link OpenPGPSignatureChain} where the delegation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at delegation signature creation time. * If no delegation signature is found, return null. * * @param thirdPartyCertificate {@link OpenPGPCertificate} of a 3rd party. - * @return chain containing the latest delegation issued by the 3rd-party certificate + * @return full signature chain of the latest delegation issued by the 3rd-party certificate */ public OpenPGPSignatureChain getDelegationBy(OpenPGPCertificate thirdPartyCertificate) { @@ -1137,11 +1151,15 @@ public OpenPGPSignatureChain getDelegationBy(OpenPGPCertificate thirdPartyCertif /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which represents a delegation of trust at evaluation time. + * The delegation is returned as an {@link OpenPGPSignatureChain} where the delegation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at delegation signature creation time. * If no delegation signature is found, return null. * * @param thirdPartyCertificate {@link OpenPGPCertificate} of a 3rd party. * @param evaluationTime reference time - * @return chain containing the (at evaluation time) latest delegation issued by the 3rd-party certificate + * @return full signature chain of the (at evaluation time) latest delegation issued by the + * 3rd-party certificate */ public OpenPGPSignatureChain getDelegationBy( OpenPGPCertificate thirdPartyCertificate, @@ -1155,9 +1173,13 @@ public OpenPGPSignatureChain getDelegationBy( * third-party {@link OpenPGPCertificate} on this certificates primary key. * A delegation revocation is a signature of type {@link PGPSignature#KEY_REVOCATION}, which revokes an earlier * delegation of trust. + * Each revocation is returned as an {@link OpenPGPSignatureChain} where the revocation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at revocation signature creation time. * * @param thirdPartyCertificate third-party certificate which issued the revocation signatures - * @return all revocations by the third-party certificate over this certificate + * @return full signature chains for all revocations by the third-party certificate over this + * certificate */ public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCertificate) { @@ -1167,9 +1189,13 @@ public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCert /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which represents a revocation of trust. + * The revocation is returned as an {@link OpenPGPSignatureChain} where the revocation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at revocation signature creation time. * * @param thirdPartyCertificate {@link OpenPGPCertificate} of a 3rd party. - * @return chain containing the latest revocation issued by the 3rd party certificate + * @return full signature chain containing the latest revocation issued by the 3rd party + * certificate */ public OpenPGPSignatureChain getRevocationBy(OpenPGPCertificate thirdPartyCertificate) { @@ -1179,10 +1205,14 @@ public OpenPGPSignatureChain getRevocationBy(OpenPGPCertificate thirdPartyCertif /** * Return an {@link OpenPGPSignatureChain} from the given 3rd-party certificate to this certificate, * which (at evaluation time) represents a revocation of trust. + * The revocation is returned as an {@link OpenPGPSignatureChain} where the revocation + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at revocation signature creation time. * * @param thirdPartyCertificate {@link OpenPGPCertificate} of a 3rd party. * @param evaluationTime reference time - * @return chain containing the (at evaluation time) latest revocation issued by the 3rd party certificate + * @return full signature chain containing the (at evaluation time) latest revocation issued + * by the 3rd party certificate */ public OpenPGPSignatureChain getRevocationBy( OpenPGPCertificate thirdPartyCertificate, @@ -2845,17 +2875,31 @@ protected OpenPGPComponentKey getKeyComponent() return primaryKey; } + /** + * Return all third-party certification signatures issued by the third-party certificate over + * this component. + * Each certification is returned as an {@link OpenPGPSignatureChain} where the certification + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at certification signature creation time. + * + * @param thirdPartyCertificate third-party certificate + * @return all certifications by the third-party cert as full signature chains + */ public OpenPGPSignatureChains getCertificationsBy(OpenPGPCertificate thirdPartyCertificate) { return getThirdPartySignatureChainsBy(thirdPartyCertificate) .getCertifications(); } + /** - * Return the latest {@link OpenPGPSignatureChain} containing a certification issued by the given - * 3rd-party certificate over this identity component. + * Return the latest third-party certification signature issued by the third-party certificate + * over this component. + * The certification is returned as an {@link OpenPGPSignatureChain} where the certification + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at certification signature creation time. * - * @param thirdPartyCertificate certificate of a 3rd party - * @return 3rd party certification + * @param thirdPartyCertificate third-party certificate + * @return latest certification by the third-party cert as full signature chain */ public OpenPGPSignatureChain getCertificationBy(OpenPGPCertificate thirdPartyCertificate) { @@ -2863,12 +2907,15 @@ public OpenPGPSignatureChain getCertificationBy(OpenPGPCertificate thirdPartyCer } /** - * Return the latest (at evaluation time) {@link OpenPGPSignatureChain} containing a certification - * issued by the given 3rd-party certificate over this identity component. + * Return the - at evaluation time - latest third-party certification signature issued by the + * third-party certificate over this component. + * The certification is returned as an {@link OpenPGPSignatureChain} where the certification + * is the leaf link prepended by the binding signature chain of the issuing third-party + * certificate component at certification signature creation time. * - * @param evaluationTime reference time - * @param thirdPartyCertificate certificate of a 3rd party - * @return 3rd party certification + * @param thirdPartyCertificate third-party certificate + * @param evaluationTime reference time + * @return at reference time latest certification by the third-party cert as full signature chain */ public OpenPGPSignatureChain getCertificationBy( OpenPGPCertificate thirdPartyCertificate, @@ -2877,6 +2924,16 @@ public OpenPGPSignatureChain getCertificationBy( return getCertificationsBy(thirdPartyCertificate).getCertificationAt(evaluationTime); } + /** + * Return all third-party revocation signatures issued by the third-party certificate over + * this component. + * Each revocation is returned as an {@link OpenPGPSignatureChain} where the revocation is the + * leaf link prepended by the binding signature chain of the issuing third-party certificate + * component at revocation signature creation time. + * + * @param thirdPartyCertificate third-party certificate + * @return all revocations by the third-party cert as full signature chains + */ public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCertificate) { return getMergedDanglingExternalSignatureChainEndsFrom(thirdPartyCertificate) @@ -2884,11 +2941,14 @@ public OpenPGPSignatureChains getRevocationsBy(OpenPGPCertificate thirdPartyCert } /** - * Return the latest {@link OpenPGPSignatureChain} containing a revocation issued by the given - * 3rd-party certificate over this identity component. + * Return the latest third-party revocation signature issued by the third-party certificate + * over this component. + * The revocation is returned as an {@link OpenPGPSignatureChain} where the revocation is the + * leaf link prepended by the binding signature chain of the issuing third-party certificate + * component at revocation signature creation time. * - * @param thirdPartyCertificate certificate of a 3rd party - * @return 3rd party revocation signature + * @param thirdPartyCertificate third-party certificate + * @return latest revocation by the third-party cert as full signature chain */ public OpenPGPSignatureChain getRevocationBy(OpenPGPCertificate thirdPartyCertificate) { @@ -2896,12 +2956,15 @@ public OpenPGPSignatureChain getRevocationBy(OpenPGPCertificate thirdPartyCertif } /** - * Return the latest (at evaluation time) {@link OpenPGPSignatureChain} containing a revocation issued by the given - * 3rd-party certificate over this identity component. + * Return the - at evaluation time - latest third-party revocation signature issued by the + * third-party certificate over this component. + * The revocation is returned as an {@link OpenPGPSignatureChain} where the revocation is the + * leaf link prepended by the binding signature chain of the issuing third-party certificate + * component at revocation signature creation time. * - * @param thirdPartyCertificate certificate of a 3rd party - * @param evaluationTime reference time - * @return 3rd party revocation signature + * @param thirdPartyCertificate third-party certificate + * @param evaluationTime reference time + * @return at reference time latest revocation by the third-party cert as full signature chain */ public OpenPGPSignatureChain getRevocationBy( OpenPGPCertificate thirdPartyCertificate, From 4afb70c08c5811b277862c3ac9f588aca72bef6b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Mar 2026 00:39:42 +0100 Subject: [PATCH 7/7] Fix javadoc --- .../java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java | 3 ++- 1 file 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 f3fb0946e4..0a648ab97d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java @@ -3083,7 +3083,8 @@ public String toString() *

* Example: *

-     *           [PRIMARY KEY 0xAAAA]<--------_
+     *                           v-----------\
+     *           [PRIMARY KEY 0xAAAA]         \
      *            /         \       \         |
      *           /           \       \__(DIRECT KEY SIG #0)
      *   (USERID BINDING #1) |