Skip to content

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 (gpg command)
  • 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.

See Also