-
Notifications
You must be signed in to change notification settings - Fork 1.3k
CVE‐2026‐71887
Title: OpenPGP data signature accepted from a signing subkey without cross-certification.
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.1.8 requires that "a signature that binds a signing subkey MUST have an Embedded Signature subpacket in this binding signature that contains a 0x19 signature made by the signing subkey on the primary key and subkey", and sec. 10.1.3 repeats it as a property of the certificate structure. That embedded Primary Key Binding signature — the back signature, or cross-certification — is the subkey's own statement that it belongs to the primary key it is bound under. Without it a Subkey Binding signature is one-sided: anyone can assert a binding over a public subkey they did not generate, and the requirement exists precisely so that assertion carries no weight.
Bouncy Castle's high-level OpenPGP API read the subkey's key flags two different ways, and the two disagreed. OpenPGPCertificate.OpenPGPComponentKey.isSigningKey() resolves the flags through getKeyFlags() and getApplyingSubpacket(), which — when the Subkey Binding signature carries no Key Flags subpacket — falls back to the primary key's direct-key or primary User ID self-signature, so the subkey inherited the primary's SIGN_DATA and counted as signing-capable. verifyEmbeddedPrimaryKeyBinding(), which is what enforces the requirement above, reads the binding signature's own hashed subpackets instead; finding no SIGN_DATA there it returned early as a non-signing key, and the MalformedOpenPGPSignatureException for a signing subkey with no embedded Primary Key Binding signature was never reached.
The same subkey was therefore signing-capable — so its data signatures were attributed to the certificate and OpenPGPSignature.OpenPGPDocumentSignature.isValid() could return true — while being exempt from the cross-certification requirement. GnuPG refuses the identical certificate and message as not cross-certified.
An attacker needs only the victim's public signing subkey, which is public material. They bind it to their own primary key with a Subkey Binding signature they are able to make — carrying no Key Flags, and no embedded Primary Key Binding signature, which they cannot make without the subkey's private key — and publish the result. A relying party that verifies one of the victim's genuinely signed messages against that certificate is told the signature is valid and is given the attacker's certificate as its issuer. Because a certificate's User IDs are self-asserted, the attacker's primary key can carry any identity string, so a verifier that pins on the subkey's fingerprint or key ID while taking the identity from the enclosing certificate reports a real signature under an attacker-chosen identity.
This is misattribution of a genuine signature rather than forgery of a new one: no private key is recovered, and the signature must be one the grafted subkey actually made, since the attacker cannot produce a new one. Attribution is deterministic when a message is verified against a specific supplied certificate; in a populated keyring that already holds the genuine owner's certificate for that subkey key ID, resolution is last-writer-wins on the key ID and the takeover is not guaranteed. The low-level PGPSignature / PGPPublicKeyRing API performs no binding checks by design and is unaffected.
Key Flags are a statement about the key the carrying signature refers to (RFC 9580 sec. 5.2.3.29), so a subkey no longer inherits them from the certificate-wide signatures of the primary key: a Subkey Binding signature that omits the subpacket now leaves the subkey with no capabilities rather than the primary's, which makes the flags the cross-certification check consults the same flags every other decision consults. Preferences and the other subpackets a direct-key signature carries are inherited as before, and the primary key itself — whose flags legitimately come from its own direct-key or User ID self-signature — is unaffected.
The fix was introduced in commit b51452fa48.
Credit: Arpan Sharma.