Verify Component Versions

Goal

Validate a component version signature to ensure it is authentic and has not been tampered with. OCM supports multiple verification algorithms. Pick the tab that matches the algorithm the signature was made with β€” each tab is a self-contained walkthrough.

Verify an RSA signature using the matching public key configured in .ocmconfig.

To run this you need the signer’s public key on disk and pointed at by .ocmconfig (see prerequisites). With Sigstore (other tabs) you don’t install a public key at all β€” you just declare which identity you trust.

You’ll end up with

  • Confidence that a component version is authentic and hasn’t been tampered with

Estimated time: ~3 minutes

Prerequisites

Steps

  1. Verify the component version

    Run the verify command against your signed component:

    ocm verify cv <repository>//<component>:<version>

    Local CTF Archive:

    ocm verify cv /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0

    Remote OCI Registry:

    ocm verify cv ghcr.io/<your-namespace>//github.com/acme.org/helloworld:1.0.0
    Expected output
    time=2025-11-19T15:58:22.431+01:00 level=INFO msg="verifying signature" name=default
    time=2025-11-19T15:58:22.435+01:00 level=INFO msg="signature verification completed" name=default duration=4.287541ms
    time=2025-11-19T15:58:22.435+01:00 level=INFO msg="SIGNATURE VERIFICATION SUCCESSFUL"

    The command exits with status code 0 on success.

  2. Verify a specific signature (optional)

    If the component has multiple signatures, specify which one to verify:

    ocm verify cv --signature prod ghcr.io/<your-namespace>//github.com/acme.org/helloworld:1.0.0

    πŸ‘‰ Without the --signature flag, OCM uses the configuration named default.

  3. List available signatures (optional)

    View all signatures in a component version:

    ocm get cv ./tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0 -o yaml | grep -A 10 signatures:

Troubleshooting (RSA)

Symptom: “signature verification failed”

Cause: Public key doesn’t match the signing private key, or the component was modified after signing.

Fix: Ensure you’re using the correct public key that corresponds to the private key used for signing:

# Check which signature names exist
ocm get cv /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0 -o yaml | grep -A 3 "signatures:"

# Verify with the correct signature name
ocm verify cv --signature <name> /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0

Symptom: “no public key found”

Cause: OCM cannot find a matching verification configuration in .ocmconfig.

Fix: Ensure your .ocmconfig has a consumer entry with the matching signature name and public_key_pem_file path.

See Configure Signing Credentials.

Symptom: “invalid key format”

Cause: The public key file is not in PEM format.

Fix: Verify the key starts with -----BEGIN PUBLIC KEY-----:

head -n 1 /tmp/keys/public-key.pem

Verify a Sigstore keyless signature made by a person who logged in via a browser. There’s no public key to install on this side either β€” you tell OCM which identity you trust, and it checks the signature was made by that identity.

If you’ve done classical key-based verification, here’s what changes:

AspectRSASigstore
Before you startObtain the signer’s public key, configure .ocmconfigNothing β€” declare expected identity in a verifier spec file
What proves trustSignature decrypts with the public key you haveSignature ties back to an OIDC identity you’ve decided to trust
What the verifier needsThe signer’s public key (rotated and re-distributed)The expected OIDC identity and issuer β€” no long-lived key

For the conceptual picture (how Fulcio, Rekor, and OIDC fit together), see Identity-Based Trust (Sigstore).

You’ll end up with

  • Confidence that a component version was signed by an identity you trust

Estimated time: ~3 minutes

Prerequisites

For the end-to-end walkthrough including Fulcio certificate validation, Rekor inclusion proofs, and TUF trust roots, see Tutorial: Sigstore (Keyless). Design background lives in ADR 0017: Sigstore Integration.

