FIPS 140-3

This page describes how the OCM CLI and the OCM controller support FIPS 140-3. Since OCM 0.20.0, the binaries and container images are built in FIPS mode. OCM does not ship a separate FIPS variant: the regular binaries and images are built for FIPS, so the same artifact runs in both normal and FIPS-restricted environments.

Support Policy

What OCM provides. OCM binaries and images are built against a frozen version of the Go Cryptographic Module and run in FIPS 140-3 mode by default. OCM is not itself a cryptographic module and is not FIPS certified or validated; FIPS 140-3 validation applies to the cryptographic module it uses. See Validation Status for that module’s current CMVP status.

What you are responsible for. Whether a deployment meets your regulatory requirements depends on more than OCM: the node operating system and kernel, the external binaries OCM runs (cosign, and gpg when GPG credentials set keyringFingerprint, see GPG), your configuration, and your compliance regime. Check these with your compliance owner. OCM does not give compliance guarantees.

Default and opt-out. FIPS mode is on by default (fips140=on), so there is no separate FIPS build. Outside regulated environments you can turn it off with GODEBUG=fips140=off. GODEBUG=fips140=only additionally rejects external binaries that run outside the FIPS boundary. It is meant for testing and assessment, not for production, see Runtime Modes.

What changes in FIPS mode. Besides the cryptography itself running in the Go Cryptographic Module, FIPS mode changes OCM’s behavior in these places:

Areafips140=on (default)fips140=onlyDetails
TLS connectionsOnly FIPS-approved TLS versions, cipher suites and key exchangesSameEffects of FIPS Mode
Signing and verificationResource and component reference digests must use SHA-256 or SHA-512SameDigest Algorithms
Sigstore signingA cosign that is not a FIPS build is used and logged at debug levelRejected; cosign, on PATH or downloaded, must be a FIPS buildSigstore and cosign
GPG signingGPG keys other than unprotected v5 or v6 RSA or ECDSA P keys are used and logged at debug levelRejected; see the four strict-mode errorsGPG

Module version. OCM builds with GOFIPS140=certified, set once in the repository’s root .env. certified selects the newest Go Cryptographic Module version that has a CMVP validation certificate, as recorded by the Go toolchain the release is built with. When a newer module version is certified, OCM releases pick it up with the next Go toolchain update; no pin needs to change. The resolved version is part of every binary’s build information, see Verifying a Binary.

Out of scope. Code outside the Go Cryptographic Module, such as golang.org/x/crypto. See Known Limitations.

Reporting gaps. Report FIPS-related problems or gaps as GitHub issues. Open work on GPG and cosign is tracked in ocm-project#1327.

Cryptographic Module

OCM uses the Go Cryptographic Module, which is part of the Go standard library. The OCM CLI and the OCM controller are built against a frozen version of the module instead of the module version that ships with the current Go toolchain.

SettingValue
Build settingGOFIPS140=certified
Module version in the binaryv1.0.0 (build information: GOFIPS140=v1.0.0-c2097c7c)
Default runtime settingGODEBUG=fips140=on

Setting GOFIPS140 at build time does two things:

  • It compiles the binary against the frozen module source.
  • It sets the default GODEBUG value to fips140=on, so FIPS mode is active without any runtime configuration.

Validation Status

  • Go Cryptographic Module v1.0.0, used by current OCM releases, is validated: CMVP Certificate #5247. Its Security Policy lists the tested operating environments, and section 11.1 names the build information that marks a correctly configured binary: build GOFIPS140=v1.0.0-c2097c7c.
  • Go Cryptographic Module v1.26.0 is on the CMVP Modules In Process List (status as of 2026-10-02: Comment Resolution - CMVP). OCM switches to it automatically through GOFIPS140=certified once it is certified and a Go release marks it as such.

v1.0.0 was frozen from Go 1.24. Standard library features added to the module later, such as crypto/mldsa, are not available in OCM until the module version changes.

Artifacts

