Skip to content

CVE‐2026‐71886

David Hook edited this page Oct 2, 2026 · 1 revision

Title: OpenPGP certification accepted from a subkey without certification authority.

Issue affecting: BC before 1.86 (from 1.81).

Fixed versions: BC 1.86.

Platform affected: Java 8 and later.

RFC 9580 sec. 5.2.3.29 gives the certification key flag (CERTIFY_OTHER, "This key may be used to certify other keys") separately from the data-signing flag (SIGN_DATA). That separation is what lets a certificate keep its certification-capable primary key offline while a signing subkey sits on an online system: compromise of the online subkey is not supposed to carry the primary key's authority to bind identities.

Bouncy Castle's high-level OpenPGP certificate API never enforced it. OpenPGPCertificate.getCertificationBy() and getDelegationBy() resolve a third-party signature through getMergedDanglingExternalSignatureChainEndsFrom(), which matches the signature's issuer key identifier against every component key of the third-party certificate, obtains that component's own binding chain, and appends the signature as the leaf link. The binding chain and the leaf signature are then verified cryptographically — but nothing required the issuing component to hold CERTIFY_OTHER at the signature's creation time.

A subkey bound only with SIGN_DATA could therefore issue a positive User ID certification over an attacker-controlled identity, or a full-trust, depth-one direct-key signature delegating introducer trust, and the API returned it as a valid signature chain attributed to the third-party certificate as a whole. An application treating getCertificationBy(...).isValid() or getDelegationBy(...) as an identity or trusted-introducer decision — the stated purpose of these methods — would attribute the attacker's assertion to the offline primary key. The same held for a legacy RSA subkey bound only for encryption, whose algorithm is nonetheless able to sign, where the attacker has the private operation or an equivalent raw signing oracle.

This is not a forged primary-key signature and does not recover any private key; it promotes an already-compromised restricted component to a stronger role than its certificate granted it, defeating the containment the key-flag separation exists to provide.

A third-party certification or delegation is now attributed to the issuing certificate only when the component key that made it is the primary key, or is a subkey holding CERTIFY_OTHER at the time the signature was created, so certification-capable subkeys continue to work and the check is on the granted capability rather than on being the primary key. Primary keys are accepted whatever their key flags say — a primary key is certification-capable by construction, and certificates carrying no key flags subpacket at all are common, where the capability predicate answers false for want of a subpacket to read rather than because authority was withheld. Third-party revocations are deliberately outside the rule, since declining to honour one would keep trust alive rather than withdraw it.

The fix was introduced in commit f0d5c8535a.

Credit: Bhargava Shastry.

Clone this wiki locally