Files
zitadel/internal/crypto/fips140.go
T
Tim MöhlmannandGitHub e94d4c3986 feat(crypto): FIPS 140-3 compliant build and runtime checks (#12233)
# Which Problems Are Solved

Enable FIPS 140-3 compliant build.

# How the Problems Are Solved

- Add runtime config validation, if the FIPS flag is enabled fail the
application when a non-compliant hasher is used, or throw a warning when
a legacy verifier is used
- Add a build matrix for FIPS certified build:
  - Go binary is built with `GOFIPS140=certified`
- Login container uses a separate base:
[ubi9](https://catalog.redhat.com/en/software/containers/ubi9/ubi/615bcf606feffc5384e8452e)
from redhat which provides a FIPS certified OpenSSL (used by NodeJS TLS
stack)
- Non-FIPS images where already pushed to both Github Container Registry
and Google Artifact Repository (GAR). Fips images are only pushed to the
GAR.
- Tag versions are suffixed `-fips`. So on release the following images
will be additionally available:

```
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel-login:vX.Y.Z-fips
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel:vX.Y.Z-fips-debug
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel:vX.Y.Z-fips
```
  
# Other changes

- Bumb Go toolchain. At least v1.25.10 is required for a
GOFIPS140=certified setting.
 
# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/4335
- Build [test
run](https://github.com/zitadel/zitadel/actions/runs/27253916052)
pushing FIPS and non-FIPS images
2026-06-16 10:16:49 +02:00

100 lines
2.9 KiB
Go

package crypto
import (
"crypto/fips140"
"fmt"
"log/slog"
"github.com/zitadel/zitadel/backend/v3/instrumentation/logging"
)
// fipsPBKDF2MinIterations is the minimum PBKDF2 iteration count per NIST SP 800-132 §5.2.
const fipsPBKDF2MinIterations uint32 = 1000
// fips140Mode reports whether FIPS 140-3 application policies are enforced.
// Defaults to crypto/fips140.Enabled; tests in this package may replace it.
var fips140Mode = fips140.Enabled
func fips140Enabled() bool {
return fips140Mode()
}
// IsFIPSCompliant reports whether the algorithm is approved for password
// hashing/verification under FIPS 140-3 (NIST SP 800-132).
func (n HashName) IsFIPSCompliant() bool {
return n == HashNamePBKDF2
}
// IsFIPSCompliant reports whether the hash mode is an approved PBKDF2 PRF
// under FIPS 140-3 (SHA-2 family; SHA-1/SHA-224 rejected for new use).
func (m HashMode) IsFIPSCompliant() bool {
return m == HashModeSHA256 || m == HashModeSHA384 || m == HashModeSHA512
}
func nonFIPSVerifiersConfigured(verifiers []HashName) []HashName {
var found []HashName
for _, v := range verifiers {
if !v.IsFIPSCompliant() {
found = append(found, v)
}
}
return found
}
func validateFIPSPBKDF2Hasher(c HasherConfig) error {
p, hashMode, err := c.pbkdf2Params()
if err != nil {
return fmt.Errorf("decode pbkdf2 hasher for FIPS validation: %w", err)
}
if !hashMode.IsFIPSCompliant() {
return fmt.Errorf(
"application cannot start in uncertified cryptographic state: pbkdf2 hash mode %q is not FIPS 140-3 compliant while FIPS mode is enabled",
hashMode,
)
}
if p.Rounds < fipsPBKDF2MinIterations {
return fmt.Errorf(
"application cannot start in uncertified cryptographic state: pbkdf2 iteration count %d is below the FIPS minimum of %d while FIPS mode is enabled",
p.Rounds, fipsPBKDF2MinIterations,
)
}
return nil
}
func (c *HashConfig) validateFIPS140() error {
if !fips140Enabled() {
return nil
}
alg := c.Hasher.Algorithm
if !alg.IsFIPSCompliant() {
return fmt.Errorf(
"application cannot start in uncertified cryptographic state: password hasher algorithm %q is not FIPS 140-3 compliant while FIPS mode is enabled",
alg,
)
}
if alg == HashNamePBKDF2 {
if err := validateFIPSPBKDF2Hasher(c.Hasher); err != nil {
return err
}
}
if legacy := nonFIPSVerifiersConfigured(c.Verifiers); len(legacy) > 0 {
logging.New(logging.StreamRuntime).Warn(
"Non-FIPS compliant password verifiers are active for migration. This instance is temporarily non-compliant until these verifiers are disabled",
slog.Any("verifiers", legacy),
)
}
if c.Limits.PBKDF2.MinRounds < fipsPBKDF2MinIterations {
logging.New(logging.StreamRuntime).Warn(
"PBKDF2 MinRounds is below the FIPS 140-3 minimum iteration count; imported hashes may use non-compliant cost parameters until limits are raised",
slog.Uint64("min_rounds", uint64(c.Limits.PBKDF2.MinRounds)),
slog.Uint64("fips_minimum", uint64(fipsPBKDF2MinIterations)),
)
}
return nil
}