ArtifactBuildImage contents
ocm CLI binaries (all OS/architectures)GOFIPS140=certified, CGO_ENABLED=0—
OCM CLI image (cli:<version>, default)GOFIPS140=certified, CGO_ENABLED=0scratch with ocm, a FIPS build of cosign, and the CA bundle
OCM CLI slim image (cli:<version>-slim)GOFIPS140=certified, CGO_ENABLED=0scratch with ocm and the CA bundle
OCM controller imageGOFIPS140=certified, CGO_ENABLED=0scratch with manager, a FIPS build of cosign, and the CA bundle

The default CLI image is built FROM scratch. It contains the static /ocm binary (the entrypoint), cosign at /usr/local/bin/cosign from the prebuilt ghcr.io/open-component-model/cosign image (static, built with GOFIPS140=certified like OCM, see OCM’s FIPS cosign), and a CA bundle at /etc/ssl/certs/ca-certificates.crt. No GnuPG is included: OCM performs all GPG cryptography in-process with the Go OpenPGP library. Sigstore signing works in the image because cosign is on PATH. The slim image is built FROM scratch and contains only /ocm and the CA bundle; use it when you don’t sign with Sigstore, or bring your own cosign. The controller image is built FROM scratch with the static /manager binary (the entrypoint), the FIPS cosign, the CA bundle, and a world-writable /tmp. No GnuPG is included.

The images have no shell and no package manager. See Sigstore and cosign and GPG for how OCM uses cosign and handles GPG signing.

The binaries are statically linked and do not use any system cryptographic library. All cryptography in OCM itself goes through the Go Cryptographic Module, except for the GPG operations listed under GPG: in the default mode they run outside the module and are logged at debug level, and in strict mode they are rejected.

Runtime Modes

You control the runtime mode with the GODEBUG environment variable.

GODEBUGBehavior
fips140=on (default)FIPS mode is active. Approved algorithms run in their FIPS-compliant form, and non-approved algorithms such as MD5 and SHA-1 stay available.
fips140=onlyLike on, but non-approved algorithms return an error or panic. OCM also rejects GPG keys that are not unprotected v5 or v6 RSA or ECDSA P keys, a cosign that runs outside the FIPS boundary, Helm chart provenance verification (see Known Limitations), and resource or reference digests other than SHA-256/SHA-512 (see Digest Algorithms). Go documents this as a best-effort mode for testing and assessment, not for production.
fips140=offFIPS mode is disabled.

OCM runs in fips140=on mode by default, which allows non-approved algorithms instead of rejecting them. Parts of the dependency tree use non-approved algorithms for non-security purposes, such as content addressing and legacy digests. The Security Policy of the Go Cryptographic Module does not require fips140=only: the module enters and leaves its approved mode per service and reports the state through a service indicator (Security Policy sections 2.4 and 4.4), and Go documents only as “not intended to be used in production”.

With fips140=only, OCM rejects GPG keys outside the strict-mode set and refuses to run a non-FIPS cosign. Go’s own enforcement, however, applies to every Go program OCM starts, including programs OCM does not control. For example, the Docker Desktop credential helper (docker-credential-desktop) panics on an internal MD5 use, so OCM cannot resolve registry credentials. Use fips140=only where all such programs are known to work, or as a one-off audit to find non-approved algorithms in a workload’s call path.

Two OCM features use non-approved hashes by design and run them outside strict enforcement (crypto/fips140.WithoutEnforcement), so they keep working with fips140=only. FIPS mode itself stays on, so TLS and SSH still negotiate approved algorithms only:

  • Git identifies objects by SHA-1. Cloning, fetching and archiving a repository runs outside strict enforcement; the archive OCM records is digested with SHA-256.
  • wget checksum verification accepts MD5 and SHA-1 checksums that a server publishes. Only these hashes run outside strict enforcement; the digest OCM records and signs is always SHA-256.

Test packages named fips140 (bindings/go/cli/cmd/fips140, bindings/go/gpg/fips140, bindings/go/git/fips140, bindings/go/helm/fips140, bindings/go/wget/fips140) set //go:debug fips140=only and run in every unit test run, so CI exercises signing, verification, Git downloads, the Helm provenance rejection and legacy checksum verification in strict mode. All other tests run in the default fips140=on mode.

Effects of FIPS Mode

