Skip to content

pgp: accept multi-signed releases when the keyring holds only some signers - #1629

Open
daniel-pinder wants to merge 1 commit into
aptly-dev:masterfrom
daniel-pinder:pgp-accept-multisigned-releases
Open

pgp: accept multi-signed releases when the keyring holds only some signers#1629
daniel-pinder wants to merge 1 commit into
aptly-dev:masterfrom
daniel-pinder:pgp-accept-multisigned-releases

Conversation

@daniel-pinder

Copy link
Copy Markdown

Problem

aptly mirror create fails against every Debian suite when the configured keyring holds only the key you actually trust:

ERROR: unable to fetch mirror: verification of detached signature failed: exit status 2

Debian co-signs each suite's InRelease with more than one archive key — trixie-security carries both the trixie and the bookworm security keys, and trixie/trixie-updates carry the stable release key alongside the archive key. gpgv verifies the signature it can check, prints Good signature from ..., and also emits NO_PUBKEY for the others, exiting 2. Both verifiers treat that as failure:

  • pgp/gnupg.go runGpgv returns an error on any non-zero gpgv exit, after having parsed GOODSIG/NO_PUBKEY into KeyInfo.
  • pgp/openpgp.go checkDetachedSignature returns ErrUnknownIssuer whenever missingKeys > 0.

So mirroring Debian requires importing every key Debian happens to sign with, including keys for releases you do not mirror. apt does not require this — methods/gpgv.cc is fatal only when there is no good signature or a BADSIG; signatures from keys outside the keyring are reported and ignored.

Change

Adopt apt's rule for unknown signers, in one path only. When gpgv exits non-zero, the --status-fd stream is parsed per signature (NEWSIG block) and the result is a success only when:

  • the process exited (a signal death is never success);
  • at least one block is a good signature from a key in the keyring;
  • at least one block is an unknown issuer (ERRSIG reason 9 with NO_PUBKEY), i.e. the non-zero exit is fully explained;
  • nothing else appears.

BADSIG, EXPSIG, EXPKEYSIG or REVKEYSIG — a signature by a key the keyring does hold — still fails, as does any unexplained gpgv failure. Where gpgv exits 0, nothing changes.

KEYEXPIRED and KEYREVOKED are deliberately not disqualifiers: GnuPG's doc/DETAILS notes KEYEXPIRED is emitted for an expired subkey even when that subkey was not used, so treating it as a verdict rejects valid signatures from keys that merely carry an old subkey. EXPKEYSIG is the signature-scoped token and is handled.

checkDetachedSignature gets the equivalent change for the internal verifier (len(signers) == missingKeys).

Notes for reviewers

  • This also reaches the uploader ACL. deb/changes.go feeds KeyInfo.GoodKeys into Changes.SignatureKeys, used by deb/uploaders.go. A .changes signed by an allowed key plus an unknown extra key is now accepted. The unknown key grants nothing, and it is consistent with "signatures from keys we do not hold carry no meaning", but it is a behaviour change outside mirroring and deserves a second opinion.
  • TestVerifyClearsigned is rebased onto generated non-expiring fixture keys. That assertion is already failing on master with GnuPG 2.4: fixture key 04EE7237B7D453EC (Debian stretch) expired 2025-05-20, so gpgv emits EXPKEYSIG rather than GOODSIG and the expected three-key list is down to two. The replacement fixtures are generated by the commands documented in a comment in pgp/verify_test.go, and only public key material is committed.
  • New tests cover: a multi-signed release with a partial keyring (both verifiers), a bad signature from a held key alongside a good one, an expired and a revoked held key, an unused expired subkey, an unexplained gpgv failure after a GOODSIG, and a release with no trusted signer at all.

Verified against the live Debian archives: with only debian-archive-trixie-security-automatic.gpg in the keyring, aptly mirror create ... http://deb.debian.org/debian-security trixie-security updates/main now succeeds; with a keyring holding none of its signers it still fails.

…gners

Debian co-signs each suite's InRelease with more than one archive key, so
gpgv reports a good signature from the key you trust and NO_PUBKEY for the
others, exiting 2. Both verifiers treat that as failure, which means
mirroring Debian requires importing every key Debian happens to sign with.
apt does not require this: it is fatal only when there is no good signature
or a BADSIG.

Adopt that rule for unknown signers. When gpgv exits non-zero the status
stream is parsed per NEWSIG block and the result is a success only when the
process exited, at least one block is a good signature from a held key, at
least one block is an unknown issuer (ERRSIG reason 9 with NO_PUBKEY), and
nothing else appears. BADSIG, EXPSIG, EXPKEYSIG and REVKEYSIG from a held
key still fail, as does any unexplained failure. The exit-zero path is
unchanged.

KEYEXPIRED and KEYREVOKED are deliberately not disqualifiers: doc/DETAILS
notes KEYEXPIRED is emitted for an expired subkey even when that subkey was
not used, so treating it as a verdict rejects valid signatures.

checkDetachedSignature gets the equivalent change for the internal verifier.
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