mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-19 06:02:52 +00:00
# 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
100 lines
2.9 KiB
Go
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
|
|
}
|