In FIPS mode, the Go Cryptographic Module:

  • Runs an integrity self-check and known-answer self-tests at startup or on first use of an algorithm.
  • Runs pairwise consistency tests on generated keys, which can make key generation up to 2x slower.
  • Implements crypto/rand with a NIST SP 800-90A DRBG.
  • Restricts crypto/tls to FIPS-approved protocol versions, cipher suites, signature algorithms, and key exchanges: TLS 1.2 and 1.3 only, AES-GCM (and ECDHE AES-CBC-SHA256 on TLS 1.2) cipher suites, no plain X25519 key exchange, and RSA certificates of at least 2048 bits. A registry or HTTP endpoint that offers only non-approved options fails the TLS handshake.
  • Draws entropy for its DRBG from the operating system. Module v1.0.0 uses the kernel (getrandom) as a passive entropy source outside the module boundary, so the quality of OCM’s random numbers rests on the operating system. Run OCM on an operating system with a FIPS-compliant entropy source, for example a distribution with FIPS-validated kernel crypto. Module v1.26.0 adds a CPU jitter entropy source with ESV certificate #E318; OCM moves to it once that module is certified.

FIPS mode does not make TLS compliant with CNSA 1.0 or CNSA 2.0, which National Security Systems and DoD IL5 require: CNSA asks for AES-256, P-384 or ML-KEM-1024, and SHA-384, while Go also offers AES-128 and P-256, and does not let applications restrict TLS 1.3 cipher suites. OCM does not configure CNSA TLS. Deployments that need it have to build OCM with a toolchain that enforces it, such as the go-fips toolchain from Chainguard.

To set the mode explicitly for the CLI:

GODEBUG=fips140=on ocm version

For the controller, set the variable in the Deployment:

env:
  - name: GODEBUG
    value: fips140=on

Verifying a Binary

The OCM CLI prints the Go build settings embedded in it, including the FIPS ones:

ocm version -o gobuildinfo | grep -E 'GOFIPS140|DefaultGODEBUG'

Expected output:

build DefaultGODEBUG=fips140=on
build GOFIPS140=v1.0.0-c2097c7c

-o gobuildinfojson prints the same information as JSON. For the CLI image, run docker run --rm ghcr.io/open-component-model/cli:<version> version -o gobuildinfo.

For any Go binary, including the controller’s /manager, go version -m shows the same settings (plain go version prints only the Go version):

go version -m ./manager | grep -E 'GOFIPS140|DefaultGODEBUG'

For a container image, copy the binary out first.

Startup Log

Both binaries log the FIPS state at startup, as reported by crypto/fips140.Enabled() and crypto/fips140.Version().

The controller logs it at info level on every start:

INFO setup FIPS 140-3 mode {"enabled": true, "module": "v1.0.0"}

The CLI logs it at debug level for every command, so it does not add noise to regular output. Pass --loglevel debug to see it:

ocm --loglevel debug get cv <reference>
level=DEBUG msg="FIPS 140-3 mode" enabled=true module=v1.0.0

enabled=false means the binary was built without GOFIPS140 or was started with GODEBUG=fips140=off.

Digest Algorithms

A component signature covers the normalized component descriptor, which is hashed with SHA-256 or SHA-512. Resources and component references are covered only through the digests recorded in the descriptor, so those digests are security-relevant: with a weak hash such as MD5 or SHA-1, content could be swapped under a valid signature.

With GODEBUG=fips140=only, OCM therefore requires every resource and component reference digest to use SHA-256 or SHA-512. In the default fips140=on mode and outside FIPS mode, it logs a warning instead, so component versions with existing MD5 or SHA-1 digests keep working:

Operationfips140=onlyfips140=on (default) and off
ocm sign cvFails with refusing to sign component version with GODEBUG=fips140=only: unsupported digest hash algorithmSigns, logs a warning
ocm verify cvFails with refusing to verify component version with GODEBUG=fips140=only: unsupported digest hash algorithmVerifies, logs a warning
Controller signature verificationFails the resolutionVerifies, logs the weak digest

Resources excluded from the signature (NO-DIGEST / EXCLUDE-FROM-SIGNATURE) are exempt. Digests that OCM computes itself, for example when adding a component version from a constructor, already use SHA-256. Downloading a resource through a digest-pinned component reference verifies the content against its resource digest, which supports only SHA-256 and SHA-512 in any mode; an unpinned download does not check its content against the resource digest.

