Add GPG signature verification on manifest load

- Implement gpgVerify function that creates a temporary keyring to verify
  detached signatures against embedded public keys
- Signature verification happens during deserialization after hash
  validation but before decompression
- Extract signatureString() as a method on manifest for generating the
  canonical signature string (MAGIC-UUID-MULTIHASH)
- Add --require-signature flag to check command to mandate signature from
  a specific GPG key ID
- Expose IsSigned() and Signer() methods on Checker for signature status
This commit is contained in:
2025-12-18 05:28:35 -08:00
parent 213364bab5
commit 4a2060087d
8 changed files with 269 additions and 16 deletions

View File

@@ -3,6 +3,7 @@ package cli
import (
"fmt"
"path/filepath"
"strings"
"time"
"github.com/dustin/go-humanize"
@@ -68,6 +69,24 @@ func (mfa *CLIApp) checkManifestOperation(ctx *cli.Context) error {
return fmt.Errorf("failed to load manifest: %w", err)
}
// Check signature requirement
requiredSigner := ctx.String("require-signature")
if requiredSigner != "" {
if !chk.IsSigned() {
return fmt.Errorf("manifest is not signed, but signature from %s is required", requiredSigner)
}
signer := chk.Signer()
if signer == nil {
return fmt.Errorf("manifest signature has no signer fingerprint")
}
// Compare signer - the required key ID might be a suffix of the full fingerprint
signerStr := string(signer)
if !strings.EqualFold(signerStr, requiredSigner) && !strings.HasSuffix(strings.ToUpper(signerStr), strings.ToUpper(requiredSigner)) {
return fmt.Errorf("manifest signed by %s, but %s is required", signerStr, requiredSigner)
}
log.Infof("manifest signature verified (signer: %s)", signerStr)
}
log.Infof("manifest contains %d files, %s", chk.FileCount(), humanize.IBytes(uint64(chk.TotalBytes())))
// Set up results channel