Verify Multi-Signature Attestations
Target audience: Security auditors, Developers Goal: Validate the GPG multi-signature attestations on a StageX artifact.
Prerequisites
- Podman installed (or Docker — set
ENGINE=docker) - GnuPG (
gpgcommand) curl- Familiarity with PGP keys and SHA-256 digests
Quick: Verify a Single Signature
Import the maintainer keyring
curl -LO https://codeberg.org/stagex/signatures/raw/branch/main/stagex-keyring.pgp
gpg --import stagex-keyring.pgp
Reconstruct the signed JSON payload
Write the Container Signature Format payload to a file:
cat > payload.json << 'EOF'
{
"critical": {
"identity": {
"docker-reference": "docker.io/stagex/pallet-rust"
},
"image": {
"docker-manifest-digest": "sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668"
},
"type": "atomic container signature"
},
"optional": {}
}
EOF
Download and verify
curl -LO "https://codeberg.org/stagex/signatures/raw/branch/main/stagex/pallet-rust@sha256=2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668/signature-1"
gpg --verify signature-1 payload.json
Expected output:
gpg: Signature made ...
gpg: using RSA key C92FE5A3FBD58DD3EC5AA26BB10116B8193F2DBD
gpg: Good signature from "Danny Grove <danny@dannygrove.com>"
gpg: WARNING: This key is not certified with a trusted signature!
Good signature confirms the payload was signed by that key. The WARNING is normal — see Troubleshooting.
Quick: Verify ALL Signatures for a Package
Download all signature files
BASE="https://codeberg.org/stagex/signatures/raw/branch/main/stagex/pallet-rust@sha256=2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668"
for i in 1 2; do
curl -fLO "$BASE/signature-$i" || echo "No signature-$i"
done
Available signatures are listed in the signatures repo.
Verify and count
for sig in signature-*; do
echo "=== $sig ==="
gpg --verify "$sig" payload.json 2>&1
done
echo "Signature count: $(ls -1 signature-* | wc -l)"
StageX requires at least 2 valid signatures per package. Cross-reference each signing key's fingerprint against the MAINTAINERS file.
Automated: Podman policy.json Setup
Lookaside storage config
Create ~/.config/containers/registries.d/default.yaml:
docker:
docker.io/stagex:
lookaside: https://sigs.stagex.tools
quay.io/stagex:
lookaside: https://sigs.stagex.tools
Verification policy
Create ~/.config/containers/policy.json:
{
"default": [
{"type": "reject"}
],
"transports": {
"docker": {
"docker.io/stagex": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/path/to/stagex-keyring.pgp",
"signedIdentity": {
"type": "matchRepoDigestOrExact"
}
}
],
"quay.io/stagex": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/path/to/stagex-keyring.pgp",
"signedIdentity": {
"type": "remapIdentity",
"prefix": "quay.io/stagex",
"signedPrefix": "docker.io/stagex"
}
}
]
}
}
}
Replace /path/to/stagex-keyring.pgp with the absolute path.
Test
podman pull docker.io/stagex/pallet-rust@sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668
Force re-verification if cached:
podman rmi docker.io/stagex/pallet-rust@sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668
podman pull docker.io/stagex/pallet-rust@sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668
On failure Podman prints:
Error: Signature validation failed for docker.io/stagex/pallet-rust@sha256:...
Cross-Reference: Published Digests
Digest files contain <sha256> <stage>-<name> entries:
curl -s https://codeberg.org/stagex/stagex/raw/branch/main/digests/pallet.txt
Filter for a specific package:
curl -s https://codeberg.org/stagex/stagex/raw/branch/main/digests/pallet.txt | grep pallet-rust
Example output:
2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668 pallet-rust
Compare against your local build:
podman inspect <your-image> --format '{{.Digest}}'
Troubleshooting
| Issue | Explanation |
|---|---|
Good signature but WARNING: This key is not certified |
Normal — no web-of-trust path. Cross-check fingerprint against MAINTAINERS. |
gpg: Can't check signature: No public key |
Key not in local keyring. Fetch individually: gpg --auto-key-locate wkd --locate-keys <email> |
| Key expired or revoked | Check MAINTAINERS for updated fingerprints. Contact maintainer via Matrix (listed in MAINTAINERS). |
| 0–1 signatures found | Package not yet signed by enough maintainers. Check signatures repo. |