MD5 and SHA-1 remain usable for purposes that are not security-relevant, such as Git object IDs or caching.

Known Limitations

FIPS mode only covers cryptography that runs through the Go Cryptographic Module. The following signing mechanism runs its cryptography in an external binary:

FeatureWhere the cryptography runs
Sigstore/cosign signing and verificationOCM runs the cosign binary from PATH, or downloads OCM’s FIPS build of cosign. OCM checks whether cosign is a FIPS build, see Sigstore and cosign.

GPG signing and verification run in-process with the Go OpenPGP library (github.com/ProtonMail/go-crypto), whose RSA, NIST ECDSA, SHA-2 and RNG run through the Go Cryptographic Module. Algorithms and operations outside the module (EdDSA/Ed25519/Ed448 via circl, v4 key fingerprints via SHA-1, passphrase-protected key unlocking via CFB, secret-key export from the GnuPG keyring) run with a debug log by default and are rejected in strict mode. See GPG.

In a FIPS-restricted environment, use RSA signing, Sigstore with a FIPS build of cosign, or GPG with v5 or v6 RSA or ECDSA P keys, see GPG.

Some dependencies bring cryptography that does not run through the Go Cryptographic Module. github.com/ProtonMail/go-crypto (OpenPGP) is also imported by go-git (commit and tag signature verification) and Helm (chart provenance). OCM does not call go-git’s verification. It does call Helm’s provenance verification when downloading a chart from a Helm repository with Helm credentials that include a keyring:

ModeHelm chart download with a keyring
fips140=on (default)Provenance verified with OpenPGP outside the module; logged at debug level
fips140=onlyRejected: Helm chart provenance verification is not available; remove the keyring to download without verification
fips140=offProvenance verified

Without a keyring, OCM only passes Helm provenance files through. In OCM’s own code, golangci-lint (depguard) rejects imports of non-approved algorithms (DES, RC4, DSA, secp256k1, golang.org/x/crypto outside reviewed exceptions) and limits MD5 and SHA-1 to wget checksum verification. The GPG signing handler (bindings/go/gpg/signing/handler) is exempted because it enforces the FIPS 140-3 mode rules itself.

Sigstore and cosign

OCM does not implement Sigstore itself. Its Sigstore signing handler runs the external cosign binary, so all Sigstore cryptography runs in cosign. The CLI and controller images include a FIPS build of cosign; the OCM CLI binaries download it on first use.

How OCM uses cosign

StepWhat OCM does
LocateUses cosign from PATH (v3.0.4 or later). If none is found, OCM downloads the OCM’s FIPS cosign release pinned in bindings/go/sigstore/signing/handler/internal/.env from GitHub, also in fips140=only. Before it trusts the release’s cosign_checksums.txt, OCM checks the file’s SHA-256 against COSIGN_CHECKSUMS_SHA256, pinned in the same .env, then verifies the binary against it and caches it under the user cache directory (~/.cache/ocm/cosign/... on Linux).
FIPS checkIn FIPS mode, reads the Go build information of cosign, the same data that go version -m shows, and looks for GOFIPS140=v<version>. See the table below.
Signocm sign cv runs cosign sign-blob <digest file> --bundle <file> --yes for keyless signing. The OIDC token is passed in SIGSTORE_ID_TOKEN (or GitHub Actions OIDC), never on the command line. OCM stores the resulting Sigstore bundle as the signature.
Verifyocm verify cv and the controller write the bundle to a file and run cosign verify-blob <digest file> --bundle <file> with the configured --certificate-identity[-regexp], --certificate-oidc-issuer[-regexp], and optionally --trusted-root or --insecure-ignore-tlog.

cosign inherits OCM’s environment, including GODEBUG, so a FIPS build of cosign runs in the same FIPS mode as OCM.

In this keyless flow, cosign only uses the Go standard library for cryptography: an ephemeral ECDSA P-256 key, SHA-256, X.509 certificate chains, and TLS to Fulcio, Rekor, and the TUF repository. In a GOFIPS140 build, all of it runs in the Go Cryptographic Module. cosign does not need cgo or a system crypto library: only hardware-token support (pkcs11key, pivkey build tags) uses cgo, and the default build leaves it out.

