Verify Component Versions
On this page
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
- OCM CLI installed
- Verification credentials configured with the public key
- A signed component version to verify in your current directory, here we use the
helloworldcomponent version from the getting started guide that you’ve signed in the How-To: Sign Component Versions guide.
Steps
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.0Remote OCI Registry:
ocm verify cv ghcr.io/<your-namespace>//github.com/acme.org/helloworld:1.0.0Expected 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
0on success.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
--signatureflag, every signature on the descriptor is verified, not just one nameddefault. Each is verified under its own name, so each needs a matching consumer entry.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.0Symptom: “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.pemVerify a GPG (OpenPGP) signature using the public key configured in .ocmconfig. Verification takes the handler from the same .ocmconfig, so put the type: GPGSigningConfiguration/v1alpha1 you configured for signing under the verifier field as well.
To run this you need the signer’s public key on disk and pointed at by publicKeyPGPFile in .ocmconfig. 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 GPG-signed component version is authentic and hasn’t been tampered with
Estimated time: ~2 minutes
Prerequisites
- OCM CLI installed
- Verification credentials configured with the public key
- A GPG-signed component version (see the Sign Component Versions how-to)
Steps
Point
.ocmconfigat the signer’s public keyIf you signed locally, the same
.ocmconfigyou wrote on the sign page already works β skip to the next step.If you’re verifying a signature someone else produced, follow How-To: Configure Signing Credentials β GPG using only the signer’s public key (
publicKeyPGPFile); theprivateKeyPGPFileentry is not needed for verification.Add a
verifierto the samesigning.config.ocm.software/v1alpha1entry you created as the signer in the sign how-to β GPG tab. RSA is the default when no verifier is configured, so this is what tellsocm verifyto use GPG:- type: signing.config.ocm.software/v1alpha1 signer: type: GPGSigningConfiguration/v1alpha1 verifier: type: GPGSigningConfiguration/v1alpha1 # addedIf you are only verifying, the
signerfield can be left out entirely:- type: signing.config.ocm.software/v1alpha1 verifier: type: GPGSigningConfiguration/v1alpha1Give the entry a
signaturefield to scope it to a single signature; without one it applies to every signature. Because the verifier is resolved per signature, a component carrying a GPG signature next to an RSA one can be verified in a single run, each with its own handler.signature: defaultapplies to a signature nameddefault; it is not a catch-all for an omitted--signatureflag. If the signature was created with--signature <name>, set the same value in the consumer identity.Consumer identities are matched exactly. Credentials are looked up under the name of the signature being verified, so a
signature: defaultentry does not serve a signature namedprod, and an entry with nosignaturefield at all matches nothing. Give every signature its own consumer entry, otherwise a run without--signaturefails on the signatures that have no matching credential.Verify the component version
Run the verify command. The GPG handler comes from the config entry you just added.
ocm verify cv \ /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0Expected output
time=2026-06-15T12:03:39.929+02:00 level=INFO msg="verifying signature" name=default time=2026-06-15T12:03:39.930+02:00 level=INFO msg="signature verification completed" name=default duration=894.458Β΅s time=2026-06-15T12:03:39.930+02:00 level=INFO msg="SIGNATURE VERIFICATION SUCCESSFUL"The command exits with status code
0on success.Verify a specific signature (optional)
If the component carries multiple signatures (e.g. a GPG signature alongside an RSA one), select the one to verify by name:
ocm verify cv \ --signature prod \ /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0Without
--signature, every signature on the descriptor is verified. Configuration and credentials are resolved separately for each one, under that signature’s own name.
Troubleshooting
Symptom: SIGNATURE VERIFICATION FAILED: gpg verify: openpgp: signature made by unknown entity
Cause: The public key in .ocmconfig doesn’t match the key that signed β most often because you exported a different key, or the signer rotated their key after signing.
Fix: Confirm publicKeyPGPFile points at the verifier-key file the signer actually shared. If you signed locally, re-run the export step from the sign how-to to regenerate verify-key.asc from the same fingerprint.
Symptom: SIGNATURE VERIFICATION FAILED: load GPG public key: load public key: open ...: no such file or directory
Cause: The publicKeyPGPFile path in .ocmconfig doesn’t exist on disk.
Fix: Check the path is correct and readable. Absolute paths avoid working-directory surprises.
Symptom: SIGNATURE VERIFICATION FAILED: no key matching fingerprint "..." found in keyring
Cause: The verifier contains a keyFingerprint that doesn’t match any key resolved from the public-key file.
Fix: Either remove keyFingerprint from the verifier (any key in the file will be tried) or correct it. Run gpg --show-keys /tmp/keys/verify-key.asc to confirm the actual fingerprint.
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:
| Aspect | RSA | Sigstore |
|---|---|---|
| Before you start | Obtain the signer’s public key, configure .ocmconfig | Nothing β declare expected identity in .ocmconfig |
| What proves trust | Signature decrypts with the public key you have | Signature ties back to an OIDC identity you’ve decided to trust |
| What the verifier needs | The 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
- OCM CLI installed
- A component version signed with Sigstore (see How-To: Sign Component Versions)
- The expected signer identity (their OIDC email and which provider they logged in with)
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
Declare which identity you trust
In Sigstore, the verifier’s only job is to decide whose signatures to accept. You do that with a
verifierentry in your.ocmconfig.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 ajane.doe@example.com)
Add the entry to the
configurationslist of your.ocmconfig:- type: signing.config.ocm.software/v1alpha1 verifier: type: SigstoreVerificationConfiguration/v1alpha1 certificateOIDCIssuer: https://github.com/login/oauth certificateIdentity: jane.doe@example.comThat’s the entire trust configuration. No public key to fetch, no certificate to install.
Pick the rightcertificateOIDCIssuervalueOn 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 via certificateOIDCIssuervalueGoogle https://accounts.google.comGitHub https://github.com/login/oauthMicrosoft https://login.microsoftonline.comBoth
certificateOIDCIssuerandcertificateIdentityare required. They’re what makes the signature meaningful β without them you’d be accepting any Sigstore signature from anyone.Read the identity from an existing signature
Already have a signed component version and want the exact identity to paste into your spec? Decode the Fulcio certificate. The OIDC issuer is recorded under the Sigstore-defined OID
1.3.6.1.4.1.57264.1.1; for interactive (browser) logins the identity is theemail:entry in the Subject Alternative Name. The cert 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/v1alpha1") | .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} /^[[:space:]]*X509v3 / {san=0} /Subject Alternative Name/ {san=1} san && /^ *email:/ {sub(/^[ \t]*email:/,""); print "certificateIdentity: " $0; next} flag=="issuer" {sub(/^[ \t]+/,""); print "certificateOIDCIssuer: " $0; flag=""} 'Output is two lines you can paste straight under the
verifierfield. (CI/workload signatures use aURI:SAN instead β see the Sigstore (CI) tab.)Verify the component version
Run the verify command. The Sigstore handler and the identity constraints both come from the config entry you just added.
ocm verify cv \ /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0ocm verify cv \ ghcr.io/<my-namespace>//github.com/acme.org/helloworld:1.0.0Expected 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"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 βdefaultif no--signatureflag was passed when signing. Omitting the flag here verifies every signature rather than picking one:ocm verify cv \ --signature default \ ghcr.io/<your namespace>//github.com/acme.org/helloworld:1.0.0The verifier’s
typefield decides which algorithm/handler verifies the signature. The--signatureflag picks which named signature on the component to verify (matched by name, not by algorithm).You can also scope a config entry itself to one signature by giving it a
signaturefield. The verifier is then resolved per signature, so a run without--signatureverifies every signature, each with the handler its own name resolves to.
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 config to match the identity actually recorded in the signature. Watch for trailing slashes and capitalization. To read the actual identity recorded in the signature, use the decode recipe in Step 1 (or see Tutorial: Sigstore (Keyless) β Inspect the signature).
Symptom: “keyless verification requires both an issuer constraint … and an identity constraint”
Cause: Your verifier config 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
- The OCM CLI available where you run verify β see How-To: Install the OCM CLI or use the pre-built OCM CLI container image
- A component version signed in CI (see the Sigstore (CI) tab on the sign how-to)
- Knowledge of which CI provider and which workflow signed it
Steps
Identify what to trust
Two values are still required:
certificateOIDCIssuer(which IdP minted the workload-identity token) andcertificateIdentity(the workflow URL the IdP stamps into the cert).For GitHub Actions the values are:
Field Value certificateOIDCIssuerhttps://token.actions.githubusercontent.comcertificateIdentityhttps://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β theissuerfield is what goes intocertificateOIDCIssuer.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 theURI: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/v1alpha1") | .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} /^[[:space:]]*X509v3 / {san=0} /Subject Alternative Name/ {san=1} san && /URI:/ {sub(/.*URI:[ \t]*/,""); print "certificateIdentity: " $0; next} flag=="issuer" {sub(/^[ \t]+/,""); print "certificateOIDCIssuer: " $0; flag=""} 'Output is two lines you can paste straight under the
verifierfield.Note:
ocm get cv -o yamlalso shows asignature.issuerfield on the OCM signature object β don’t rely on it for verification. The cert is the authoritative source.
Configure the verifier
Add the entry to the
configurationslist of your.ocmconfig:- type: signing.config.ocm.software/v1alpha1 verifier: type: SigstoreVerificationConfiguration/v1alpha1 certificateOIDCIssuer: https://token.actions.githubusercontent.com certificateIdentity: https://github.com/acme/helloworld/.github/workflows/release.yml@refs/heads/mainFor GitHub Actions you often want to accept a release workflow on any ref (any branch, any tag). Use
certificateIdentityRegexpinstead ofcertificateIdentity:- type: signing.config.ocm.software/v1alpha1 verifier: type: SigstoreVerificationConfiguration/v1alpha1 certificateOIDCIssuer: https://token.actions.githubusercontent.com certificateIdentityRegexp: ^https://github\.com/acme/helloworld/\.github/workflows/release\.yml@refs/.*$Exactly one of
certificateIdentityorcertificateIdentityRegexpis required (same applies to issuer’s*Regexpform).Verify the component version
ocm verify cv \ /tmp/helloworld/transport-archive//github.com/acme.org/helloworld:1.0.0ocm verify cv \ ghcr.io/<your-namespace>//github.com/acme.org/helloworld:1.0.0A 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
| Command | Description |
|---|---|
ocm verify component-version | Verify a component version signature |
ocm get component-version | View component with signatures |
Next Steps
- How-to: Sign Component Versions β Add signatures to your components (RSA or Sigstore)
- Tutorial: Signing and Verification β Learn how to sign and verify components in a complete tutorial
Related Documentation
- Concept: Signing and Verification β Understand how OCM signing works
- ADR 0017: Sigstore Integration β Sigstore design and OIDC flow details