Steps

  1. Declare which identity you trust

    In Sigstore, the verifier’s only job is to decide whose signatures to accept. You do that with a small spec file.

    Two values matter:

    • certificateIdentity β€” the email or workload identity of whoever signed (e.g. jane.doe@example.com)
    • certificateOIDCIssuer β€” which OIDC provider they logged in with (e.g. GitHub vs. Google β€” both could have a jane.doe@example.com)

    Create sigstore-verify.yaml:

    # sigstore-verify.yaml
    type: SigstoreVerificationConfiguration/v1alpha1
    certificateOIDCIssuer: https://github.com/login/oauth
    certificateIdentity: jane.doe@example.com

    That’s the entire trust configuration. No public key to fetch, no certificate to install.

    Pick the right certificateOIDCIssuer value

    On public Sigstore (oauth2.sigstore.dev), the issuer recorded in the signing certificate is the upstream identity provider’s issuer URL β€” not the Sigstore Dex endpoint. Pick the one matching the provider the signer logged in with:

    Signer logged in viacertificateOIDCIssuer value
    Googlehttps://accounts.google.com
    GitHubhttps://github.com/login/oauth
    Microsofthttps://login.microsoftonline.com

    Both certificateOIDCIssuer and certificateIdentity are required. They’re what makes the signature meaningful β€” without them you’d be accepting any Sigstore signature from anyone.

  2. Verify the component version

    Run the verify command with the verifier spec:

    ocm verify cv \
      --verifier-spec ./sigstore-verify.yaml \
      /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0
    ocm verify cv \
      --verifier-spec ./sigstore-verify.yaml \
      ghcr.io/<my-namespace>//github.com/acme.org/helloworld:1.0.0
    Expected output
    time=2026-05-19T18:49:13.316+02:00 level=INFO msg="verifying signature" name=default
    time=2026-05-19T18:49:13.856+02:00 level=INFO msg="signature verification completed" name=default duration=539.512209ms
    time=2026-05-19T18:49:13.856+02:00 level=INFO msg="SIGNATURE VERIFICATION SUCCESSFUL"
  3. Verify a specific signature

    If the component carries multiple signatures (e.g. an RSA signature and a Sigstore signature), select one by name with --signature. The name is whatever was set at sign time β€” default if no --signature flag was passed:

    ocm verify cv \
      --verifier-spec ./sigstore-verify.yaml \
      --signature default \
      ghcr.io/<your namespace>//github.com/acme.org/helloworld:1.0.0

    The verifier spec’s type field decides which algorithm/handler verifies the signature. The --signature flag picks which named signature on the component to verify (matched by name, not by algorithm).

Troubleshooting

Symptom: “no matching CertificateIdentity found”

Cause: The signature was made by a different identity than what your spec expects β€” or the same identity but via a different OIDC provider.

The full error names which side did not match. For an identity mismatch:

Error: SIGNATURE VERIFICATION FAILED: cosign verify-blob failed: exit status 1
stderr: Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "nobody@nowhere.invalid", got "john.doe@gmail.com"

For an issuer mismatch:

Error: SIGNATURE VERIFICATION FAILED: cosign verify-blob failed: exit status 1
stderr: Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://accounts.google.com", got "https://github.com/login/oauth"

Fix: Update certificateIdentity / certificateOIDCIssuer in your verifier spec to match the identity actually recorded in the signature. Watch for trailing slashes and capitalization. To read the actual identity stored in the signature, see Tutorial: Sigstore (Keyless) β€” Inspect the signature.

Symptom: “keyless verification requires both an issuer constraint … and an identity constraint”

Cause: Your verifier spec is missing certificateIdentity, certificateOIDCIssuer, or both.

The full error:

Error: SIGNATURE VERIFICATION FAILED: invalid verification config: keyless verification requires both an issuer constraint (CertificateOIDCIssuer or CertificateOIDCIssuerRegexp) and an identity constraint (CertificateIdentity or CertificateIdentityRegexp)

Fix: Both are mandatory β€” they’re how Sigstore knows whose signatures to accept. See Step 1 above.

Verify a Sigstore signature that was created in a CI pipeline. Verification works exactly like the interactive case β€” only the identity you trust is different. Instead of a person’s email and a consumer OIDC issuer (Google, GitHub, Microsoft), you trust a CI workflow’s identity issued by a CI-provider OIDC.

You’ll end up with

  • Confidence that a component version was signed by a specific CI workflow you trust

Estimated time: ~3 minutes

Prerequisites