The following cosign features use golang.org/x/crypto, which is outside the Go Cryptographic Module. OCM’s keyless flow uses none of them:

  • Encrypted private key files (cosign generate-key-pair): scrypt and NaCl secretbox.
  • Rekor entries signed with PGP keys: x/crypto/openpgp.
  • SSH-format keys: x/crypto/ssh.
  • Cloud KMS providers: the signing runs in the KMS; the Azure and GCP SDKs use x/crypto for credentials and transport options.

FIPS Check

The OCM cosign build is a FIPS build. Other cosign builds, such as the upstream sigstore/cosign releases, usually are not.

Modecosign that is not a FIPS build
fips140=on (default)Used; logged at debug level
fips140=onlyRejected: Sigstore signing and verification require a cosign built against a frozen Go Cryptographic Module
fips140=offUsed

The check applies to the cosign on PATH and to the downloaded one alike.

OCM’s FIPS cosign

OCM publishes cosign built unmodified from upstream sources against the Go Cryptographic Module, with cgo disabled, so the binary is statically linked and runs on any Linux distribution. It is available as release binaries for the same platforms as upstream and as the image ghcr.io/open-component-model/cosign, whose binary is /cosign. The CLI and controller images copy it from there. The version OCM downloads is pinned in bindings/go/sigstore/signing/handler/internal/.env, together with the SHA-256 of that release’s cosign_checksums.txt; the images use the same release.

The fork follows upstream automatically: it mirrors every upstream stable release hourly, applies dependency and Go toolchain updates daily, and publishes a new vX.Y.Z+ocm.N release when a maintenance branch changed. Releases carry GitHub SLSA build provenance, which you can check with gh attestation verify <binary> --repo open-component-model/cosign.

Like OCM, the fork builds with GOFIPS140=certified, but with its own pinned Go toolchain. Both resolve certified to the newest certified module version their toolchain knows, so after a new module version is certified, ocm and cosign can run different module versions until both toolchains are updated. Both are frozen, certified modules, which is what the FIPS check requires.

Check the build information. The output must contain GOFIPS140=v<version>, CGO_ENABLED=0, and fips140=on in DefaultGODEBUG:

$ go version -m cosign | grep -E 'GOFIPS140|CGO_ENABLED|DefaultGODEBUG'
        build   DefaultGODEBUG=fips140=on
        build   CGO_ENABLED=0
        build   GOFIPS140=v1.0.0-c2097c7c
  • Local ocm binary: put that cosign on PATH before running ocm, or let OCM download it.

  • Slim CLI image: mount the cosign for the image’s platform at /usr/local/bin/cosign, which is on the default PATH:

    docker run --rm \
      -v "$PWD/cosign:/usr/local/bin/cosign:ro" \
      -v "$PWD/.ocmconfig:/.ocmconfig:ro" \
      ghcr.io/open-component-model/cli:latest-slim \
      verify cv --config /.ocmconfig ghcr.io/<namespace>//<component>:<version>

Commercial cosign Images

Commercial FIPS images of cosign are also available, such as Chainguard’s cosign-fips and the FIPS variant of the Docker Hardened Image dhi.io/cosign. These images use the OpenSSL FIPS provider instead of the Go Cryptographic Module, so their cosign only works inside the image. Copy the static ocm binary into such an image instead of copying cosign out:

FROM <registry>/cosign-fips:<tag>
COPY --from=ghcr.io/open-component-model/cli:<version> /ocm /usr/local/bin/ocm
ENTRYPOINT ["/usr/local/bin/ocm"]

OCM’s check only recognizes GOFIPS140 builds, so with fips140=only it rejects these images’ cosign; with the default fips140=on it uses them.

GPG

OCM signs and verifies GPG signatures in-process with the Go OpenPGP library (github.com/ProtonMail/go-crypto). No external gpg binary is needed for signing or verification. The gpg binary is used only when keyringFingerprint is set in the GPG credentials, to export key material from the user’s GnuPG keyring (gpg --export, gpg --export-secret-keys).

