Skip to content

FIPS Mode

NeySlim edited this page Oct 18, 2026 · 10 revisions

FIPS Mode

Run UCM's cryptography in the validated OpenSSL FIPS Provider.

Version: 2.241 (preview)
Status: 🧪 Preview, Docker image -fips, DEB and RPM packages built on a FIPS host


Overview

When deployed as documented on this page, UCM runs its cryptography in the validated OpenSSL FIPS Provider (OpenSSL FIPS Provider 3.1.2, CMVP certificate #4985). UCM itself is not validated: the validation covers the OpenSSL module, not UCM or its configuration.

FIPS mode is available from v2.241 as a preview. It needs two things:

Piece Role
The -fips image (<version>-fips) Ships the 3.1.2 provider (fips.so) and links every library that uses OpenSSL (cryptography, psycopg2, xmlsec, Python's ssl and hashlib) to the system OpenSSL, so none carries its own copy
UCM_FIPS=1 Turns FIPS mode on: the module is configured at start, checked, and UCM refuses what the module does not approve

The same -fips image without UCM_FIPS runs in standard mode. The standard image with UCM_FIPS=1 refuses to start.

On RHEL 9 and Ubuntu 24.04 Pro, the DEB and RPM packages can instead run on the FIPS module of the host (see DEB and RPM packages).


Deployment

Requirements

  • Image neyslim/ultimate-ca-manager:<version>-fips (also on ghcr.io/neyslim/ultimate-ca-manager), for amd64 and arm64. latest-fips follows the latest stable release
  • UCM_FIPS=1 (also accepted: true, yes, on)
  • A writable /run/ucm for the container user (uid 1000): the entrypoint writes the module configuration there at every start. The image provides it; with a read-only root filesystem (--read-only), mount a tmpfs there, and one on /tmp, which UCM needs for temporary files
  • The usual volumes: /opt/ucm/data and /etc/ucm (see Installation Guide)

Docker

docker run -d \
  --name ucm \
  -p 8443:8443 \
  -p 8080:8080 \
  -e UCM_FIPS=1 \
  -v ucm-data:/opt/ucm/data \
  -v ucm-config:/etc/ucm \
  neyslim/ultimate-ca-manager:2.241-fips

# With a read-only root filesystem, add:
#   --read-only --tmpfs /run/ucm:uid=1000 --tmpfs /tmp

Docker Compose

services:
  ucm:
    image: neyslim/ultimate-ca-manager:2.241-fips
    container_name: ucm
    ports:
      - "8443:8443"
      - "8080:8080"
    environment:
      - UCM_FIPS=1
    volumes:
      - ucm-data:/opt/ucm/data
      - ucm-config:/etc/ucm
    # Only with a read-only root filesystem:
    # read_only: true
    # tmpfs:
    #   - /run/ucm:uid=1000
    #   - /tmp

volumes:
  ucm-data:
  ucm-config:

Helm

helm install ucm ./charts/ucm --set fips.enabled=true

fips.enabled: true adds the -fips suffix to the image tag (unless the tag already has it), sets UCM_FIPS=1 and mounts an in-memory volume on /run/ucm. The pod refuses to start if the provider is missing or fails its self-tests.

Building the image yourself

docker build --build-arg UCM_FIPS_BUILD=1 -t ucm:fips .
# or
./docker/build.sh --fips

The build downloads openssl-3.1.2.tar.gz, checks its SHA-256, builds the provider with the commands of its security policy (./Configure enable-fips, make) and keeps only fips.so. It also compiles cryptography, psycopg2, lxml and xmlsec from source against the system libraries, and fails if any of them still bundles its own OpenSSL or libxml2. Expect a longer build than the standard image. docker/build.sh --fips tags the result <tag>-fips.

DEB and RPM packages

On a host in FIPS mode, the packages can use the FIPS module of the distribution instead of the 3.1.2 provider. Supported hosts: RHEL, AlmaLinux and Rocky Linux 9 (RPM ucm-el9-VERSION-1.el9.noarch.rpm) and Ubuntu 24.04 with Ubuntu Pro (sudo pro enable fips-updates, then reboot). With UCM_FIPS_BUILD=1, the package compiles cryptography, psycopg2, lxml and xmlsec against the system OpenSSL; other distributions refuse the option.

RHEL, AlmaLinux and Rocky Linux 9 (on RHEL, enable CodeReady Builder with sudo subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms instead of the first line):

sudo dnf config-manager --set-enabled crb
sudo dnf install -y python3.12-devel gcc pkgconf-pkg-config libpq-devel libxml2-devel libxslt-devel \
    openssl-devel xmlsec1-devel xmlsec1-openssl libtool-ltdl-devel rust cargo
sudo install -d -m 750 /etc/ucm && echo 'UCM_FIPS_BUILD=1' | sudo tee /etc/ucm/ucm.env
sudo dnf install ./ucm-el9-VERSION-1.el9.noarch.rpm

Ubuntu 24.04 with Ubuntu Pro:

sudo apt-get install -y gcc pkg-config python3-dev python3-venv libpq-dev libxml2-dev libxslt1-dev \
    zlib1g-dev libssl-dev libxmlsec1-dev libxmlsec1t64-openssl rustc-1.91 cargo-1.91
sudo install -d -m 750 /etc/ucm && echo 'UCM_FIPS_BUILD=1' | sudo tee /etc/ucm/ucm.env
sudo apt install ./ucm_VERSION_all.deb

The first build takes about five minutes (log: /var/log/ucm/fips-build.log); later installs rebuild a package only when its version changed. When a tool is missing, the installation stops with the command to run and downloads nothing. On a host in FIPS mode, the package adds UCM_FIPS=1 to ucm.env (an explicit UCM_FIPS=0 is kept) and prints the readiness report. The built-in updater checks the build tools before each update and refuses it when one is missing.

The readiness check runs the same way on a native install:

cd /opt/ucm/backend && sudo -u ucm bash -c 'set -a; . /etc/ucm/ucm.env; set +a; /opt/ucm/venv/bin/python -m scripts.fips_preflight'

What Happens at Startup

With UCM_FIPS=1, the entrypoint and then the application check the module before anything is served:

  1. Provider present: without fips.so in the image (standard image), the container stops and says to use the -fips variant or remove UCM_FIPS
  2. /run/ucm writable: otherwise the container stops and names the tmpfs to mount
  3. openssl fipsinstall runs on the machine at every start and writes the module configuration; it runs the module's self-tests, and a failure stops the container
  4. OpenSSL configuration: fips and base providers, default_properties = fips=yes (approved algorithms only)
  5. Pre-checks: the fips provider is active, MD5 is refused, SHA-256 works, and Python's hashlib runs on the provider
  6. Application checks: both cryptography and hashlib must run on the FIPS provider, approved operations (ECDSA P-256, AES-GCM, PBKDF2) must work and refused ones (MD5, scrypt) must fail. Otherwise UCM logs the reason and exits

When everything passes, the log shows:

FIPS mode active: OpenSSL FIPS Provider 3.1.2 (library OpenSSL 3.5.x ...)

The entrypoint also forces HSM_AUTO_INIT=false: no default SoftHSM token is created in FIPS mode (see Limitations).


Verifying FIPS Mode

Health endpoint (no authentication):

curl -sk https://localhost:8443/api/v2/health
# {"status": "ok", ..., "fips_active": true}

fips_active is true only when UCM_FIPS=1 is set and the provider is active.

Service status (authenticated): GET /api/v2/system/service/status includes a crypto block:

"crypto": {
  "fips_requested": true,
  "fips_active": true,
  "provider_active": true,
  "library": "OpenSSL 3.5.x ...",
  "provider": {"name": "OpenSSL FIPS Provider", "version": "3.1.2", "buildinfo": "3.1.2"}
}

From inside the container: the entrypoint sets OPENSSL_CONF for UCM's own process only, so name it in docker exec:

docker exec -e OPENSSL_CONF=/run/ucm/openssl-fips.cnf ucm openssl list -providers
# Providers:
#   base
#     name: OpenSSL Base Provider
#     ...
#   fips
#     name: OpenSSL FIPS Provider
#     version: 3.1.2
#     status: active

docker exec -e OPENSSL_CONF=/run/ucm/openssl-fips.cnf ucm sh -c 'echo x | openssl md5'
# must fail: MD5 is not in the module

What Changes in FIPS Mode

A refused operation usually answers HTTP 400 with the problem type urn:ucm:problem:fips-restricted and a message of the form <feature> not available in FIPS mode; a few refusals give their own message (Argon2id restore, NTLM channel). Protocol endpoints answer in their own terms (SCEP badAlg, or 503 for a SCEP CA whose key is refused; OCSP unauthorized). In the web interface, forms stop offering what FIPS mode refuses: no Ed25519 for SSH CAs and keys, a FIPS rules notice on the Policies page.

Feature In FIPS mode Alternative
Ed25519 and Ed448 keys (X.509 CAs and certificates, SSH CAs and SSH keys, deploy keys, OCSP responders) Refused at creation and import. Existing Ed25519/Ed448 objects stay in the database but cannot be used: signing, CRL, OCSP, SCEP, renewal and SSH deployment answer a clear error, naming the CA or certificate where there is one ECDSA P-256, P-384, P-521 or RSA. The SSH CA, SSH user keys and deploy keys default to ECDSA P-256 in FIPS mode (Ed25519 otherwise)
RSA below 2048 bits, curves other than P-256/P-384/P-521 Refused RSA 2048, 3072, 4096; NIST P-256, P-384, P-521
SHA-1 signatures The FIPS mode system policy allows SHA-256, SHA-384 and SHA-512 only; SCEP requests signed with SHA-1 are refused with badAlg (the FIPS reason is only in the log) SHA-256 or stronger. SHA-1 as a plain digest stays (OCSP CertID, SHA-1 fingerprint)
MD5 fingerprint Not computed: the md5 field is null SHA-256 (or SHA-1) fingerprint
TSIG hmac-md5 (RFC 2136 DNS provider) Not offered; creating or switching a provider to it is refused, and an existing one sends nothing hmac-sha256 or another SHA-2 TSIG algorithm
PKCS#12 legacy export (3DES/SHA-1) Refused Default PKCS#12 export (AES-256), or PEM
JKS export Refused PKCS#12 (default mode), which Java reads as a keystore
PKCS#12 import Refused: the module has no PKCS#12 key derivation, so no archive's MAC can be verified Import the certificate as PEM and the private key as PEM or encrypted PKCS#8
Argon2id backups New backups use PBKDF2; requesting "kdf": "argon2id" is refused, and an Argon2id archive cannot be restored On the standard instance, create a backup with "kdf": "pbkdf2" (API, see Backup and Restore), and set Key derivation of scheduled backups to PBKDF2
scrypt password hashes (from before v2.240) Cannot be verified: the login fails and the password must be reset Before switching, have each local user log in once on v2.240 or later: the hash is replaced with PBKDF2 at login
SCEP 3DES content encryption and PKCS#1 v1.5 key transport are refused (badAlg); GetCACaps announces nothing refused, and replies never use PKCS#1 v1.5 Clients using RSA-OAEP and AES are served
NTLM to a Microsoft AD CS (WinRM admin channel) Testing or saving an NTLM channel is refused; a stored NTLM channel is not opened Kerberos transport (see Microsoft CA)
OpenBao / Vault Transit keys Ed25519 and ChaCha20-Poly1305 Transit keys are not listed RSA or ECDSA Transit keys
Issuance policy An implicit FIPS mode system policy applies ahead of all others on every issuance path: RSA 2048 to 4096 and EC P-256 to P-521 keys, SHA-256 to SHA-512 signatures. It cannot be edited; your policies can only narrow it, and saving a policy that allows Ed25519 or Ed448 is refused
Key generation and randomness RSA and EC keys are generated by openssl genpkey on the module. The salts, IVs and nonces of backups and of the EST and SCEP envelopes come from the module's DRBG; the encryption of stored private keys and secrets (Fernet) takes its IV from the kernel

Switching an Existing Instance to FIPS Mode

  1. Upgrade the standard instance to v2.241 or later, on the standard image, and check that it runs normally.
  2. Run the FIPS preflight, which lists what FIPS mode would refuse on this instance and changes nothing: Settings > About > Check FIPS readiness, GET /api/v2/system/fips-preflight (permission admin:system), or from the command line:
    docker exec -u ucm -w /opt/ucm/backend ucm python -m scripts.fips_preflight          # add --json for JSON
    Each finding is a blocker, a warning or an information, with a remedy. The command exits with 0 when nothing blocks, 1 with blockers, 2 when a check could not run. On an instance already in FIPS mode, add -e OPENSSL_CONF=/run/ucm/openssl-fips.cnf to docker exec: without it the command runs outside the module, reports a fips.provider error and exits with 2.
  3. Replace what FIPS mode refuses: Ed25519/Ed448 CAs, SSH CAs and deploy keys (create ECDSA or RSA replacements and reissue), TSIG hmac-md5 DNS providers, NTLM admin channels to AD CS (switch to Kerberos), SCEP clients that only send 3DES or PKCS#1 v1.5, policies that allow Ed25519/Ed448.
  4. Refresh password hashes: every local user logs in once (the scrypt hash is replaced at login). 2FA backup codes generated before v2.240 and EST, WSTEP and XCEP passwords set before v2.240 are not replaced at use: regenerate the codes and set the passwords again.
  5. Back up with PBKDF2, so that the FIPS instance can restore the archive:
    POST /api/v2/system/backup/create
    {"password": "...", "kdf": "pbkdf2"}
    Set Key derivation of scheduled backups to PBKDF2 too. Keep a copy of both volumes as well (see Backup and Restore).
  6. Switch the image to <version>-fips with the same volumes, add UCM_FIPS=1 (and the /run/ucm and /tmp tmpfs if the root filesystem is read-only).
  7. Verify: the startup log line, fips_active: true on /api/v2/health, then issue a test certificate from each CA you use.

To go back, remove UCM_FIPS (the -fips image then runs in standard mode) or return to the standard image. Backups and password hashes written in FIPS mode use PBKDF2, which a standard instance reads.


Limitations

  • UCM is not validated. Only the OpenSSL FIPS Provider 3.1.2 holds a validation (CMVP #4985), and its certificate notes that it gives no assurance of the minimum strength of the keys it generates. Whether a deployment meets your requirements is your assessment.
  • Operational environment. The tested configurations of certificate #4985 include Ubuntu 22.04 and Debian 11 on x86-64 (and FreeBSD, Windows and macOS platforms). The image is based on Debian 13, and the arm64 image runs on Linux aarch64: neither is in that list. The module runs there on platforms outside its tested configurations: what the CMVP calls user affirmation, which extends no validation to them. The library is the system OpenSSL 3.5 of Debian 13; the validated part is the provider it loads.
  • Entropy. The module expects an entropy source supplied by its environment, outside the module boundary. The image relies on the host kernel's entropy, through OpenSSL; the 3.1.2 provider has no built-in jitter entropy source.
  • SoftHSM is not a validated module. In FIPS mode no default SoftHSM token is created; a SoftHSM provider you configure still works, but its keys are not protected by a validated module.
  • External HSMs carry their own validation: with an HSM-backed CA, signatures are made by the HSM, so check that HSM's certificate and mode (see HSM Support).
  • DEB and RPM packages run on the FIPS module of the distribution, whose validation is the distribution's: check its certificate and the configurations it covers for your release. Without UCM_FIPS_BUILD=1, the packages install Python wheels that bundle their own OpenSSL, and UCM_FIPS=1 refuses to start. Ubuntu 22.04 is not supported.
  • PostgreSQL: in the -fips image, psycopg2 is linked to the system libpq and OpenSSL. In FIPS mode, UCM refuses to start when DATABASE_URL reaches PostgreSQL over TCP without sslmode=require or stricter; use verify-full when the server certificate can be checked. A server on the same host can be reached through its Unix socket instead, which needs no TLS: DATABASE_URL=postgresql://ucm:<password>@/ucm?host=/var/run/postgresql. See Database Backend.

Compliance Frameworks

In FIPS mode, UCM performs its cryptographic operations in a CMVP-validated module: the OpenSSL FIPS Provider 3.1.2 (certificate #4985) in the -fips image, or the FIPS module of the distribution with the DEB and RPM packages. Record the module's CMVP certificate in your register, with the systems that rely on it. UCM itself is not a validated cryptographic module and is not FIPS certified.

  • NIST SP 800-53 Rev. 5 (FedRAMP), SC-13: a validated module is necessary but not sufficient. Authorization covers the whole system, including its environment, its operations and the provider's development controls.
  • NIST SP 800-171 Rev. 2 (CMMC Level 2), 3.13.11: the requirement is FIPS-validated cryptography to protect CUI; FIPS mode helps meet it when the deployment is configured and operated as documented on this page.
  • Outside the US and Canada, FIPS mode is supporting evidence at most (Australia's ISM, Germany's BSI TR-02102, France's ANSSI guide PG-083), and it does not cover national algorithm suites such as SM2/SM3/SM4 or GOST, which UCM does not implement today.

Whether a deployment meets a given framework depends on that framework and on how your organization supports it.


Related Documentation

Clone this wiki locally