Compare --require-signature with the key that signed (closes #167)
check / check (push) Canceled after 0s

check and fetch --require-signature compared the required fingerprint
with the first key in the manifest's embedded public key block, while
gpg accepted a good signature by any key in that block.

Loading a signed manifest now refuses one whose embedded block holds
more than one primary key, counted as gpg reads the block, or whose
signer field is not the primary key fingerprint gpg reports for the
signature. --require-signature compares with the signer field, which
loading has checked. Signing names and embeds the key gpg reports it
signed with, so a key ID matching several keys still writes a manifest
that loads. docs/FORMAT.md states what a verifier checks.

Model: opus-5-5
This commit was merged in pull request #171.
This commit is contained in:
2026-10-07 13:59:17 +02:00
parent 2a174e3ba2
commit 0762a728d4
11 changed files with 479 additions and 181 deletions
+13 -3
View File
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"strings"
"github.com/klauspost/compress/zstd"
"github.com/spf13/afero"
@@ -28,6 +29,8 @@ var (
errInvalidManifestPath = errors.New("manifest contains invalid path")
errDecodedTooLarge = errors.New(
"manifest would take too much memory to decode")
errSignerNotSigningKey = errors.New(
"signer is not the fingerprint of the key that made the signature")
)
// validateUUID checks that the byte slice is the 16 bytes of a binary UUID.
@@ -60,8 +63,10 @@ func (m *manifest) validateOuterHeader() error {
return nil
}
// verifyOuterIntegrity checks the hash of the compressed payload and,
// if a signature is present, verifies it against the embedded public key.
// verifyOuterIntegrity checks the hash of the compressed payload and, if a
// signature is present, verifies it against the embedded public key, which
// must be one key, and checks that the signer field is that key's
// fingerprint.
func (m *manifest) verifyOuterIntegrity() error {
h := sha256.New()
@@ -91,7 +96,7 @@ func (m *manifest) verifyOuterIntegrity() error {
}
// Loading a manifest takes no context; gpgTimeout still bounds gpg.
err = gpgVerify(
signingKey, err := gpgVerify(
context.Background(),
[]byte(sigString),
m.pbOuter.GetSignature(),
@@ -101,6 +106,11 @@ func (m *manifest) verifyOuterIntegrity() error {
return fmt.Errorf("signature verification failed: %w", err)
}
if !strings.EqualFold(string(m.pbOuter.GetSigner()), signingKey) {
return fmt.Errorf("%w: signer %q, signing key %s",
errSignerNotSigningKey, m.pbOuter.GetSigner(), signingKey)
}
log.Infof("signature verified successfully")
return nil