OCM reads v4 keys, LibrePGP v5 keys (GnuPG, RNP) and RFC 9580 v6 keys, including v6 post-quantum signing keys (ML-DSA+EdDSA, SLH-DSA). Encryption-only subkeys are ignored, so GnuPG’s PQC keys work: their primary key signs, and their Kyber encryption subkey is skipped. GnuPG’s Ed448 keys are not supported, because the Go OpenPGP library cannot read their key encoding. Keys on hardware tokens are not supported either: GnuPG exports only a stub of such a secret key.

The following table shows what happens in each mode for GPG operations that fall outside the Go Cryptographic Module:

Operationfips140=on (default)fips140=onlyfips140=off
v4 keys (SHA-1 fingerprints)Used; logged at debug levelwith GODEBUG=fips140=only, GPG signing and verification require OpenPGP v6 (RFC 9580) or v5 (LibrePGP) keys: v4 and older keys are identified by SHA-1 fingerprintsUsed
EdDSA, Ed25519, Ed448, ML-DSA, SLH-DSA, DSA, non-P curves, RSA below 2048 bitsUsed; logged at debug levelwith GODEBUG=fips140=only, GPG signing and verification require an RSA (at least 2048 bits) or ECDSA (P-256, P-384, P-521) keyUsed
Passphrase-protected keysUsed; logged at debug levelwith GODEBUG=fips140=only, passphrase-protected GPG keys cannot be unlocked because OpenPGP key protection is not FIPS-approved; provide the private key without passphrase protection, for example from a secret storeUsed
Exporting a secret key from the GnuPG keyring (keyringFingerprint)Used; logged at debug levelwith GODEBUG=fips140=only, secret keys cannot be exported from the GnuPG keyring because gpg-agent processes them outside the Go Cryptographic Module; provide the private key in the GPG credentialsUsed

To use GPG in strict mode (fips140=only), provide:

  • An OpenPGP v6 key (RFC 9580) or v5 key (LibrePGP); both use SHA-256 fingerprints. GnuPG creates v4 keys for RSA and ECDSA (v5 only for Ed448 and PQC, which strict mode rejects for their algorithm), and it can neither create nor verify v6 keys. Use Sequoia (sq) to generate a v6 key:

    sq key generate --profile rfc9580 --signing-algorithm rsa3k \
      --cannot-encrypt --cannot-authenticate --without-password \
      --own-key --name "OCM Signing" --email "ocm@example.com" \
      --output signing-key.pgp --rev-cert signing-key.rev
    
    # Export the secret key (ASCII-armored):
    sq key export --cert "OCM Signing" > signing-key.asc
    
    # Export the public certificate (ASCII-armored):
    sq cert export --cert-email "ocm@example.com" > verify-key.asc
  • An RSA (at least 2048 bits) or ECDSA (P-256, P-384, P-521) key. This applies to the primary key and every signing subkey, not only the key that signs, because OCM verifies their self-signatures when it reads the key.

  • An unprotected secret key (no passphrase). Use a secret store instead.

  • Key material in the GPG credentials (privateKeyPGPFile/publicKeyPGPFile or inline privateKeyPGP/publicKeyPGP), not keyringFingerprint.

Building from Source

To build FIPS binaries yourself, use the same settings as the release build (build:target in bindings/go/cli/Taskfile.yml). Run this from bindings/go:

CGO_ENABLED=0 GOTOOLCHAIN=local GOFIPS140=certified go build \
  -ldflags "-s -w -X ocm.software/open-component-model/bindings/go/cli/cmd/version.BuildVersion=<version>" \
  -o ocm ./cli

GOTOOLCHAIN=local makes the build fail if the installed Go does not match the toolchain in go.mod, instead of downloading another toolchain.

-s -w strips the symbol table and DWARF debug information, as the release binaries do. The GOFIPS140 build information that go version -m reads is kept.

The module setting lives once, as GOFIPS140 in the repository’s root .env. task bindings/go/cli:build, the controller image build (task docker-build/multi-arch), and the bindings/go test tasks all read it from there. The controller Dockerfile refuses to build without the GOFIPS140 build argument.