Steps

  1. Identify what to trust

    Two values are still required: certificateOIDCIssuer (which IdP minted the workload-identity token) and certificateIdentity (the workflow URL the IdP stamps into the cert).

    For GitHub Actions the values are:

    FieldValue
    certificateOIDCIssuerhttps://token.actions.githubusercontent.com
    certificateIdentityhttps://github.com/<org>/<repo>/.github/workflows/<file>@refs/heads/<branch>
    Other CI providers (GitLab, Buildkite, self-hosted, …)

    The mechanism is the same; only the issuer URL and identity shape are provider-specific. Two ways to find them:

    • Read your provider’s OIDC discovery document at https://<provider>/.well-known/openid-configuration β€” the issuer field is what goes into certificateOIDCIssuer.

    • Decode an existing signature to read the values directly. The OIDC issuer is recorded in the Fulcio cert under the Sigstore-defined OID 1.3.6.1.4.1.57264.1.1 (the same OID is used by every Fulcio deployment β€” public Sigstore, scaffolding stacks, enterprise installs); the SAN identity is in the URI: line. Yes, this recipe is ugly β€” there’s no clean field for it on the OCM side, and the cert itself is buried two base64 layers deep:

      REF=ctf::./ctf//github.com/acme.org/helloworld:1.0.0
      
      ocm get cv "$REF" -o yaml \
        | yq '.[].signatures[]? | select(.signature.algorithm == "sigstore") | .signature.value' \
        | head -1 | base64 -d | jq -r '.verificationMaterial.certificate.rawBytes' \
        | base64 -d | openssl x509 -inform DER -noout -text \
        | awk '
          /1.3.6.1.4.1.57264.1.1:/ {flag="issuer"; next}
          /URI:/ {sub(/.*URI:[ \t]*/,""); print "certificateIdentity:    " $0; next}
          flag=="issuer" {sub(/^[ \t]+/,""); print "certificateOIDCIssuer: " $0; flag=""}
        '

      Output is two lines you can paste straight into your verifier spec.

      Note: ocm get cv -o yaml also shows a signature.issuer field on the OCM signature object β€” don’t rely on it for verification. The cert is the authoritative source.

  2. Create the verifier spec

    Create sigstore-verify-ci.yaml:

    # sigstore-verify-ci.yaml
    type: SigstoreVerificationConfiguration/v1alpha1
    certificateOIDCIssuer: https://token.actions.githubusercontent.com
    certificateIdentity: https://github.com/acme/helloworld/.github/workflows/release.yml@refs/heads/main

    For GitHub Actions you often want to accept a release workflow on any ref (any branch, any tag). Use certificateIdentityRegexp instead of certificateIdentity:

    # sigstore-verify-ci-regex.yaml
    type: SigstoreVerificationConfiguration/v1alpha1
    certificateOIDCIssuer: https://token.actions.githubusercontent.com
    certificateIdentityRegexp: ^https://github\.com/acme/helloworld/\.github/workflows/release\.yml@refs/.*$

    Exactly one of certificateIdentity or certificateIdentityRegexp is required (same applies to issuer’s *Regexp form).

  3. Verify the component version

    ocm verify cv \
      --verifier-spec ./sigstore-verify-ci.yaml \
      /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0
    ocm verify cv \
      --verifier-spec ./sigstore-verify-ci.yaml \
      ghcr.io/<your-namespace>//github.com/acme.org/helloworld:1.0.0

    A successful run prints SIGNATURE VERIFICATION SUCCESSFUL. The same --signature <name> flag from the interactive tab applies if the component carries multiple signatures.

Troubleshooting

Symptom: identity mismatch with a long workflow URL

Cause: Subtle differences in the workflow path: ref type (refs/heads/ vs refs/tags/), branch name, file casing, or trailing slashes.

Fix: Decode the actual cert (recipe in Step 1) and copy the URI: line verbatim into certificateIdentity. For multi-ref signing, switch to certificateIdentityRegexp.

Issuer mismatch with a non-GitHub CI

Cause: Other CI providers β€” GitLab, Buildkite, self-hosted runners β€” issue tokens from their own URL, not https://token.actions.githubusercontent.com.

Fix: Set certificateOIDCIssuer to your provider’s issuer URL (find it under https://<provider>/.well-known/openid-configuration β†’ issuer). The certificateIdentity shape is provider-specific too β€” decode an existing cert (recipe in Step 1) to read the actual SAN.

CLI Reference

CommandDescription
ocm verify component-versionVerify a component version signature
ocm get component-versionView component with signatures

Next Steps