Skip to content

openpgp: prevent panic when candidate identity has no self-signature#313

Open
lexfrei wants to merge 1 commit into
ProtonMail:mainfrom
lexfrei:fix/prefer-identity-nil-self-signature-panic
Open

openpgp: prevent panic when candidate identity has no self-signature#313
lexfrei wants to merge 1 commit into
ProtonMail:mainfrom
lexfrei:fix/prefer-identity-nil-self-signature-panic

Conversation

@lexfrei
Copy link
Copy Markdown

@lexfrei lexfrei commented Jun 2, 2026

Summary

shouldPreferIdentity panics with a nil pointer dereference when an identity in Entity.Identities has a nil SelfSignature. It already guards the case where the existing primary has a nil SelfSignature, but dereferences the candidate's SelfSignature unconditionally a few lines later.

A user ID is stored with a nil SelfSignature when it carries only a revocation, or when its self-signature fails to verify — addUserID still inserts the identity into the map in the revocation-only case. Because PrimaryIdentity ranges over the Identities map, whose iteration order is randomized, that empty identity lands in the candidate slot in a fraction of runs and panics. This is the same class of failure fixed for the existing-identity side in #99; the candidate side was left unguarded.

Fix

Add the symmetric nil check: if the candidate has no self-signature, keep the existing identity (which is known to have a valid one). An identity with a real self-signature is always preferred over one without, regardless of map iteration order.

Impact

go-crypto is the OpenPGP implementation OpenTofu uses to authenticate provider packages, and the panic is reachable from PrimarySelfSignatureEntityList.KeysById during signature verification. When it fires, tofu init aborts with a Go panic and stack trace instead of installing the provider, so that provider — and therefore every subsequent OpenTofu command — cannot be used.

Because the crash depends on the randomized map iteration order, it is intermittent: re-running init often succeeds. That makes it a flaky CI failure, which is costly to diagnose and easy to misattribute to infrastructure rather than to a library bug.

The trigger is not a contrived edge case. Any caller verifying signatures with a key whose identities include a user ID that has no usable self-signature is affected — for example a revoked user ID, or one whose self-signature fails to verify. Long-lived signing keys commonly accumulate such user IDs (e.g. revoked addresses from a previous employer), so real-world provider keys hit this.

Reproduction

Observed with OpenTofu (which pins go-crypto v1.4.1) installing a provider whose signing key carried a user ID without a usable self-signature: tofu init panicked in a large fraction of runs during signature authentication, while succeeding on others.

Tests

  • A table test on shouldPreferIdentity covering the candidate-nil, existing-nil, and both-nil cases.
  • TestPrimaryIdentityWithNilSelfSignature builds an Entity with one valid and one self-signature-less identity and calls PrimaryIdentity in a loop to defeat the randomized map order, asserting the identity with a valid self-signature is always chosen and that no panic occurs.

Both tests panic at keys.go:115 without the fix and pass with it.

A user ID can be stored with a nil SelfSignature, for example when it
only carries a revocation or when its self-signature fails to verify.
shouldPreferIdentity already guards the case where the existing primary
has no self-signature, but dereferences the candidate's SelfSignature
unconditionally. Because PrimaryIdentity iterates the Identities map in
a non-deterministic order, the candidate is the empty identity in a
fraction of runs, which panics.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant