fips

package
v2.35.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 1 Imported by: 0

README

fips

Package fips reports whether the running binary performs cryptographic operations through a FIPS 140-3 validated module, and which module that is.

Detection is gated on the fips build tag. Without it the package compiles to constants — ActiveBackend() is BackendNone and Enabled() is false — so ordinary builds carry no FIPS-related code and no extra dependencies. The package imports only the standard library.

Quick start

// Log the FIPS posture once during start-up.
fips.LogStatus(a.Logger())

// Branch on it where behaviour has to differ.
if fips.Enabled() {
    // ...
}

Backends

Built with -tags fips, two backends are recognised:

Backend Probe Active when
BackendBoringCrypto crypto/boring Built with the golang-fips toolchain (GOEXPERIMENT=boringcrypto, CGO_ENABLED=1, amd64 Linux), a loadable system OpenSSL, and the backend switched on by either a FIPS kernel or GOLANG_FIPS=1 — see below
BackendNativeGo crypto/fips140 Built with GOFIPS140 set to a module version (e.g. v1.0.0), or run with GODEBUG=fips140=on — see Go FIPS 140-3 compliance
BackendNone Neither of the above

crypto/boring only has buildable files when the boringcrypto build tag is set, so that probe is isolated behind it (boring.go / notboring.go). This keeps -tags fips usable on a toolchain that ships the native Go Cryptographic Module instead of the golang-fips fork; importing crypto/boring unconditionally fails to compile there.

BackendBoringCrypto takes precedence: when the golang-fips toolchain routes the crypto packages through OpenSSL, OpenSSL is the validated module doing the work regardless of what crypto/fips140 reports.

Enabled() is derived from ActiveBackend() rather than probing a second time, so the two cannot disagree.

Logging

LogStatus records the posture at info level under the fips_backend field. It distinguishes four states, including "compiled with FIPS support but no backend activated" — a misconfigured FIPS deployment, which is very different from a build that never had FIPS support:

{"level":"INFO","msg":"FIPS mode is enabled, using the native Go Cryptographic Module","fips_backend":"native-go"}

A nil logger falls back to slog.Default(), so LogStatus is safe to call before a logger is wired up.

If you log the backend yourself, use the same fips_backend key so the field stays queryable across services:

logger.Info("crypto backend", "fips_backend", fips.ActiveBackend())

SSH algorithms

FIPS-compliant SSH algorithm sets live in the fips/sshalgo subpackage, which depends on golang.org/x/crypto. It is separate so that services which only need to report their posture do not take on that dependency.

import (
    "gitlab.com/gitlab-org/labkit/v2/fips/sshalgo"
    "golang.org/x/crypto/ssh"
)

config := &ssh.ServerConfig{}

algorithms := sshalgo.DefaultAlgorithms()
config.Ciphers = algorithms.Ciphers
config.MACs = algorithms.MACs
config.KeyExchanges = algorithms.KeyExchanges
config.PublicKeyAuthAlgorithms = algorithms.PublicKeyAuths

signer, err := sshalgo.HostKeySigner(hostKey)
if err != nil {
    return err
}
config.AddHostKey(signer)

ssh.NewServerConn calls SetDefaults() itself, so there is no need to call it here — and doing so before assigning these fields would have no effect anyway.

Assigning PublicKeyAuthAlgorithms and wrapping each host key are required, not optional. ssh.ServerConfig has no field that can express either policy on its own:

  • SetDefaults() never populates PublicKeyAuthAlgorithms. Leave it empty and NewServerConn fills it from a default set containing ssh-rsa and ssh-dss.
  • A server ignores Config.HostKeyAlgorithms entirely, deriving what it advertises from the keys it was given. An RSA key yields rsa-sha2-256, rsa-sha2-512 and ssh-rsa.

Skip either and a FIPS server offers SHA-1 signatures.

Without the tag both functions return golang.org/x/crypto/ssh's own sets unchanged and HostKeySigner returns its argument, so callers can wire all of this in unconditionally and let the build tag decide the policy.

Policy

Algorithms fall into three tiers, because there are three different reasons to withhold one. COMPLIANCE.md maps every algorithm to the governing NIST document, and records which primitives run inside a validated boundary on each backend.

Tier Offered Contents
1 Never ChaCha20-Poly1305; X25519 and its alias; security-key public key variants
2 Native module only ML-KEM768/X25519
3 By default, removed by WithoutDeprecated() HMAC-SHA1, HMAC-SHA1-96, DH-group14-SHA1, and finite-field DH
// Hardened: no SHA-1, and every key exchange inside a validated boundary.
algorithms := sshalgo.DefaultAlgorithms(sshalgo.WithoutDeprecated())

Tier 3 is the compatibility tier. Everything in it is still FIPS approved — HMAC-SHA-1 via SP 800-140C Rev. 2, 96-bit truncation via SP 800-107 Rev. 1 §5.3.4, SHA-1 in the SSH KDF via SP 800-135 Rev. 1 §5.2, and the DH groups as safe primes from SP 800-56A Rev. 3 Appendix D. SP 800-131A deprecates the SHA-1 constructions only until 2030-12-31. Withholding them is a hardening choice, so the default keeps older clients working; pass the option when the deployment can require modern clients.

Why the ML-KEM hybrid depends on the backend

Upstream Go approves the ML-KEM768/X25519 hybrid under fips140=on, and x/crypto/ssh registers it ahead of its own FIPS branch so it survives. LabKit agrees, on the native module: ML-KEM-768 supplies an approved shared secret, so the X25519 contribution is permissible additional keying material under SP 800-227 §4.6.2.

It is withheld on BoringCrypto for an operational reason, not a compliance one. The golang-fips toolchain patches crypto/ecdh to refuse X25519 whenever OpenSSL is active (golang-fips/go#316), so the hybrid cannot complete a handshake there. x/crypto/ssh implements no Curve25519-free hybrid, so BoringCrypto deployments have no post-quantum SSH key exchange available.

Standalone X25519 stays in Tier 1 on every backend: the FIPS 140-3 Implementation Guidance excludes curves in SP 800-186 but not SP 800-56Arev3 from key agreement, naming ECDH X25519 — see !216.

What the native module adds

Under GOFIPS140 the result is narrowed further to what x/crypto/ssh can actually negotiate. Non-approved algorithms are never registered in its internal maps, and SetDefaults() — called unconditionally by NewServerConn and NewClientConn — drops anything absent from them, so a caller cannot negotiate one even by configuring it explicitly. Deriving that narrowing rather than hand-maintaining a second list keeps LabKit in step with the linked module, whose contents depend on GOFIPS140 and may change across Go versions.

Finite-field Diffie-Hellman is the visible case — the native module does not implement it, so WithoutDeprecated() is a no-op there:

native module : [mlkem768x25519-sha256  ecdh-p256  ecdh-p384  ecdh-p521]
boringcrypto  : [ecdh-p256  ecdh-p384  ecdh-p521  dh-group14-sha256  dh-group14-sha1]

HostKeys is always left empty: those algorithms are derived from the host keys presented at handshake time, which is what HostKeySigner constrains.

Both functions also drop empty algorithm names, in every build configuration. This works around a bug in golang.org/x/crypto/ssh (present through v0.54.0) where defaultCiphers aliases the supportedCiphers backing array, so the in-place slices.DeleteFunc it runs under fips140.Enabled() leaves a zeroed tail behind — ssh.SupportedAlgorithms().Ciphers ends in "" on any GOFIPS140 build. See filter.go.

Testing

./scripts/test.sh runs both modules with $BUILD_TAGS, so the test-fips (golang-fips image, GOEXPERIMENT=boringcrypto) and test-fips-native (upstream image, GOFIPS140=v1.0.0) CI jobs both exercise this package. Two jobs are needed because cmd/go rejects GOFIPS140 combined with GOEXPERIMENT=boringcrypto.

test-fips also sets GOLANG_FIPS=1. Without it boring.Enabled() reports false, because a CI container has no /proc/sys/crypto/fips_enabled, and the job would compile the BoringCrypto path without ever activating it.

A FIPS kernel is not the only switch, and not the usual one. GOLANG_FIPS=1 switches the backend on by itself. GitLab's CNG FIPS images set it from the gitlab-base entrypoint, keyed on /etc/system-fips, so the backend is active in those images with no FIPS kernel involved. A consumer that does not run those images has to set GOLANG_FIPS=1 or run on a FIPS kernel; otherwise the binary carries the BoringCrypto path without using it, and LogStatus reports that state.

The SSH tests negotiate a real handshake and assert on ssh.NegotiatedAlgorithms, because the algorithm sets alone cannot show what a server offers — see COMPLIANCE.md for the four configurations that matter and how to reproduce them locally in containers.

Documentation

Overview

Package fips reports whether this binary performs cryptographic operations through a FIPS 140-3 validated module.

Detection is gated on the fips build tag. Without it, ActiveBackend always reports BackendNone and Enabled always reports false, so ordinary builds carry no FIPS-related code. Built with -tags fips, two backends are recognised:

  • BackendBoringCrypto: the FIPS Go compiler in https://github.com/golang-fips/go, which routes the crypto packages through a system OpenSSL and is probed via crypto/boring. It requires CGO_ENABLED=1, an amd64 Linux runtime, a system OpenSSL that can be loaded via dlopen(), and the backend to be switched on. Either a kernel in FIPS mode (/proc/sys/crypto/fips_enabled is 1) or GOLANG_FIPS=1 in the environment switches it on; the kernel is not required. GitLab's CNG FIPS images set GOLANG_FIPS from the gitlab-base entrypoint, so the backend is active there without a FIPS kernel. Consumers outside those images have to arrange one of the two themselves.

  • BackendNativeGo: upstream Go's native Go Cryptographic Module, probed via crypto/fips140. It requires the binary to be built with GOFIPS140 set to a module version (for example v1.0.0), or to run with GODEBUG=fips140=on. See https://go.dev/doc/security/fips140.

crypto/boring only has buildable files when the boringcrypto build tag is set (GOEXPERIMENT=boringcrypto), so that probe is isolated behind the tag. This keeps the fips build tag usable on a toolchain that ships the native module instead of the golang-fips fork.

This package depends only on the standard library. FIPS-compliant SSH algorithm sets live in the gitlab.com/gitlab-org/labkit/v2/fips/sshalgo subpackage, which pulls in golang.org/x/crypto.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Enabled

func Enabled() bool

Enabled reports whether cryptographic operations are backed by a FIPS module. Callers that need to know which module should use ActiveBackend instead.

Enabled is derived from ActiveBackend rather than probing the backends a second time, so the two cannot disagree.

func LogStatus

func LogStatus(logger *slog.Logger)

LogStatus records this binary's FIPS posture at info level, under the fips_backend field. Services should call it once during start-up: the posture is fixed for the lifetime of the process, and an operator diagnosing a FIPS deployment needs it in the logs of every component.

A nil logger falls back to slog.Default.

Types

type Backend

type Backend string

Backend identifies the cryptographic backend satisfying FIPS 140-3 for this binary. It is a string so it renders usefully in logs and metrics labels.

const (
	// BackendNone indicates no FIPS backend is active. Either the binary was
	// not built with the fips build tag, or it was but neither backend
	// activated at runtime.
	BackendNone Backend = "none"

	// BackendBoringCrypto indicates a BoringCrypto-compatible backend is
	// handling crypto operations, i.e. the external OpenSSL library used by
	// the golang-fips toolchain.
	BackendBoringCrypto Backend = "boringcrypto"

	// BackendNativeGo indicates upstream Go's native Go Cryptographic Module
	// is handling crypto operations.
	BackendNativeGo Backend = "native-go"
)

func ActiveBackend

func ActiveBackend() Backend

ActiveBackend reports which cryptographic backend is satisfying FIPS 140-3 for this binary. Without the fips build tag no backend is compiled in, so this is always BackendNone.

func (Backend) String

func (b Backend) String() string

String implements fmt.Stringer.

Directories

Path Synopsis
Package sshalgo provides the SSH algorithm sets a server should offer, filtered for FIPS 140-3 compliance when the binary is built with the fips build tag.
Package sshalgo provides the SSH algorithm sets a server should offer, filtered for FIPS 140-3 compliance when the binary is built with the fips build tag.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL