Expand description
FIPS 140-3 Level 1 module boundary.
This crate defines the cryptographic module boundary per
FIPS 140-3 Section 7.2 and the service-gating plumbing every
other crate in the workspace depends on. No algorithm is
permitted to produce output until the power-up self-tests have
run and the module has entered the Operational state; on any
self-test failure the module latches into a terminal Error
state and rejects every subsequent call for the remainder of
the process lifetime.
§State machine (FIPS 140-3 §7.2)
┌──────────┐ initialize_with_tests() ┌───────────┐ all KATs pass ┌──────────────┐
│ PowerOff │───────────────────────────▶│ SelfTest │────────────────▶│ Operational │
└──────────┘ └───────────┘ └──────────────┘
│ │
KAT failure conditional self-test
│ failure
▼ │
┌─────────┐◀───────────────────────┘
│ Error │
└─────────┘An empty integrity group moves SelfTest to the terminal
IntegrityUnverified state instead of Error: the module was never
checked, rather than checked and found wrong.
§What this crate ships
Stateand the transition machine (PowerOff → SelfTest → Operational | Error | IntegrityUnverified).initialize_with_tests— the canonical one-shot entry point that the top-level caller uses to run the integrity test and every approved algorithm’s power-up KAT before opening the service interface.require_operational— the gate that every approved service in every other crate calls on entry. ReturnsError::NotOperational(with the observed state) on any pre-Operationalor post-Errorcall.require_self_test_started— the guard that every public*_internalfunction calls on entry. It admitsOperational, andSelfTeston the thread running the power-up self-tests, so those tests can drive the internals, and refuses everything else.enter_error_state— irreversible transition into the terminal error state, called by conditional self-tests (pairwise consistency, DRBG health) when they detect a failure.KatEntryand theSelfTesttrait — the registry shape that algorithm crates use to expose their power-up KATs.AlgorithmProfile— runtime selection of an algorithm restriction policy (Unrestricted, CNSA 2.0, CNSA 1.0, Migration). Set once at initialization viainitialize_with_profile.Service— per-algorithm-and-parameter enumeration of every approved service in the module. Used withrequire_allowedto enforce profile restrictions.
The test registry is not assembled by linker-section
tricks: callers pass an explicit &[KatEntry] slice. That
means the full set of power-up tests is visible in source at
every call site, which makes it straightforward to audit the
module’s power-up inventory against the Security Policy.
§Algorithm profiles
A single binary serves general FIPS 140-3 consumers
and CNSA-restricted deployments via a runtime
AlgorithmProfile selection. The operator passes the
desired profile to initialize_with_profile; subsequent
calls to require_allowed enforce it. Services not
permitted by the active profile return
Error::AlgorithmRestricted.
Four profiles are defined:
- Unrestricted — all FIPS-approved algorithms are
available. This is the default, matching the behavior of
initialize_with_tests. - CNSA 2.0 — CNSSP-15 (March 2025) Annex B: AES-256, SHA-384 or SHA-512, ML-KEM-1024, ML-DSA-87, and the SP 800-208 stateful hash-based signatures for software and firmware signing, together with the supporting services those need at matching strength. Every approved LMS parameter set is permitted; Annex B places no subset restriction on LMS. No Keccak-family hash or XOF is permitted: the suite names none for a software module, and the policy states that algorithms using SHA3 as a component do not thereby make SHA3 an approved hash function. No SLH-DSA parameter set is permitted — CNSSP-15 lists none.
- CNSA 1.0 — CNSSP-15 (October 2016) Annex B: AES-256, ECDH and ECDSA on P-384, SHA-384, and Diffie-Hellman and RSA at a minimum 3072-bit modulus, with the supporting services those need at matching strength. SHA-256 is refused: the suite admits it only under a near-term allowance scoped to UNCLASSIFIED data, a condition the module cannot evaluate.
- Migration — both suites at once, for a deployment moving between them. It is not a CNSA profile and no standard defines it; it is the union of the two above, and is narrower than Unrestricted, which refuses nothing.
The profiles do not nest. Neither CNSA suite contains the other: CNSA 1.0 admits ECDSA, RSA and Diffie-Hellman that CNSA 2.0 refuses, and CNSA 2.0 admits the post-quantum and stateful hash-based algorithms that CNSA 1.0 refuses. The two overlap in AES-256 and SHA-384 and their matching-strength support, and nowhere else. KATs still run all algorithms regardless of profile — the profile only restricts post-initialization service access.
§Sensitive security parameters (SSPs)
This crate holds no SSPs of its own. It only gates
access to other crates that do. Secret material lives in the
algorithm crates that own it (e.g. oxicrypt-rsa’s
RsaPrivateKey2048, oxicrypt-drbg’s DRBG states). The error
latch here does not perform SSP zeroization on its own — each
owning crate is responsible for ensuring its SSPs are
dropped when the process restarts following a transition into
the Error state.
§FIPS 140-3 / SP 800-140B mapping
| SP 800-140B / IG clause | Implementation |
|---|---|
| §7.10 power-up self-tests | initialize_with_tests runs every registered KatEntry sequentially; the first failure latches State::Error. |
| §7.10 conditional self-tests | Algorithm crates call enter_error_state on detecting a pairwise-consistency or DRBG-health failure. |
| IG 9.5.A approved-mode indicator | is_operational / state — queryable at runtime. |
| §7.10.2.2 software integrity | Delegated to oxicrypt-integrity (separate crate), whose KATS the top-level caller runs first: the technique’s own CAST, then the integrity test. |
§Thread safety
State is stored in a single AtomicU64 and is safe to read
from any thread. Its low byte is the State; while the state is
SelfTest, the bits above it hold the operating system’s id for the
thread running the self-tests, published in the same word so that no
reader sees one without the other. The id comes from
oxicrypt-imageread: gettid on Linux and Android,
pthread_threadid_np on Darwin, GetCurrentThreadId on Windows. A
target with none of these cannot own SelfTest and does not initialise.
A target without 64-bit atomics (target_has_atomic = "64") cannot
hold the word, and the crate refuses to compile for it.
initialize_with_tests uses a compare-and-swap to guarantee the
self-test phase runs exactly once per process lifetime even under
concurrent first-calls. Every self-test runs on the thread that won
it, and require_self_test_started admits SelfTest to that thread
alone, so another thread’s *_internal call made while initialisation
is running, or paused, is refused. Racing losers wait on the state
word, so they do not observe the transient phase, and then receive
Error::AlreadyInitialized or Error::AlreadyFailed. A self-test
that unwinds out of initialisation leaves the module in Error.
A service’s check in Operational is one acquire load of the state
word and one comparison: a plain load on x86_64, ldar on AArch64,
and on a 32-bit target the platform’s 64-bit atomic load. The thread id
is asked for only by an *_internal call that finds SelfTest.
Structs§
- KatEntry
- A power-up KAT registered with the module.
- Self
Test Failure - Marker returned by a failing
SelfTest::run. - Self
Test Started - Proof that
require_self_test_startedpassed for the holder.
Enums§
- Algorithm
Profile - Algorithm restriction profile selected at module initialization.
- Error
- Errors surfaced by the module boundary.
- Service
- Enumeration of every approved service in the module, at the algorithm-and-parameter level.
- State
- Module lifecycle state per FIPS 140-3 Section 7.2.
Constants§
- THREAD_
ID_ CHECK - The name
Error::SelfTestFailedcarries when initialisation fails because the platform gives the initialising thread no id the module can record, so no thread could ownSelfTest.
Traits§
- Self
Test - A single power-up self-test.
Functions§
- active_
profile - Returns the active algorithm profile.
- enter_
error_ state - Forces the module into the terminal
Errorstate. - initialize_
with_ profile - Initializes the module with a specific algorithm profile and runs every supplied power-up KAT.
- initialize_
with_ tests - Initializes the module and runs every supplied power-up KAT.
- is_
operational - Returns
trueiff the module has passed its power-up self-tests and is currently in theOperationalstate. - require_
allowed - Guard used at the entry point of every approved service, after
require_operational, to enforce the active algorithm profile. - require_
operational - Guard used at the entry point of every approved service. Returns
Ok(())only when the module isOperational; otherwise returns aError::NotOperationaldescribing the current state so callers cannot accidentally ignore it. - require_
self_ test_ started - Guard used at the entry point of every public
*_internalfunction. - require_
self_ test_ started_ token require_self_test_started, returning aSelfTestStartedproof.- state
- Returns the current module state.