Skip to main content

oxicrypt_module/
lib.rs

1//! FIPS 140-3 Level 1 module boundary.
2//!
3//! This crate defines the **cryptographic module boundary** per
4//! FIPS 140-3 Section 7.2 and the service-gating plumbing every
5//! other crate in the workspace depends on. No algorithm is
6//! permitted to produce output until the power-up self-tests have
7//! run and the module has entered the `Operational` state; on any
8//! self-test failure the module latches into a terminal `Error`
9//! state and rejects every subsequent call for the remainder of
10//! the process lifetime.
11//!
12//! # State machine (FIPS 140-3 §7.2)
13//!
14//! ```text
15//!    ┌──────────┐  initialize_with_tests()  ┌───────────┐  all KATs pass  ┌──────────────┐
16//!    │ PowerOff │───────────────────────────▶│ SelfTest  │────────────────▶│ Operational  │
17//!    └──────────┘                            └───────────┘                 └──────────────┘
18//!                                                   │                              │
19//!                                               KAT failure                conditional self-test
20//!                                                   │                            failure
21//!                                                   ▼                              │
22//!                                              ┌─────────┐◀───────────────────────┘
23//!                                              │  Error  │
24//!                                              └─────────┘
25//! ```
26//!
27//! # What this crate ships
28//!
29//! - [`State`] and the transition machine (`PowerOff →
30//!   SelfTest → Operational | Error`).
31//! - [`initialize_with_tests`] — the canonical one-shot entry
32//!   point that the top-level caller uses to run the integrity
33//!   test and every approved algorithm's power-up KAT before
34//!   opening the service interface.
35//! - [`require_operational`] — the gate that every approved
36//!   service in every other crate calls on entry. Returns
37//!   [`Error::NotOperational`] (with the observed state) on any
38//!   pre-`Operational` or post-`Error` call.
39//! - [`enter_error_state`] — irreversible transition into the
40//!   terminal error state, called by conditional self-tests
41//!   (pairwise consistency, DRBG health) when they detect a
42//!   failure.
43//! - [`KatEntry`] and the [`SelfTest`] trait — the registry
44//!   shape that algorithm crates use to expose their power-up
45//!   KATs.
46//! - [`AlgorithmProfile`] — runtime selection of an algorithm
47//!   restriction policy (Unrestricted, CNSA 2.0, CNSA 1.0,
48//!   Migration).
49//!   Set once at initialization via
50//!   [`initialize_with_profile`].
51//! - [`Service`] — per-algorithm-and-parameter enumeration of
52//!   every approved service in the module. Used with
53//!   [`require_allowed`] to enforce profile restrictions.
54//!
55//! The test registry is **not** assembled by linker-section
56//! tricks: callers pass an explicit `&[KatEntry]` slice. That
57//! means the full set of power-up tests is visible in source at
58//! every call site, which makes it straightforward to audit the
59//! module's power-up inventory against the Security Policy.
60//!
61//! # Algorithm profiles
62//!
63//! A single binary serves general FIPS 140-3 consumers
64//! and CNSA-restricted deployments via a runtime
65//! [`AlgorithmProfile`] selection. The operator passes the
66//! desired profile to [`initialize_with_profile`]; subsequent
67//! calls to [`require_allowed`] enforce it. Services not
68//! permitted by the active profile return
69//! [`Error::AlgorithmRestricted`].
70//!
71//! Four profiles are defined:
72//!
73//! - **Unrestricted** — all FIPS-approved algorithms are
74//!   available. This is the default, matching the behavior of
75//!   [`initialize_with_tests`].
76//! - **CNSA 2.0** — CNSSP-15 (March 2025) Annex B: AES-256,
77//!   SHA-384 or SHA-512, ML-KEM-1024, ML-DSA-87, and the
78//!   SP 800-208 stateful hash-based signatures for software and
79//!   firmware signing, together with the supporting services
80//!   those need at matching strength. Every approved LMS
81//!   parameter set is permitted; Annex B places no subset
82//!   restriction on LMS. No Keccak-family hash or XOF is
83//!   permitted: the suite names none for a software module, and
84//!   the policy states that algorithms using SHA3 as a component
85//!   do not thereby make SHA3 an approved hash function. No
86//!   SLH-DSA parameter set is permitted — CNSSP-15 lists none.
87//! - **CNSA 1.0** — CNSSP-15 (October 2016) Annex B: AES-256,
88//!   ECDH and ECDSA on P-384, SHA-384, and Diffie-Hellman and
89//!   RSA at a minimum 3072-bit modulus, with the supporting
90//!   services those need at matching strength. SHA-256 is
91//!   refused: the suite admits it only under a near-term
92//!   allowance scoped to UNCLASSIFIED data, a condition the
93//!   module cannot evaluate.
94//! - **Migration** — both suites at once, for a deployment
95//!   moving between them. It is **not** a CNSA profile and no
96//!   standard defines it; it is the union of the two above, and
97//!   is narrower than Unrestricted, which refuses nothing.
98//!
99//! **The profiles do not nest.** Neither CNSA suite contains the
100//! other: CNSA 1.0 admits ECDSA, RSA and Diffie-Hellman that
101//! CNSA 2.0 refuses, and CNSA 2.0 admits the post-quantum and
102//! stateful hash-based algorithms that CNSA 1.0 refuses. The two
103//! overlap in AES-256 and SHA-384 and their matching-strength
104//! support, and nowhere else. KATs still run all algorithms
105//! regardless of profile — the profile only restricts
106//! post-initialization service access.
107//!
108//! # Sensitive security parameters (SSPs)
109//!
110//! This crate holds **no SSPs** of its own. It only gates
111//! access to other crates that do. Secret material lives in the
112//! algorithm crates that own it (e.g. `oxicrypt-rsa`'s
113//! `RsaPrivateKey2048`, `oxicrypt-drbg`'s DRBG states). The error
114//! latch here does not perform SSP zeroization on its own — each
115//! owning crate is responsible for ensuring its SSPs are
116//! dropped when the process restarts following a transition into
117//! the `Error` state.
118//!
119//! # FIPS 140-3 / SP 800-140B mapping
120//!
121//! | SP 800-140B / IG clause | Implementation |
122//! |-------------------------|----------------|
123//! | §7.10 power-up self-tests | [`initialize_with_tests`] runs every registered [`KatEntry`] sequentially; the first failure latches [`State::Error`]. |
124//! | §7.10 conditional self-tests | Algorithm crates call [`enter_error_state`] on detecting a pairwise-consistency or DRBG-health failure. |
125//! | IG 9.5.A approved-mode indicator | [`is_operational`] / [`state`] — queryable at runtime. |
126//! | §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. |
127//!
128//! # Thread safety
129//!
130//! State is stored in a single `AtomicU8` and is safe to read
131//! from any thread. [`initialize_with_tests`] uses a
132//! compare-and-swap to guarantee the self-test phase runs
133//! exactly once per process lifetime even under concurrent
134//! first-calls; racing losers spin on the `SelfTest` state byte
135//! so they do not observe the transient phase and then receive
136//! [`Error::AlreadyInitialized`].
137
138#![no_std]
139#![forbid(unsafe_code)]
140
141use core::fmt;
142use core::sync::atomic::{AtomicU8, Ordering};
143
144/// Module lifecycle state per FIPS 140-3 Section 7.2.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146#[repr(u8)]
147pub enum State {
148    /// Module has not yet been initialized. No services are available.
149    PowerOff = 0,
150    /// Power-up self-tests are currently executing. No services are
151    /// available. This state is transient.
152    SelfTest = 1,
153    /// All power-up self-tests have passed. Approved services are
154    /// available.
155    Operational = 2,
156    /// A self-test has failed (either a power-up KAT or a conditional
157    /// test). This state is **terminal** within a process: no further
158    /// services are offered and all cached secret material must be
159    /// treated as zeroized. The only recovery is to restart the
160    /// containing process.
161    Error = 3,
162    /// The module reached the end of its pre-operational sequence
163    /// without the software integrity test having been performed, so it
164    /// was never verified against its reference MAC. **Terminal**, and
165    /// no services are offered.
166    ///
167    /// Distinct from [`State::Error`] because the two are different
168    /// faults with different owners. `Error` means a test ran and the
169    /// module failed it: the artifact is wrong. This state means nothing
170    /// checked the artifact at all — a front end initialised the module
171    /// without the integrity test in its inventory. Nothing here says
172    /// the module is corrupt; it says the module is unverified, and an
173    /// operator told only "Error" would go looking for corruption that
174    /// may not exist.
175    IntegrityUnverified = 4,
176}
177
178impl State {
179    const fn from_u8(raw: u8) -> Self {
180        match raw {
181            0 => Self::PowerOff,
182            1 => Self::SelfTest,
183            2 => Self::Operational,
184            4 => Self::IntegrityUnverified,
185            // Any unknown discriminant collapses to `Error` so that a
186            // corrupted state byte can never be interpreted as
187            // `Operational`. This is defence-in-depth: `AtomicU8`
188            // should only ever hold values written through this
189            // module.
190            _ => Self::Error,
191        }
192    }
193}
194
195impl fmt::Display for State {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        let s = match self {
198            Self::PowerOff => "PowerOff",
199            Self::SelfTest => "SelfTest",
200            Self::Operational => "Operational",
201            Self::Error => "Error",
202            Self::IntegrityUnverified => "IntegrityUnverified",
203        };
204        f.write_str(s)
205    }
206}
207
208/// Global module state. Written only by [`initialize_with_tests`] and
209/// [`enter_error_state`]; read by [`state`] and [`require_operational`].
210static STATE: AtomicU8 = AtomicU8::new(State::PowerOff as u8);
211
212/// Returns the current module state.
213pub fn state() -> State {
214    State::from_u8(STATE.load(Ordering::Acquire))
215}
216
217/// Returns `true` iff the module has passed its power-up self-tests and is
218/// currently in the `Operational` state.
219pub fn is_operational() -> bool {
220    state() == State::Operational
221}
222
223/// Guard used at the entry point of every approved service. Returns
224/// `Ok(())` only when the module is `Operational`; otherwise returns a
225/// [`Error::NotOperational`] describing the current state so callers
226/// cannot accidentally ignore it.
227pub fn require_operational() -> Result<(), Error> {
228    let current = state();
229    if current == State::Operational {
230        Ok(())
231    } else {
232        Err(Error::NotOperational { current })
233    }
234}
235
236/// Errors surfaced by the module boundary.
237///
238/// Every variant is constructed with a `&'static str` description so
239/// that formatting is allocation-free; this lets the module report
240/// failures even from `no_std` contexts and from within the self-test
241/// runner itself.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum Error {
244    /// A service was invoked while the module was not in the
245    /// `Operational` state.
246    NotOperational {
247        /// The state the module was actually in when the call arrived.
248        current: State,
249    },
250    /// A power-up self-test failed. The module is now in the `Error`
251    /// state.
252    SelfTestFailed {
253        /// Name of the failing test, supplied by its
254        /// [`SelfTest`] implementation.
255        test: &'static str,
256    },
257    /// A conditional self-test (e.g. DRBG CRNGT, pairwise consistency)
258    /// failed during operation. The module is now in the `Error` state.
259    ConditionalTestFailed {
260        /// Short description of the failing check.
261        reason: &'static str,
262    },
263    /// The pre-operational self-test sequence completed without the
264    /// software integrity test having been performed. The module is now
265    /// in the [`State::IntegrityUnverified`] state.
266    ///
267    /// This is an integration fault rather than a finding about the
268    /// module: the test inventory handed to [`initialize_with_tests`]
269    /// did not include `oxicrypt_integrity::KATS`.
270    IntegrityNotAttested,
271    /// [`initialize_with_tests`] was called while the module was already
272    /// past `PowerOff`. This is not itself a FIPS violation — it just means
273    /// a second caller raced with the first. The first call's outcome
274    /// is authoritative; the race loser should read [`state`] to find
275    /// out.
276    AlreadyInitialized,
277    /// A cryptographic service was called with input bytes that do not
278    /// encode a valid domain element (for example, a scalar that is
279    /// zero or not less than the group order, or a point that is not
280    /// a canonical encoding on the curve). This is distinct from a
281    /// self-test failure: the module is still operational; the caller
282    /// supplied out-of-range bytes. Algorithm primitives should return
283    /// this rather than silently substituting a default.
284    InvalidInput,
285    /// A service was invoked that is not permitted under the active
286    /// [`AlgorithmProfile`]. The module is still operational — the
287    /// algorithm exists but is restricted by policy. Switch to a
288    /// less restrictive profile (which requires a process restart)
289    /// or use an alternative algorithm that is permitted.
290    AlgorithmRestricted {
291        /// The specific service that was blocked.
292        service: Service,
293    },
294    /// A service was invoked for an algorithm that exists in the
295    /// [`Service`] enum but whose implementation has not yet been
296    /// completed. Stub crates return this. The algorithm is
297    /// recognized by the profile system and its gate will pass if
298    /// the profile permits it, but the actual cryptographic
299    /// operation is not yet available.
300    NotImplemented,
301}
302
303impl fmt::Display for Error {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        match self {
306            Self::NotOperational { current } => {
307                write!(f, "FIPS module not operational (state = {current})")
308            }
309            Self::SelfTestFailed { test } => {
310                write!(f, "FIPS power-up self-test failed: {test}")
311            }
312            Self::ConditionalTestFailed { reason } => {
313                write!(f, "FIPS conditional self-test failed: {reason}")
314            }
315            Self::IntegrityNotAttested => f.write_str(
316                "this module did not check itself at startup, so it cannot tell whether this \
317                 copy is the one that was built and signed. That does not mean the module is \
318                 damaged: the program that started it skipped the check.",
319            ),
320            Self::AlreadyInitialized => f.write_str("FIPS module already initialized"),
321            Self::InvalidInput => f.write_str("invalid input to FIPS service"),
322            Self::AlgorithmRestricted { service } => {
323                write!(
324                    f,
325                    "algorithm {service} is not permitted under the {} profile",
326                    active_profile()
327                )
328            }
329            Self::NotImplemented => f.write_str("algorithm not yet implemented"),
330        }
331    }
332}
333
334/// Marker returned by a failing [`SelfTest::run`].
335///
336/// Self-tests are allowed to describe *what* failed, but they are not
337/// allowed to exfiltrate the intermediate values that triggered the
338/// failure — including them in an error payload would potentially leak
339/// information about internal state. The runner treats the mere
340/// presence of this value as "test failed" and converts it into
341/// [`Error::SelfTestFailed`] using the test's own [`SelfTest::NAME`].
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub struct SelfTestFailure;
344
345impl fmt::Display for SelfTestFailure {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        f.write_str("self-test failure")
348    }
349}
350
351/// A single power-up self-test.
352///
353/// Each approved algorithm crate will implement this trait for at least
354/// one test vector, per SP 800-140B. The [`initialize_with_tests`] routine
355/// runs every registered test sequentially before the module can leave the
356/// `SelfTest` state.
357///
358/// Implementations **must** be deterministic, **must not** allocate,
359/// and **must not** read from any input the caller controls: the
360/// whole point of a KAT is that its inputs and expected outputs are
361/// baked into the module binary.
362pub trait SelfTest {
363    /// Stable, human-readable identifier for this test. Appears in
364    /// [`Error::SelfTestFailed`] and in audit logs.
365    const NAME: &'static str;
366
367    /// Runs the test. Returns `Ok(())` on success, `Err(SelfTestFailure)`
368    /// on any mismatch. The runner converts the `Err` into
369    /// [`Error::SelfTestFailed`] using [`Self::NAME`].
370    fn run() -> Result<(), SelfTestFailure>;
371}
372
373/// A power-up KAT registered with the module.
374///
375/// Algorithm crates expose one or more `KatEntry` values that the
376/// top-level caller passes into [`initialize_with_tests`] at startup.
377/// The runner executes every entry sequentially before the module
378/// leaves the `SelfTest` state; a single failure latches the module
379/// into `Error` for the remainder of the process lifetime, per
380/// FIPS 140-3 Section 7.10 and SP 800-140B.
381///
382/// The function pointer signature takes no inputs on purpose: a KAT
383/// must run against compile-time-baked vectors only, never against
384/// caller-supplied data.
385#[derive(Debug, Clone, Copy)]
386pub struct KatEntry {
387    /// Stable identifier for the test. Surfaces in
388    /// [`Error::SelfTestFailed`] and audit logs. Must match the
389    /// [`SelfTest::NAME`] used for the underlying vector.
390    pub name: &'static str,
391    /// The test routine itself.
392    pub run: fn() -> Result<(), SelfTestFailure>,
393}
394
395/// Initializes the module and runs every supplied power-up KAT.
396///
397/// This is the canonical entry point. It transitions
398/// `PowerOff -> SelfTest`, executes every entry in `tests` in order,
399/// and then transitions to `Operational` on success or to the
400/// terminal `Error` state on the first failure. The returned
401/// [`Error::SelfTestFailed`] carries the name of the failing test.
402///
403/// Concurrent calls are safe: exactly one caller wins the
404/// `PowerOff -> SelfTest` CAS; losers receive
405/// [`Error::AlreadyInitialized`] and should consult [`state`] to
406/// discover whether the winning call succeeded.
407///
408/// Note: the slice is passed **by reference**, not built up by magic.
409/// This repository deliberately avoids linker-section registration
410/// tricks: the full set of power-up tests is visible in source at
411/// every call site, which makes it straightforward to audit the
412/// module's test inventory against the Security Policy.
413pub fn initialize_with_tests(integrity: &[KatEntry], tests: &[KatEntry]) -> Result<(), Error> {
414    // Try to claim the SelfTest phase. Only the first caller in the
415    // process lifetime succeeds.
416    let cas = STATE.compare_exchange(
417        State::PowerOff as u8,
418        State::SelfTest as u8,
419        Ordering::AcqRel,
420        Ordering::Acquire,
421    );
422    if cas.is_err() {
423        // Another thread is running (or has run) the self-tests. If
424        // it is still inside `SelfTest`, spin until it publishes a
425        // terminal state so racing callers don't observe the
426        // transient phase and trip `require_operational`.
427        while STATE.load(Ordering::Acquire) == State::SelfTest as u8 {
428            core::hint::spin_loop();
429        }
430        let current = state();
431        if current == State::Operational {
432            return Err(Error::AlreadyInitialized);
433        }
434        // The module is in a terminal state. Reporting
435        // `AlreadyInitialized` here would read as success to a caller
436        // that treats it as one, so the state is named instead.
437        return Err(Error::NotOperational { current });
438    }
439
440    // The integrity group runs first and cannot be OMITTED: it is a
441    // separate parameter rather than an entry in `tests`, so a caller
442    // cannot drop it by handing over a shorter list, and an empty group
443    // is refused rather than treated as "nothing to check".
444    //
445    // What this does NOT establish is the group's identity. `KatEntry`
446    // is a public struct, so any non-empty slice satisfies this check —
447    // including one whose `run` returns `Ok(())` without verifying
448    // anything. Passing a counterfeit group is an integration fault
449    // that leaves the validated configuration, in the same class as
450    // calling an approved algorithm with a key the operator invented;
451    // the module cannot detect it and does not claim to. The Security
452    // Policy states the obligation: initialise with
453    // `oxicrypt_integrity::KATS`.
454    if integrity.is_empty() {
455        STATE.store(State::IntegrityUnverified as u8, Ordering::Release);
456        return Err(Error::IntegrityNotAttested);
457    }
458
459    for entry in integrity.iter().chain(tests) {
460        if (entry.run)().is_err() {
461            // Latch the module into Error before returning. Any
462            // subsequent service call will be rejected by
463            // require_operational().
464            STATE.store(State::Error as u8, Ordering::Release);
465            return Err(Error::SelfTestFailed { test: entry.name });
466        }
467    }
468
469    // The only place in the workspace where the module becomes
470    // operational. Every entry point reaches it, so the integrity
471    // requirement is enforced once here rather than at the 129
472    // `require_operational` call sites, and no service pays for the
473    // check on its hot path.
474    STATE.store(State::Operational as u8, Ordering::Release);
475    Ok(())
476}
477
478/// Forces the module into the terminal `Error` state.
479///
480/// Called by conditional self-tests (DRBG CRNGT, pairwise consistency
481/// on keygen, etc.) when they detect a failure. Once invoked the
482/// module will reject every subsequent [`require_operational`] call
483/// for the remainder of the process lifetime.
484///
485/// This function is intentionally irreversible: FIPS 140-3 requires
486/// that a module which has entered an error condition stay there
487/// until it is re-initialized, and our "re-initialization" is a
488/// process restart.
489pub fn enter_error_state(_reason: &'static str) {
490    STATE.store(State::Error as u8, Ordering::Release);
491}
492
493// =========================================================================
494// Algorithm profile gating (CNSA 2.0 / CNSA 1.0)
495// =========================================================================
496
497/// Algorithm restriction profile selected at module initialization.
498///
499/// A single binary can serve general FIPS 140-3 consumers
500/// (`Unrestricted`) and CNSA-restricted deployments (`Cnsa2`,
501/// `Cnsa1`) by choosing the appropriate profile at init time.
502///
503/// The profile is immutable once set: it is stored alongside the
504/// module `State` before the first KAT runs and cannot be changed
505/// without restarting the process (which resets the state machine).
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
507#[repr(u8)]
508pub enum AlgorithmProfile {
509    /// All FIPS-approved algorithms are available. This is the
510    /// default and preserves backward compatibility with callers
511    /// that use [`initialize_with_tests`].
512    Unrestricted = 0,
513    /// CNSA 2.0 — CNSSP-15 (March 2025) Annex B: AES-256, ML-KEM-1024,
514    /// ML-DSA-87, SHA-384 or SHA-512, and the SP 800-208 stateful
515    /// hash-based signatures for software and firmware signing, with the
516    /// supporting services those need at matching strength. The suite
517    /// names no Keccak-family algorithm for a software module, so none is
518    /// permitted here. Everything else returns
519    /// [`Error::AlgorithmRestricted`].
520    Cnsa2 = 1,
521    /// CNSA 1.0 — CNSSP-15 (October 2016) Annex B: AES-256, ECDH and
522    /// ECDSA on P-384, SHA-384, and Diffie-Hellman and RSA at a minimum
523    /// 3072-bit modulus, with the supporting services those need at
524    /// matching strength. SHA-256 and its variants are refused: the suite
525    /// admits them only under a near-term condition scoped to UNCLASSIFIED
526    /// data, which a module cannot evaluate.
527    Cnsa1 = 2,
528    /// Both suites at once — **not a CNSA profile and not defined by any
529    /// standard.** This is oxicrypt's own union of CNSA 1.0 and CNSA 2.0,
530    /// for a deployment migrating between them that must run classical and
531    /// post-quantum algorithms side by side.
532    ///
533    /// It is narrower than [`Unrestricted`](Self::Unrestricted), which
534    /// refuses nothing: SHA-1, AES-128, RSA-2048 and P-256 are all refused
535    /// here. It admits no Keccak-family algorithm, because neither suite
536    /// does.
537    Migration = 3,
538}
539
540impl AlgorithmProfile {
541    const fn from_u8(raw: u8) -> Self {
542        match raw {
543            0 => Self::Unrestricted,
544            2 => Self::Cnsa1,
545            3 => Self::Migration,
546            // Unknown discriminant (including 1 = Cnsa2) defaults to
547            // the most restrictive profile as defence-in-depth.
548            _ => Self::Cnsa2,
549        }
550    }
551}
552
553impl fmt::Display for AlgorithmProfile {
554    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555        let s = match self {
556            Self::Unrestricted => "Unrestricted",
557            Self::Cnsa2 => "CNSA 2.0",
558            Self::Cnsa1 => "CNSA 1.0",
559            Self::Migration => "Migration (CNSA 1.0 + CNSA 2.0)",
560        };
561        f.write_str(s)
562    }
563}
564
565/// Global algorithm profile. Written once by [`initialize_with_profile`];
566/// read by [`require_allowed`] and [`active_profile`].
567static PROFILE: AtomicU8 = AtomicU8::new(AlgorithmProfile::Unrestricted as u8);
568
569/// Returns the active algorithm profile.
570pub fn active_profile() -> AlgorithmProfile {
571    AlgorithmProfile::from_u8(PROFILE.load(Ordering::Acquire))
572}
573
574/// Initializes the module with a specific algorithm profile and runs
575/// every supplied power-up KAT.
576///
577/// This is the profile-aware entry point. It stores the selected
578/// profile before running KATs, then transitions to `Operational` on
579/// success. KATs always run all algorithms regardless of the profile —
580/// the profile only restricts post-initialization service access via
581/// [`require_allowed`].
582///
583/// See [`initialize_with_tests`] for the backward-compatible wrapper
584/// that uses [`AlgorithmProfile::Unrestricted`].
585pub fn initialize_with_profile(
586    integrity: &[KatEntry],
587    tests: &[KatEntry],
588    profile: AlgorithmProfile,
589) -> Result<(), Error> {
590    // Store the profile before running KATs. This is safe even if
591    // initialization fails: a failed init latches a terminal state —
592    // State::Error, or State::IntegrityUnverified when the integrity
593    // test was absent — so no service call can reach require_allowed()
594    // anyway.
595    PROFILE.store(profile as u8, Ordering::Release);
596    initialize_with_tests(integrity, tests)
597}
598
599/// Enumeration of every approved service in the module, at the
600/// algorithm-and-parameter level.
601///
602/// Each variant represents a specific algorithm instantiation (e.g.
603/// `Aes128Ecb` not just `Aes`). This granularity is necessary
604/// because CNSA restrictions operate at the key-size and hash-size
605/// level.
606///
607/// Algorithm crates pass the appropriate `Service` variant to
608/// [`require_allowed`] at their public entry points.
609#[derive(Debug, Clone, Copy, PartialEq, Eq)]
610#[repr(u16)]
611#[allow(missing_docs)]
612pub enum Service {
613    // ----- oxicrypt-sha: FIPS 180-4 / FIPS 202 -----
614    Sha1 = 0,
615    Sha224 = 1,
616    Sha256 = 2,
617    Sha384 = 3,
618    Sha512 = 4,
619    Sha512_224 = 5,
620    Sha512_256 = 6,
621
622    Sha3_224 = 10,
623    Sha3_256 = 11,
624    Sha3_384 = 12,
625    Sha3_512 = 13,
626
627    // ----- oxicrypt-xof: FIPS 202 / SP 800-185 -----
628    Shake128 = 20,
629    Shake256 = 21,
630    CShake128 = 22,
631    CShake256 = 23,
632    Kmac128 = 24,
633    Kmac256 = 25,
634    KmacXof128 = 26,
635    KmacXof256 = 27,
636    TupleHash128 = 28,
637    TupleHash256 = 29,
638    TupleHashXof128 = 30,
639    TupleHashXof256 = 31,
640    ParallelHash128 = 32,
641    ParallelHash256 = 33,
642    ParallelHashXof128 = 34,
643    ParallelHashXof256 = 35,
644
645    // ----- oxicrypt-hmac: FIPS 198-1 -----
646    HmacSha1 = 40,
647    HmacSha224 = 41,
648    HmacSha256 = 42,
649    HmacSha384 = 43,
650    HmacSha512 = 44,
651    HmacSha512_224 = 45,
652    HmacSha512_256 = 46,
653    HmacSha3_224 = 47,
654    HmacSha3_256 = 48,
655    HmacSha3_384 = 49,
656    HmacSha3_512 = 50,
657
658    // ----- oxicrypt-cmac: SP 800-38B -----
659    CmacAes128 = 60,
660    CmacAes192 = 61,
661    CmacAes256 = 62,
662
663    // ----- oxicrypt-aes: FIPS 197 / SP 800-38A/D/C/F/Fp -----
664    Aes128Ecb = 70,
665    Aes128Cbc = 71,
666    Aes128Ctr = 72,
667    Aes128Gcm = 73,
668    Aes128Ccm = 74,
669    Aes128Kw = 75,
670    Aes128Kwp = 76,
671    Aes192Ecb = 80,
672    Aes192Cbc = 81,
673    Aes192Ctr = 82,
674    Aes192Gcm = 83,
675    Aes192Ccm = 84,
676    Aes192Kw = 85,
677    Aes192Kwp = 86,
678    Aes256Ecb = 90,
679    Aes256Cbc = 91,
680    Aes256Ctr = 92,
681    Aes256Gcm = 93,
682    Aes256Ccm = 94,
683    Aes256Kw = 95,
684    Aes256Kwp = 96,
685
686    // Key-construction gates — checked when building an AES key
687    // object before the caller selects a mode.  All per-mode
688    // variants of the same key size share identical profile
689    // permissions, so a single gate at key construction is
690    // sufficient.
691    /// AES-128 key construction (gates all AES-128 mode services).
692    Aes128 = 97,
693    /// AES-192 key construction (gates all AES-192 mode services).
694    Aes192 = 98,
695    /// AES-256 key construction (gates all AES-256 mode services).
696    Aes256 = 99,
697
698    // ----- oxicrypt-drbg: SP 800-90A -----
699    CtrDrbgAes128 = 100,
700    CtrDrbgAes192 = 101,
701    CtrDrbgAes256 = 102,
702    HashDrbgSha256 = 103,
703    HashDrbgSha384 = 104,
704    HashDrbgSha512 = 105,
705    HmacDrbgSha256 = 106,
706    HmacDrbgSha384 = 107,
707    HmacDrbgSha512 = 108,
708
709    // ----- oxicrypt-kdf: SP 800-56C / SP 800-108 / SP 800-132 -----
710    HkdfSha1 = 120,
711    HkdfSha256 = 121,
712    HkdfSha384 = 122,
713    HkdfSha512 = 123,
714    KbkdfHmacSha256 = 130,
715    KbkdfHmacSha384 = 131,
716    KbkdfHmacSha512 = 132,
717    KbkdfCmacAes128 = 133,
718    KbkdfCmacAes192 = 134,
719    KbkdfCmacAes256 = 135,
720    Pbkdf2HmacSha1 = 140,
721    Pbkdf2HmacSha224 = 141,
722    Pbkdf2HmacSha256 = 142,
723    Pbkdf2HmacSha384 = 143,
724    Pbkdf2HmacSha512 = 144,
725
726    // ----- oxicrypt-rsa: FIPS 186-5 / SP 800-56Br2 -----
727    RsaKeygen2048 = 150,
728    RsaPkcs1v15Sign2048 = 151,
729    RsaPssSign2048 = 152,
730    RsaOaep2048 = 153,
731    RsaPkcs1v15Verify2048 = 154,
732    RsaPssVerify2048 = 155,
733    RsaKeygen3072 = 160,
734    RsaPkcs1v15Sign3072 = 161,
735    RsaPssSign3072 = 162,
736    RsaOaep3072 = 163,
737    RsaPkcs1v15Verify3072 = 164,
738    RsaPssVerify3072 = 165,
739    RsaKeygen4096 = 170,
740    RsaPkcs1v15Sign4096 = 171,
741    RsaPssSign4096 = 172,
742    RsaOaep4096 = 173,
743    RsaPkcs1v15Verify4096 = 174,
744    RsaPssVerify4096 = 175,
745
746    // ----- oxicrypt-ecdsa: FIPS 186-5 -----
747    EcdsaP256Sign = 200,
748    EcdsaP256Verify = 201,
749    EcdsaP256Keygen = 202,
750    EcdsaP384Sign = 210,
751    EcdsaP384Verify = 211,
752    EcdsaP384Keygen = 212,
753
754    // ----- oxicrypt-ecdh: SP 800-56Ar3 -----
755    EcdhP256 = 220,
756    EcdhP384 = 221,
757
758    // ----- oxicrypt-eddsa: FIPS 186-5 / RFC 8032 -----
759    Ed25519Sign = 230,
760    Ed25519Verify = 231,
761    Ed25519Keygen = 232,
762
763    // ----- oxicrypt-tls-kdf: SP 800-135r1 + RFC 8446 §7.1 -----
764    Tls12Kdf = 240,
765    Tls13Kdf = 241,
766
767    // ----- oxicrypt-ml-kem: FIPS 203 -----
768    MlKem1024Encaps = 300,
769    MlKem1024Decaps = 301,
770    MlKem1024Keygen = 302,
771    MlKem512Encaps = 303,
772    MlKem512Decaps = 304,
773    MlKem512Keygen = 305,
774    MlKem768Encaps = 306,
775    MlKem768Decaps = 307,
776    MlKem768Keygen = 308,
777
778    // ----- oxicrypt-ml-dsa: FIPS 204 -----
779    MlDsa87Sign = 310,
780    MlDsa87Verify = 311,
781    MlDsa87Keygen = 312,
782    MlDsa44Sign = 313,
783    MlDsa44Verify = 314,
784    MlDsa44Keygen = 315,
785    MlDsa65Sign = 316,
786    MlDsa65Verify = 317,
787    MlDsa65Keygen = 318,
788
789    // ----- oxicrypt-slh-dsa: FIPS 205 (12 parameter sets × 3 ops = 36 variants) -----
790    // SHA-2 256s claims 320-322 for historical reasons, then the rest of the
791    // SHA-2 family (128s/f, 192s/f, 256f), then the SHAKE family. No SLH-DSA
792    // parameter set is permitted under either CNSA profile.
793    SlhDsaSha2256sKeygen = 320,
794    SlhDsaSha2256sSign = 321,
795    SlhDsaSha2256sVerify = 322,
796    SlhDsaSha2128sKeygen = 323,
797    SlhDsaSha2128sSign = 324,
798    SlhDsaSha2128sVerify = 325,
799    SlhDsaSha2128fKeygen = 326,
800    SlhDsaSha2128fSign = 327,
801    SlhDsaSha2128fVerify = 328,
802    SlhDsaSha2192sKeygen = 329,
803    SlhDsaSha2192sSign = 330,
804    SlhDsaSha2192sVerify = 331,
805    SlhDsaSha2192fKeygen = 332,
806    SlhDsaSha2192fSign = 333,
807    SlhDsaSha2192fVerify = 334,
808    SlhDsaSha2256fKeygen = 335,
809    SlhDsaSha2256fSign = 336,
810    SlhDsaSha2256fVerify = 337,
811    SlhDsaShake128sKeygen = 338,
812    SlhDsaShake128sSign = 339,
813    SlhDsaShake128sVerify = 340,
814    SlhDsaShake128fKeygen = 341,
815    SlhDsaShake128fSign = 342,
816    SlhDsaShake128fVerify = 343,
817    SlhDsaShake192sKeygen = 344,
818    SlhDsaShake192sSign = 345,
819    SlhDsaShake192sVerify = 346,
820    SlhDsaShake192fKeygen = 347,
821    SlhDsaShake192fSign = 348,
822    SlhDsaShake192fVerify = 349,
823    SlhDsaShake256sKeygen = 350,
824    SlhDsaShake256sSign = 351,
825    SlhDsaShake256sVerify = 352,
826    SlhDsaShake256fKeygen = 353,
827    SlhDsaShake256fSign = 354,
828    SlhDsaShake256fVerify = 355,
829
830    // ----- oxicrypt-lms: SP 800-208 (RFC 8554 / RFC 8708) -----
831    // 160 per-pair entries (80 pairs × Sign + Verify), discriminants 500-659.
832    // Layout: 500-539 SHA-256/M=32, 540-579 SHA-256/M=24,
833    // 580-619 SHAKE/M=32, 620-659 SHAKE/M=24.
834    // Within each family: (H ascending, W ascending), Sign/Verify alternating.
835    // The block is contiguous and holds nothing but LMS, which is what lets
836    // `is_lms_service` range-test it; `lms_discriminant_block_is_contiguous_and_pure`
837    // pins that. All 80 pairs are permitted under every profile.
838
839    // SHA-256 / N=32 family (RFC 8554 §A.1+§A.2)
840    LmsSha256M32H5W1Sign = 500,
841    LmsSha256M32H5W1Verify = 501,
842    LmsSha256M32H5W2Sign = 502,
843    LmsSha256M32H5W2Verify = 503,
844    LmsSha256M32H5W4Sign = 504,
845    LmsSha256M32H5W4Verify = 505,
846    LmsSha256M32H5W8Sign = 506,
847    LmsSha256M32H5W8Verify = 507,
848    LmsSha256M32H10W1Sign = 508,
849    LmsSha256M32H10W1Verify = 509,
850    LmsSha256M32H10W2Sign = 510,
851    LmsSha256M32H10W2Verify = 511,
852    LmsSha256M32H10W4Sign = 512,
853    LmsSha256M32H10W4Verify = 513,
854    LmsSha256M32H10W8Sign = 514,
855    LmsSha256M32H10W8Verify = 515,
856    LmsSha256M32H15W1Sign = 516,
857    LmsSha256M32H15W1Verify = 517,
858    LmsSha256M32H15W2Sign = 518,
859    LmsSha256M32H15W2Verify = 519,
860    LmsSha256M32H15W4Sign = 520,
861    LmsSha256M32H15W4Verify = 521,
862    LmsSha256M32H15W8Sign = 522,
863    LmsSha256M32H15W8Verify = 523,
864    LmsSha256M32H20W1Sign = 524,
865    LmsSha256M32H20W1Verify = 525,
866    LmsSha256M32H20W2Sign = 526,
867    LmsSha256M32H20W2Verify = 527,
868    LmsSha256M32H20W4Sign = 528,
869    LmsSha256M32H20W4Verify = 529,
870    LmsSha256M32H20W8Sign = 530,
871    LmsSha256M32H20W8Verify = 531,
872    LmsSha256M32H25W1Sign = 532,
873    LmsSha256M32H25W1Verify = 533,
874    LmsSha256M32H25W2Sign = 534,
875    LmsSha256M32H25W2Verify = 535,
876    LmsSha256M32H25W4Sign = 536,
877    LmsSha256M32H25W4Verify = 537,
878    LmsSha256M32H25W8Sign = 538,
879    LmsSha256M32H25W8Verify = 539,
880
881    // SHA-256 / N=24 family (RFC 8708 §4.1)
882    LmsSha256M24H5W1Sign = 540,
883    LmsSha256M24H5W1Verify = 541,
884    LmsSha256M24H5W2Sign = 542,
885    LmsSha256M24H5W2Verify = 543,
886    LmsSha256M24H5W4Sign = 544,
887    LmsSha256M24H5W4Verify = 545,
888    LmsSha256M24H5W8Sign = 546,
889    LmsSha256M24H5W8Verify = 547,
890    LmsSha256M24H10W1Sign = 548,
891    LmsSha256M24H10W1Verify = 549,
892    LmsSha256M24H10W2Sign = 550,
893    LmsSha256M24H10W2Verify = 551,
894    LmsSha256M24H10W4Sign = 552,
895    LmsSha256M24H10W4Verify = 553,
896    LmsSha256M24H10W8Sign = 554,
897    LmsSha256M24H10W8Verify = 555,
898    LmsSha256M24H15W1Sign = 556,
899    LmsSha256M24H15W1Verify = 557,
900    LmsSha256M24H15W2Sign = 558,
901    LmsSha256M24H15W2Verify = 559,
902    LmsSha256M24H15W4Sign = 560,
903    LmsSha256M24H15W4Verify = 561,
904    LmsSha256M24H15W8Sign = 562,
905    LmsSha256M24H15W8Verify = 563,
906    LmsSha256M24H20W1Sign = 564,
907    LmsSha256M24H20W1Verify = 565,
908    LmsSha256M24H20W2Sign = 566,
909    LmsSha256M24H20W2Verify = 567,
910    LmsSha256M24H20W4Sign = 568,
911    LmsSha256M24H20W4Verify = 569,
912    LmsSha256M24H20W8Sign = 570,
913    LmsSha256M24H20W8Verify = 571,
914    LmsSha256M24H25W1Sign = 572,
915    LmsSha256M24H25W1Verify = 573,
916    LmsSha256M24H25W2Sign = 574,
917    LmsSha256M24H25W2Verify = 575,
918    LmsSha256M24H25W4Sign = 576,
919    LmsSha256M24H25W4Verify = 577,
920    LmsSha256M24H25W8Sign = 578,
921    LmsSha256M24H25W8Verify = 579,
922
923    // SHAKE-256 / N=32 family (RFC 8708 §3.1)
924    LmsShakeM32H5W1Sign = 580,
925    LmsShakeM32H5W1Verify = 581,
926    LmsShakeM32H5W2Sign = 582,
927    LmsShakeM32H5W2Verify = 583,
928    LmsShakeM32H5W4Sign = 584,
929    LmsShakeM32H5W4Verify = 585,
930    LmsShakeM32H5W8Sign = 586,
931    LmsShakeM32H5W8Verify = 587,
932    LmsShakeM32H10W1Sign = 588,
933    LmsShakeM32H10W1Verify = 589,
934    LmsShakeM32H10W2Sign = 590,
935    LmsShakeM32H10W2Verify = 591,
936    LmsShakeM32H10W4Sign = 592,
937    LmsShakeM32H10W4Verify = 593,
938    LmsShakeM32H10W8Sign = 594,
939    LmsShakeM32H10W8Verify = 595,
940    LmsShakeM32H15W1Sign = 596,
941    LmsShakeM32H15W1Verify = 597,
942    LmsShakeM32H15W2Sign = 598,
943    LmsShakeM32H15W2Verify = 599,
944    LmsShakeM32H15W4Sign = 600,
945    LmsShakeM32H15W4Verify = 601,
946    LmsShakeM32H15W8Sign = 602,
947    LmsShakeM32H15W8Verify = 603,
948    LmsShakeM32H20W1Sign = 604,
949    LmsShakeM32H20W1Verify = 605,
950    LmsShakeM32H20W2Sign = 606,
951    LmsShakeM32H20W2Verify = 607,
952    LmsShakeM32H20W4Sign = 608,
953    LmsShakeM32H20W4Verify = 609,
954    LmsShakeM32H20W8Sign = 610,
955    LmsShakeM32H20W8Verify = 611,
956    LmsShakeM32H25W1Sign = 612,
957    LmsShakeM32H25W1Verify = 613,
958    LmsShakeM32H25W2Sign = 614,
959    LmsShakeM32H25W2Verify = 615,
960    LmsShakeM32H25W4Sign = 616,
961    LmsShakeM32H25W4Verify = 617,
962    LmsShakeM32H25W8Sign = 618,
963    LmsShakeM32H25W8Verify = 619,
964
965    // SHAKE-256 / N=24 family (RFC 8708 §4.2)
966    LmsShakeM24H5W1Sign = 620,
967    LmsShakeM24H5W1Verify = 621,
968    LmsShakeM24H5W2Sign = 622,
969    LmsShakeM24H5W2Verify = 623,
970    LmsShakeM24H5W4Sign = 624,
971    LmsShakeM24H5W4Verify = 625,
972    LmsShakeM24H5W8Sign = 626,
973    LmsShakeM24H5W8Verify = 627,
974    LmsShakeM24H10W1Sign = 628,
975    LmsShakeM24H10W1Verify = 629,
976    LmsShakeM24H10W2Sign = 630,
977    LmsShakeM24H10W2Verify = 631,
978    LmsShakeM24H10W4Sign = 632,
979    LmsShakeM24H10W4Verify = 633,
980    LmsShakeM24H10W8Sign = 634,
981    LmsShakeM24H10W8Verify = 635,
982    LmsShakeM24H15W1Sign = 636,
983    LmsShakeM24H15W1Verify = 637,
984    LmsShakeM24H15W2Sign = 638,
985    LmsShakeM24H15W2Verify = 639,
986    LmsShakeM24H15W4Sign = 640,
987    LmsShakeM24H15W4Verify = 641,
988    LmsShakeM24H15W8Sign = 642,
989    LmsShakeM24H15W8Verify = 643,
990    LmsShakeM24H20W1Sign = 644,
991    LmsShakeM24H20W1Verify = 645,
992    LmsShakeM24H20W2Sign = 646,
993    LmsShakeM24H20W2Verify = 647,
994    LmsShakeM24H20W4Sign = 648,
995    LmsShakeM24H20W4Verify = 649,
996    LmsShakeM24H20W8Sign = 650,
997    LmsShakeM24H20W8Verify = 651,
998    LmsShakeM24H25W1Sign = 652,
999    LmsShakeM24H25W1Verify = 653,
1000    LmsShakeM24H25W2Sign = 654,
1001    LmsShakeM24H25W2Verify = 655,
1002    LmsShakeM24H25W4Sign = 656,
1003    LmsShakeM24H25W4Verify = 657,
1004    LmsShakeM24H25W8Sign = 658,
1005    LmsShakeM24H25W8Verify = 659,
1006
1007    // ----- oxicrypt-xmss: SP 800-208 (stub) -----
1008    // Renumbered 340/341 → 370/371 in Batch 4 to make room for the SLH-DSA block.
1009    XmssVerify = 371,
1010
1011    // ----- oxicrypt-dh: RFC 3526 (stub) -----
1012    // Renumbered 350 → 380 in Batch 4 to make room for the SLH-DSA block.
1013    Dh3072 = 380,
1014}
1015
1016impl fmt::Display for Service {
1017    // The match arm count is proportional to the number of approved
1018    // services in the module; there is no natural factoring that
1019    // would reduce it.
1020    #[allow(clippy::too_many_lines)]
1021    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1022        let name = match self {
1023            Self::Sha1 => "SHA-1",
1024            Self::Sha224 => "SHA-224",
1025            Self::Sha256 => "SHA-256",
1026            Self::Sha384 => "SHA-384",
1027            Self::Sha512 => "SHA-512",
1028            Self::Sha512_224 => "SHA-512/224",
1029            Self::Sha512_256 => "SHA-512/256",
1030            Self::Sha3_224 => "SHA3-224",
1031            Self::Sha3_256 => "SHA3-256",
1032            Self::Sha3_384 => "SHA3-384",
1033            Self::Sha3_512 => "SHA3-512",
1034            Self::Shake128 => "SHAKE128",
1035            Self::Shake256 => "SHAKE256",
1036            Self::CShake128 => "cSHAKE128",
1037            Self::CShake256 => "cSHAKE256",
1038            Self::Kmac128 => "KMAC128",
1039            Self::Kmac256 => "KMAC256",
1040            Self::KmacXof128 => "KMACXOF128",
1041            Self::KmacXof256 => "KMACXOF256",
1042            Self::TupleHash128 => "TupleHash128",
1043            Self::TupleHash256 => "TupleHash256",
1044            Self::TupleHashXof128 => "TupleHashXOF128",
1045            Self::TupleHashXof256 => "TupleHashXOF256",
1046            Self::ParallelHash128 => "ParallelHash128",
1047            Self::ParallelHash256 => "ParallelHash256",
1048            Self::ParallelHashXof128 => "ParallelHashXOF128",
1049            Self::ParallelHashXof256 => "ParallelHashXOF256",
1050            Self::HmacSha1 => "HMAC-SHA-1",
1051            Self::HmacSha224 => "HMAC-SHA-224",
1052            Self::HmacSha256 => "HMAC-SHA-256",
1053            Self::HmacSha384 => "HMAC-SHA-384",
1054            Self::HmacSha512 => "HMAC-SHA-512",
1055            Self::HmacSha512_224 => "HMAC-SHA-512/224",
1056            Self::HmacSha512_256 => "HMAC-SHA-512/256",
1057            Self::HmacSha3_224 => "HMAC-SHA3-224",
1058            Self::HmacSha3_256 => "HMAC-SHA3-256",
1059            Self::HmacSha3_384 => "HMAC-SHA3-384",
1060            Self::HmacSha3_512 => "HMAC-SHA3-512",
1061            Self::CmacAes128 => "CMAC-AES-128",
1062            Self::CmacAes192 => "CMAC-AES-192",
1063            Self::CmacAes256 => "CMAC-AES-256",
1064            Self::Aes128Ecb => "AES-128-ECB",
1065            Self::Aes128Cbc => "AES-128-CBC",
1066            Self::Aes128Ctr => "AES-128-CTR",
1067            Self::Aes128Gcm => "AES-128-GCM",
1068            Self::Aes128Ccm => "AES-128-CCM",
1069            Self::Aes128Kw => "AES-128-KW",
1070            Self::Aes128Kwp => "AES-128-KWP",
1071            Self::Aes192Ecb => "AES-192-ECB",
1072            Self::Aes192Cbc => "AES-192-CBC",
1073            Self::Aes192Ctr => "AES-192-CTR",
1074            Self::Aes192Gcm => "AES-192-GCM",
1075            Self::Aes192Ccm => "AES-192-CCM",
1076            Self::Aes192Kw => "AES-192-KW",
1077            Self::Aes192Kwp => "AES-192-KWP",
1078            Self::Aes256Ecb => "AES-256-ECB",
1079            Self::Aes256Cbc => "AES-256-CBC",
1080            Self::Aes256Ctr => "AES-256-CTR",
1081            Self::Aes256Gcm => "AES-256-GCM",
1082            Self::Aes256Ccm => "AES-256-CCM",
1083            Self::Aes256Kw => "AES-256-KW",
1084            Self::Aes256Kwp => "AES-256-KWP",
1085            Self::Aes128 => "AES-128",
1086            Self::Aes192 => "AES-192",
1087            Self::Aes256 => "AES-256",
1088            Self::CtrDrbgAes128 => "CTR_DRBG-AES-128",
1089            Self::CtrDrbgAes192 => "CTR_DRBG-AES-192",
1090            Self::CtrDrbgAes256 => "CTR_DRBG-AES-256",
1091            Self::HashDrbgSha256 => "Hash_DRBG-SHA-256",
1092            Self::HashDrbgSha384 => "Hash_DRBG-SHA-384",
1093            Self::HashDrbgSha512 => "Hash_DRBG-SHA-512",
1094            Self::HmacDrbgSha256 => "HMAC_DRBG-SHA-256",
1095            Self::HmacDrbgSha384 => "HMAC_DRBG-SHA-384",
1096            Self::HmacDrbgSha512 => "HMAC_DRBG-SHA-512",
1097            Self::HkdfSha1 => "HKDF-SHA-1",
1098            Self::HkdfSha256 => "HKDF-SHA-256",
1099            Self::HkdfSha384 => "HKDF-SHA-384",
1100            Self::HkdfSha512 => "HKDF-SHA-512",
1101            Self::KbkdfHmacSha256 => "KBKDF-HMAC-SHA-256",
1102            Self::KbkdfHmacSha384 => "KBKDF-HMAC-SHA-384",
1103            Self::KbkdfHmacSha512 => "KBKDF-HMAC-SHA-512",
1104            Self::KbkdfCmacAes128 => "KBKDF-CMAC-AES-128",
1105            Self::KbkdfCmacAes192 => "KBKDF-CMAC-AES-192",
1106            Self::KbkdfCmacAes256 => "KBKDF-CMAC-AES-256",
1107            Self::Pbkdf2HmacSha1 => "PBKDF2-HMAC-SHA-1",
1108            Self::Pbkdf2HmacSha224 => "PBKDF2-HMAC-SHA-224",
1109            Self::Pbkdf2HmacSha256 => "PBKDF2-HMAC-SHA-256",
1110            Self::Pbkdf2HmacSha384 => "PBKDF2-HMAC-SHA-384",
1111            Self::Pbkdf2HmacSha512 => "PBKDF2-HMAC-SHA-512",
1112            Self::RsaKeygen2048 => "RSA-2048 keygen",
1113            Self::RsaPkcs1v15Sign2048 => "RSA-PKCS1v1.5-Sign-2048",
1114            Self::RsaPssSign2048 => "RSA-PSS-Sign-2048",
1115            Self::RsaOaep2048 => "RSA-OAEP-2048",
1116            Self::RsaPkcs1v15Verify2048 => "RSA-PKCS1v1.5-Verify-2048",
1117            Self::RsaPssVerify2048 => "RSA-PSS-Verify-2048",
1118            Self::RsaKeygen3072 => "RSA-3072 keygen",
1119            Self::RsaPkcs1v15Sign3072 => "RSA-PKCS1v1.5-Sign-3072",
1120            Self::RsaPssSign3072 => "RSA-PSS-Sign-3072",
1121            Self::RsaOaep3072 => "RSA-OAEP-3072",
1122            Self::RsaPkcs1v15Verify3072 => "RSA-PKCS1v1.5-Verify-3072",
1123            Self::RsaPssVerify3072 => "RSA-PSS-Verify-3072",
1124            Self::RsaKeygen4096 => "RSA-4096 keygen",
1125            Self::RsaPkcs1v15Sign4096 => "RSA-PKCS1v1.5-Sign-4096",
1126            Self::RsaPssSign4096 => "RSA-PSS-Sign-4096",
1127            Self::RsaOaep4096 => "RSA-OAEP-4096",
1128            Self::RsaPkcs1v15Verify4096 => "RSA-PKCS1v1.5-Verify-4096",
1129            Self::RsaPssVerify4096 => "RSA-PSS-Verify-4096",
1130            Self::EcdsaP256Sign => "ECDSA-P256 sign",
1131            Self::EcdsaP256Verify => "ECDSA-P256 verify",
1132            Self::EcdsaP256Keygen => "ECDSA-P256 keygen",
1133            Self::EcdsaP384Sign => "ECDSA-P384 sign",
1134            Self::EcdsaP384Verify => "ECDSA-P384 verify",
1135            Self::EcdsaP384Keygen => "ECDSA-P384 keygen",
1136            Self::EcdhP256 => "ECDH-P256",
1137            Self::EcdhP384 => "ECDH-P384",
1138            Self::Ed25519Sign => "Ed25519 sign",
1139            Self::Ed25519Verify => "Ed25519 verify",
1140            Self::Ed25519Keygen => "Ed25519 keygen",
1141            Self::Tls12Kdf => "TLS 1.2 KDF",
1142            Self::Tls13Kdf => "TLS 1.3 KDF",
1143            Self::MlKem1024Encaps => "ML-KEM-1024 encaps",
1144            Self::MlKem1024Decaps => "ML-KEM-1024 decaps",
1145            Self::MlKem1024Keygen => "ML-KEM-1024 keygen",
1146            Self::MlKem512Encaps => "ML-KEM-512 encaps",
1147            Self::MlKem512Decaps => "ML-KEM-512 decaps",
1148            Self::MlKem512Keygen => "ML-KEM-512 keygen",
1149            Self::MlKem768Encaps => "ML-KEM-768 encaps",
1150            Self::MlKem768Decaps => "ML-KEM-768 decaps",
1151            Self::MlKem768Keygen => "ML-KEM-768 keygen",
1152            Self::MlDsa87Sign => "ML-DSA-87 sign",
1153            Self::MlDsa87Verify => "ML-DSA-87 verify",
1154            Self::MlDsa87Keygen => "ML-DSA-87 keygen",
1155            Self::MlDsa44Sign => "ML-DSA-44 sign",
1156            Self::MlDsa44Verify => "ML-DSA-44 verify",
1157            Self::MlDsa44Keygen => "ML-DSA-44 keygen",
1158            Self::MlDsa65Sign => "ML-DSA-65 sign",
1159            Self::MlDsa65Verify => "ML-DSA-65 verify",
1160            Self::MlDsa65Keygen => "ML-DSA-65 keygen",
1161            Self::SlhDsaSha2256sKeygen => "SLH-DSA-SHA2-256s keygen",
1162            Self::SlhDsaSha2256sSign => "SLH-DSA-SHA2-256s sign",
1163            Self::SlhDsaSha2256sVerify => "SLH-DSA-SHA2-256s verify",
1164            Self::SlhDsaSha2128sKeygen => "SLH-DSA-SHA2-128s keygen",
1165            Self::SlhDsaSha2128sSign => "SLH-DSA-SHA2-128s sign",
1166            Self::SlhDsaSha2128sVerify => "SLH-DSA-SHA2-128s verify",
1167            Self::SlhDsaSha2128fKeygen => "SLH-DSA-SHA2-128f keygen",
1168            Self::SlhDsaSha2128fSign => "SLH-DSA-SHA2-128f sign",
1169            Self::SlhDsaSha2128fVerify => "SLH-DSA-SHA2-128f verify",
1170            Self::SlhDsaSha2192sKeygen => "SLH-DSA-SHA2-192s keygen",
1171            Self::SlhDsaSha2192sSign => "SLH-DSA-SHA2-192s sign",
1172            Self::SlhDsaSha2192sVerify => "SLH-DSA-SHA2-192s verify",
1173            Self::SlhDsaSha2192fKeygen => "SLH-DSA-SHA2-192f keygen",
1174            Self::SlhDsaSha2192fSign => "SLH-DSA-SHA2-192f sign",
1175            Self::SlhDsaSha2192fVerify => "SLH-DSA-SHA2-192f verify",
1176            Self::SlhDsaSha2256fKeygen => "SLH-DSA-SHA2-256f keygen",
1177            Self::SlhDsaSha2256fSign => "SLH-DSA-SHA2-256f sign",
1178            Self::SlhDsaSha2256fVerify => "SLH-DSA-SHA2-256f verify",
1179            Self::SlhDsaShake128sKeygen => "SLH-DSA-SHAKE-128s keygen",
1180            Self::SlhDsaShake128sSign => "SLH-DSA-SHAKE-128s sign",
1181            Self::SlhDsaShake128sVerify => "SLH-DSA-SHAKE-128s verify",
1182            Self::SlhDsaShake128fKeygen => "SLH-DSA-SHAKE-128f keygen",
1183            Self::SlhDsaShake128fSign => "SLH-DSA-SHAKE-128f sign",
1184            Self::SlhDsaShake128fVerify => "SLH-DSA-SHAKE-128f verify",
1185            Self::SlhDsaShake192sKeygen => "SLH-DSA-SHAKE-192s keygen",
1186            Self::SlhDsaShake192sSign => "SLH-DSA-SHAKE-192s sign",
1187            Self::SlhDsaShake192sVerify => "SLH-DSA-SHAKE-192s verify",
1188            Self::SlhDsaShake192fKeygen => "SLH-DSA-SHAKE-192f keygen",
1189            Self::SlhDsaShake192fSign => "SLH-DSA-SHAKE-192f sign",
1190            Self::SlhDsaShake192fVerify => "SLH-DSA-SHAKE-192f verify",
1191            Self::SlhDsaShake256sKeygen => "SLH-DSA-SHAKE-256s keygen",
1192            Self::SlhDsaShake256sSign => "SLH-DSA-SHAKE-256s sign",
1193            Self::SlhDsaShake256sVerify => "SLH-DSA-SHAKE-256s verify",
1194            Self::SlhDsaShake256fKeygen => "SLH-DSA-SHAKE-256f keygen",
1195            Self::SlhDsaShake256fSign => "SLH-DSA-SHAKE-256f sign",
1196            Self::SlhDsaShake256fVerify => "SLH-DSA-SHAKE-256f verify",
1197            Self::LmsSha256M32H5W1Sign => "LMS SHA-256 M=32 H=5 W=1 sign",
1198            Self::LmsSha256M32H5W1Verify => "LMS SHA-256 M=32 H=5 W=1 verify",
1199            Self::LmsSha256M32H5W2Sign => "LMS SHA-256 M=32 H=5 W=2 sign",
1200            Self::LmsSha256M32H5W2Verify => "LMS SHA-256 M=32 H=5 W=2 verify",
1201            Self::LmsSha256M32H5W4Sign => "LMS SHA-256 M=32 H=5 W=4 sign",
1202            Self::LmsSha256M32H5W4Verify => "LMS SHA-256 M=32 H=5 W=4 verify",
1203            Self::LmsSha256M32H5W8Sign => "LMS SHA-256 M=32 H=5 W=8 sign",
1204            Self::LmsSha256M32H5W8Verify => "LMS SHA-256 M=32 H=5 W=8 verify",
1205            Self::LmsSha256M32H10W1Sign => "LMS SHA-256 M=32 H=10 W=1 sign",
1206            Self::LmsSha256M32H10W1Verify => "LMS SHA-256 M=32 H=10 W=1 verify",
1207            Self::LmsSha256M32H10W2Sign => "LMS SHA-256 M=32 H=10 W=2 sign",
1208            Self::LmsSha256M32H10W2Verify => "LMS SHA-256 M=32 H=10 W=2 verify",
1209            Self::LmsSha256M32H10W4Sign => "LMS SHA-256 M=32 H=10 W=4 sign",
1210            Self::LmsSha256M32H10W4Verify => "LMS SHA-256 M=32 H=10 W=4 verify",
1211            Self::LmsSha256M32H10W8Sign => "LMS SHA-256 M=32 H=10 W=8 sign",
1212            Self::LmsSha256M32H10W8Verify => "LMS SHA-256 M=32 H=10 W=8 verify",
1213            Self::LmsSha256M32H15W1Sign => "LMS SHA-256 M=32 H=15 W=1 sign",
1214            Self::LmsSha256M32H15W1Verify => "LMS SHA-256 M=32 H=15 W=1 verify",
1215            Self::LmsSha256M32H15W2Sign => "LMS SHA-256 M=32 H=15 W=2 sign",
1216            Self::LmsSha256M32H15W2Verify => "LMS SHA-256 M=32 H=15 W=2 verify",
1217            Self::LmsSha256M32H15W4Sign => "LMS SHA-256 M=32 H=15 W=4 sign",
1218            Self::LmsSha256M32H15W4Verify => "LMS SHA-256 M=32 H=15 W=4 verify",
1219            Self::LmsSha256M32H15W8Sign => "LMS SHA-256 M=32 H=15 W=8 sign",
1220            Self::LmsSha256M32H15W8Verify => "LMS SHA-256 M=32 H=15 W=8 verify",
1221            Self::LmsSha256M32H20W1Sign => "LMS SHA-256 M=32 H=20 W=1 sign",
1222            Self::LmsSha256M32H20W1Verify => "LMS SHA-256 M=32 H=20 W=1 verify",
1223            Self::LmsSha256M32H20W2Sign => "LMS SHA-256 M=32 H=20 W=2 sign",
1224            Self::LmsSha256M32H20W2Verify => "LMS SHA-256 M=32 H=20 W=2 verify",
1225            Self::LmsSha256M32H20W4Sign => "LMS SHA-256 M=32 H=20 W=4 sign",
1226            Self::LmsSha256M32H20W4Verify => "LMS SHA-256 M=32 H=20 W=4 verify",
1227            Self::LmsSha256M32H20W8Sign => "LMS SHA-256 M=32 H=20 W=8 sign",
1228            Self::LmsSha256M32H20W8Verify => "LMS SHA-256 M=32 H=20 W=8 verify",
1229            Self::LmsSha256M32H25W1Sign => "LMS SHA-256 M=32 H=25 W=1 sign",
1230            Self::LmsSha256M32H25W1Verify => "LMS SHA-256 M=32 H=25 W=1 verify",
1231            Self::LmsSha256M32H25W2Sign => "LMS SHA-256 M=32 H=25 W=2 sign",
1232            Self::LmsSha256M32H25W2Verify => "LMS SHA-256 M=32 H=25 W=2 verify",
1233            Self::LmsSha256M32H25W4Sign => "LMS SHA-256 M=32 H=25 W=4 sign",
1234            Self::LmsSha256M32H25W4Verify => "LMS SHA-256 M=32 H=25 W=4 verify",
1235            Self::LmsSha256M32H25W8Sign => "LMS SHA-256 M=32 H=25 W=8 sign",
1236            Self::LmsSha256M32H25W8Verify => "LMS SHA-256 M=32 H=25 W=8 verify",
1237            Self::LmsSha256M24H5W1Sign => "LMS SHA-256 M=24 H=5 W=1 sign",
1238            Self::LmsSha256M24H5W1Verify => "LMS SHA-256 M=24 H=5 W=1 verify",
1239            Self::LmsSha256M24H5W2Sign => "LMS SHA-256 M=24 H=5 W=2 sign",
1240            Self::LmsSha256M24H5W2Verify => "LMS SHA-256 M=24 H=5 W=2 verify",
1241            Self::LmsSha256M24H5W4Sign => "LMS SHA-256 M=24 H=5 W=4 sign",
1242            Self::LmsSha256M24H5W4Verify => "LMS SHA-256 M=24 H=5 W=4 verify",
1243            Self::LmsSha256M24H5W8Sign => "LMS SHA-256 M=24 H=5 W=8 sign",
1244            Self::LmsSha256M24H5W8Verify => "LMS SHA-256 M=24 H=5 W=8 verify",
1245            Self::LmsSha256M24H10W1Sign => "LMS SHA-256 M=24 H=10 W=1 sign",
1246            Self::LmsSha256M24H10W1Verify => "LMS SHA-256 M=24 H=10 W=1 verify",
1247            Self::LmsSha256M24H10W2Sign => "LMS SHA-256 M=24 H=10 W=2 sign",
1248            Self::LmsSha256M24H10W2Verify => "LMS SHA-256 M=24 H=10 W=2 verify",
1249            Self::LmsSha256M24H10W4Sign => "LMS SHA-256 M=24 H=10 W=4 sign",
1250            Self::LmsSha256M24H10W4Verify => "LMS SHA-256 M=24 H=10 W=4 verify",
1251            Self::LmsSha256M24H10W8Sign => "LMS SHA-256 M=24 H=10 W=8 sign",
1252            Self::LmsSha256M24H10W8Verify => "LMS SHA-256 M=24 H=10 W=8 verify",
1253            Self::LmsSha256M24H15W1Sign => "LMS SHA-256 M=24 H=15 W=1 sign",
1254            Self::LmsSha256M24H15W1Verify => "LMS SHA-256 M=24 H=15 W=1 verify",
1255            Self::LmsSha256M24H15W2Sign => "LMS SHA-256 M=24 H=15 W=2 sign",
1256            Self::LmsSha256M24H15W2Verify => "LMS SHA-256 M=24 H=15 W=2 verify",
1257            Self::LmsSha256M24H15W4Sign => "LMS SHA-256 M=24 H=15 W=4 sign",
1258            Self::LmsSha256M24H15W4Verify => "LMS SHA-256 M=24 H=15 W=4 verify",
1259            Self::LmsSha256M24H15W8Sign => "LMS SHA-256 M=24 H=15 W=8 sign",
1260            Self::LmsSha256M24H15W8Verify => "LMS SHA-256 M=24 H=15 W=8 verify",
1261            Self::LmsSha256M24H20W1Sign => "LMS SHA-256 M=24 H=20 W=1 sign",
1262            Self::LmsSha256M24H20W1Verify => "LMS SHA-256 M=24 H=20 W=1 verify",
1263            Self::LmsSha256M24H20W2Sign => "LMS SHA-256 M=24 H=20 W=2 sign",
1264            Self::LmsSha256M24H20W2Verify => "LMS SHA-256 M=24 H=20 W=2 verify",
1265            Self::LmsSha256M24H20W4Sign => "LMS SHA-256 M=24 H=20 W=4 sign",
1266            Self::LmsSha256M24H20W4Verify => "LMS SHA-256 M=24 H=20 W=4 verify",
1267            Self::LmsSha256M24H20W8Sign => "LMS SHA-256 M=24 H=20 W=8 sign",
1268            Self::LmsSha256M24H20W8Verify => "LMS SHA-256 M=24 H=20 W=8 verify",
1269            Self::LmsSha256M24H25W1Sign => "LMS SHA-256 M=24 H=25 W=1 sign",
1270            Self::LmsSha256M24H25W1Verify => "LMS SHA-256 M=24 H=25 W=1 verify",
1271            Self::LmsSha256M24H25W2Sign => "LMS SHA-256 M=24 H=25 W=2 sign",
1272            Self::LmsSha256M24H25W2Verify => "LMS SHA-256 M=24 H=25 W=2 verify",
1273            Self::LmsSha256M24H25W4Sign => "LMS SHA-256 M=24 H=25 W=4 sign",
1274            Self::LmsSha256M24H25W4Verify => "LMS SHA-256 M=24 H=25 W=4 verify",
1275            Self::LmsSha256M24H25W8Sign => "LMS SHA-256 M=24 H=25 W=8 sign",
1276            Self::LmsSha256M24H25W8Verify => "LMS SHA-256 M=24 H=25 W=8 verify",
1277            Self::LmsShakeM32H5W1Sign => "LMS SHAKE M=32 H=5 W=1 sign",
1278            Self::LmsShakeM32H5W1Verify => "LMS SHAKE M=32 H=5 W=1 verify",
1279            Self::LmsShakeM32H5W2Sign => "LMS SHAKE M=32 H=5 W=2 sign",
1280            Self::LmsShakeM32H5W2Verify => "LMS SHAKE M=32 H=5 W=2 verify",
1281            Self::LmsShakeM32H5W4Sign => "LMS SHAKE M=32 H=5 W=4 sign",
1282            Self::LmsShakeM32H5W4Verify => "LMS SHAKE M=32 H=5 W=4 verify",
1283            Self::LmsShakeM32H5W8Sign => "LMS SHAKE M=32 H=5 W=8 sign",
1284            Self::LmsShakeM32H5W8Verify => "LMS SHAKE M=32 H=5 W=8 verify",
1285            Self::LmsShakeM32H10W1Sign => "LMS SHAKE M=32 H=10 W=1 sign",
1286            Self::LmsShakeM32H10W1Verify => "LMS SHAKE M=32 H=10 W=1 verify",
1287            Self::LmsShakeM32H10W2Sign => "LMS SHAKE M=32 H=10 W=2 sign",
1288            Self::LmsShakeM32H10W2Verify => "LMS SHAKE M=32 H=10 W=2 verify",
1289            Self::LmsShakeM32H10W4Sign => "LMS SHAKE M=32 H=10 W=4 sign",
1290            Self::LmsShakeM32H10W4Verify => "LMS SHAKE M=32 H=10 W=4 verify",
1291            Self::LmsShakeM32H10W8Sign => "LMS SHAKE M=32 H=10 W=8 sign",
1292            Self::LmsShakeM32H10W8Verify => "LMS SHAKE M=32 H=10 W=8 verify",
1293            Self::LmsShakeM32H15W1Sign => "LMS SHAKE M=32 H=15 W=1 sign",
1294            Self::LmsShakeM32H15W1Verify => "LMS SHAKE M=32 H=15 W=1 verify",
1295            Self::LmsShakeM32H15W2Sign => "LMS SHAKE M=32 H=15 W=2 sign",
1296            Self::LmsShakeM32H15W2Verify => "LMS SHAKE M=32 H=15 W=2 verify",
1297            Self::LmsShakeM32H15W4Sign => "LMS SHAKE M=32 H=15 W=4 sign",
1298            Self::LmsShakeM32H15W4Verify => "LMS SHAKE M=32 H=15 W=4 verify",
1299            Self::LmsShakeM32H15W8Sign => "LMS SHAKE M=32 H=15 W=8 sign",
1300            Self::LmsShakeM32H15W8Verify => "LMS SHAKE M=32 H=15 W=8 verify",
1301            Self::LmsShakeM32H20W1Sign => "LMS SHAKE M=32 H=20 W=1 sign",
1302            Self::LmsShakeM32H20W1Verify => "LMS SHAKE M=32 H=20 W=1 verify",
1303            Self::LmsShakeM32H20W2Sign => "LMS SHAKE M=32 H=20 W=2 sign",
1304            Self::LmsShakeM32H20W2Verify => "LMS SHAKE M=32 H=20 W=2 verify",
1305            Self::LmsShakeM32H20W4Sign => "LMS SHAKE M=32 H=20 W=4 sign",
1306            Self::LmsShakeM32H20W4Verify => "LMS SHAKE M=32 H=20 W=4 verify",
1307            Self::LmsShakeM32H20W8Sign => "LMS SHAKE M=32 H=20 W=8 sign",
1308            Self::LmsShakeM32H20W8Verify => "LMS SHAKE M=32 H=20 W=8 verify",
1309            Self::LmsShakeM32H25W1Sign => "LMS SHAKE M=32 H=25 W=1 sign",
1310            Self::LmsShakeM32H25W1Verify => "LMS SHAKE M=32 H=25 W=1 verify",
1311            Self::LmsShakeM32H25W2Sign => "LMS SHAKE M=32 H=25 W=2 sign",
1312            Self::LmsShakeM32H25W2Verify => "LMS SHAKE M=32 H=25 W=2 verify",
1313            Self::LmsShakeM32H25W4Sign => "LMS SHAKE M=32 H=25 W=4 sign",
1314            Self::LmsShakeM32H25W4Verify => "LMS SHAKE M=32 H=25 W=4 verify",
1315            Self::LmsShakeM32H25W8Sign => "LMS SHAKE M=32 H=25 W=8 sign",
1316            Self::LmsShakeM32H25W8Verify => "LMS SHAKE M=32 H=25 W=8 verify",
1317            Self::LmsShakeM24H5W1Sign => "LMS SHAKE M=24 H=5 W=1 sign",
1318            Self::LmsShakeM24H5W1Verify => "LMS SHAKE M=24 H=5 W=1 verify",
1319            Self::LmsShakeM24H5W2Sign => "LMS SHAKE M=24 H=5 W=2 sign",
1320            Self::LmsShakeM24H5W2Verify => "LMS SHAKE M=24 H=5 W=2 verify",
1321            Self::LmsShakeM24H5W4Sign => "LMS SHAKE M=24 H=5 W=4 sign",
1322            Self::LmsShakeM24H5W4Verify => "LMS SHAKE M=24 H=5 W=4 verify",
1323            Self::LmsShakeM24H5W8Sign => "LMS SHAKE M=24 H=5 W=8 sign",
1324            Self::LmsShakeM24H5W8Verify => "LMS SHAKE M=24 H=5 W=8 verify",
1325            Self::LmsShakeM24H10W1Sign => "LMS SHAKE M=24 H=10 W=1 sign",
1326            Self::LmsShakeM24H10W1Verify => "LMS SHAKE M=24 H=10 W=1 verify",
1327            Self::LmsShakeM24H10W2Sign => "LMS SHAKE M=24 H=10 W=2 sign",
1328            Self::LmsShakeM24H10W2Verify => "LMS SHAKE M=24 H=10 W=2 verify",
1329            Self::LmsShakeM24H10W4Sign => "LMS SHAKE M=24 H=10 W=4 sign",
1330            Self::LmsShakeM24H10W4Verify => "LMS SHAKE M=24 H=10 W=4 verify",
1331            Self::LmsShakeM24H10W8Sign => "LMS SHAKE M=24 H=10 W=8 sign",
1332            Self::LmsShakeM24H10W8Verify => "LMS SHAKE M=24 H=10 W=8 verify",
1333            Self::LmsShakeM24H15W1Sign => "LMS SHAKE M=24 H=15 W=1 sign",
1334            Self::LmsShakeM24H15W1Verify => "LMS SHAKE M=24 H=15 W=1 verify",
1335            Self::LmsShakeM24H15W2Sign => "LMS SHAKE M=24 H=15 W=2 sign",
1336            Self::LmsShakeM24H15W2Verify => "LMS SHAKE M=24 H=15 W=2 verify",
1337            Self::LmsShakeM24H15W4Sign => "LMS SHAKE M=24 H=15 W=4 sign",
1338            Self::LmsShakeM24H15W4Verify => "LMS SHAKE M=24 H=15 W=4 verify",
1339            Self::LmsShakeM24H15W8Sign => "LMS SHAKE M=24 H=15 W=8 sign",
1340            Self::LmsShakeM24H15W8Verify => "LMS SHAKE M=24 H=15 W=8 verify",
1341            Self::LmsShakeM24H20W1Sign => "LMS SHAKE M=24 H=20 W=1 sign",
1342            Self::LmsShakeM24H20W1Verify => "LMS SHAKE M=24 H=20 W=1 verify",
1343            Self::LmsShakeM24H20W2Sign => "LMS SHAKE M=24 H=20 W=2 sign",
1344            Self::LmsShakeM24H20W2Verify => "LMS SHAKE M=24 H=20 W=2 verify",
1345            Self::LmsShakeM24H20W4Sign => "LMS SHAKE M=24 H=20 W=4 sign",
1346            Self::LmsShakeM24H20W4Verify => "LMS SHAKE M=24 H=20 W=4 verify",
1347            Self::LmsShakeM24H20W8Sign => "LMS SHAKE M=24 H=20 W=8 sign",
1348            Self::LmsShakeM24H20W8Verify => "LMS SHAKE M=24 H=20 W=8 verify",
1349            Self::LmsShakeM24H25W1Sign => "LMS SHAKE M=24 H=25 W=1 sign",
1350            Self::LmsShakeM24H25W1Verify => "LMS SHAKE M=24 H=25 W=1 verify",
1351            Self::LmsShakeM24H25W2Sign => "LMS SHAKE M=24 H=25 W=2 sign",
1352            Self::LmsShakeM24H25W2Verify => "LMS SHAKE M=24 H=25 W=2 verify",
1353            Self::LmsShakeM24H25W4Sign => "LMS SHAKE M=24 H=25 W=4 sign",
1354            Self::LmsShakeM24H25W4Verify => "LMS SHAKE M=24 H=25 W=4 verify",
1355            Self::LmsShakeM24H25W8Sign => "LMS SHAKE M=24 H=25 W=8 sign",
1356            Self::LmsShakeM24H25W8Verify => "LMS SHAKE M=24 H=25 W=8 verify",
1357            Self::XmssVerify => "XMSS verify",
1358            Self::Dh3072 => "DH-3072",
1359        };
1360        f.write_str(name)
1361    }
1362}
1363
1364/// Guard used at the entry point of every approved service, after
1365/// [`require_operational`], to enforce the active algorithm profile.
1366///
1367/// Returns `Ok(())` if the service is permitted under the profile
1368/// selected at initialization, or
1369/// `Err(Error::AlgorithmRestricted { .. })` otherwise.
1370///
1371/// This gate does **not** check [`State`] — callers must call
1372/// [`require_operational`] first. The two-gate pattern is:
1373///
1374/// ```ignore
1375/// require_operational()?;
1376/// require_allowed(Service::Sha256)?;
1377/// // ... perform the operation
1378/// ```
1379pub fn require_allowed(service: Service) -> Result<(), Error> {
1380    let profile = active_profile();
1381    if is_allowed(profile, service) {
1382        Ok(())
1383    } else {
1384        Err(Error::AlgorithmRestricted { service })
1385    }
1386}
1387
1388/// Single source of truth for which services are allowed in each
1389/// profile.
1390const fn is_allowed(profile: AlgorithmProfile, service: Service) -> bool {
1391    match profile {
1392        AlgorithmProfile::Unrestricted => true,
1393        AlgorithmProfile::Cnsa2 => is_cnsa2_allowed(service),
1394        AlgorithmProfile::Cnsa1 => is_cnsa1_allowed(service),
1395        // Derived from the two suites rather than enumerated a third time:
1396        // a hand-written union is a third list to drift, and drift is the
1397        // defect this work exists to repair. `is_cnsa2_allowed` already
1398        // covers the LMS block, so naming it again here would be that third
1399        // rule in miniature.
1400        AlgorithmProfile::Migration => is_cnsa1_allowed(service) || is_cnsa2_allowed(service),
1401    }
1402}
1403
1404/// True for every LMS service, matching on the contiguous discriminant
1405/// block the `Service` enum reserves for LMS.
1406///
1407/// A range test rather than 160 enumerated arms: the LMS block occupies
1408/// `500..=659` with nothing else inside it. That is a property of the
1409/// enum, not of this function, so it is pinned separately by
1410/// `lms_discriminant_block_is_contiguous_and_pure` — without which a
1411/// variant inserted into the range would silently become CNSA-permitted.
1412const fn is_lms_service(service: Service) -> bool {
1413    lms_block_contains(service as u16)
1414}
1415
1416/// The range arithmetic behind [`is_lms_service`], over a raw discriminant.
1417///
1418/// Split out so the boundaries can be tested at all. `is_lms_service` takes a
1419/// `Service`, and the LMS block sits at the very top of the enum — there is no
1420/// variant above 659 — so a widened *upper* bound admits nothing today and no
1421/// `Service`-typed probe can detect it. Over a raw `u16`, both edges are
1422/// reachable: `lms_block_boundaries_are_exact` pins 499/500/659/660, which
1423/// fails if either bound moves in either direction.
1424const fn lms_block_contains(discriminant: u16) -> bool {
1425    discriminant >= Service::LmsSha256M32H5W1Sign as u16
1426        && discriminant <= Service::LmsShakeM24H25W8Verify as u16
1427}
1428
1429/// CNSA 2.0 allowed set — CNSSP-15 (March 2025) Annex B.
1430///
1431/// The suite names AES-256, ML-KEM-1024, ML-DSA-87, SHA-384 or SHA-512,
1432/// and the SP 800-208 stateful hash-based signatures for software and
1433/// firmware signing. It names no Keccak-family hash or XOF: SHA-3
1434/// appears only as "allowed for internal hardware functionality", and
1435/// the policy states that algorithms using SHA3 as a component, such as
1436/// LMS or ML-KEM, do not thereby make SHA3 an approved hash function.
1437/// This module is software, so no Keccak service is permitted here —
1438/// ML-KEM and ML-DSA reach Keccak through `new_internal`, which is a
1439/// component path and not a service.
1440const fn is_cnsa2_allowed(service: Service) -> bool {
1441    matches!(
1442        service,
1443        // AES-256 all modes
1444        Service::Aes256Ecb
1445            | Service::Aes256Cbc
1446            | Service::Aes256Ctr
1447            | Service::Aes256Gcm
1448            | Service::Aes256Ccm
1449            | Service::Aes256Kw
1450            | Service::Aes256Kwp
1451            | Service::Aes256
1452            // SHA-384, SHA-512
1453            | Service::Sha384
1454            | Service::Sha512
1455            // No Keccak-family service: Annex B allows SHA3-384/512 only
1456            // for internal hardware functionality, which a software module
1457            // cannot claim, and names no SHAKE, KMAC, TupleHash or
1458            // ParallelHash at all.
1459            // HMAC with allowed hashes
1460            | Service::HmacSha384
1461            | Service::HmacSha512
1462            // CMAC-AES-256
1463            | Service::CmacAes256
1464            // DRBGs backed by AES-256 or SHA-384/512
1465            | Service::CtrDrbgAes256
1466            | Service::HashDrbgSha384
1467            | Service::HashDrbgSha512
1468            | Service::HmacDrbgSha384
1469            | Service::HmacDrbgSha512
1470            // KDFs with allowed backing
1471            | Service::HkdfSha384
1472            | Service::HkdfSha512
1473            | Service::KbkdfHmacSha384
1474            | Service::KbkdfHmacSha512
1475            | Service::KbkdfCmacAes256
1476            // No TLS KDF: Annex B names no protocol-specific key derivation
1477            // function, and none is required in order to operate an algorithm
1478            // the suite does name. Both reach the fail-safe default-block.
1479            // Post-quantum (CNSA 2.0 core)
1480            | Service::MlKem1024Encaps
1481            | Service::MlKem1024Decaps
1482            | Service::MlKem1024Keygen
1483            | Service::MlDsa87Sign
1484            | Service::MlDsa87Verify
1485            | Service::MlDsa87Keygen
1486            // XMSS — SP 800-208 verification. §8.1 confines key generation and
1487            // signing to hardware modules, so the module offers neither.
1488            | Service::XmssVerify
1489    )
1490        // LMS — SP 800-208 stateful HBS for software/firmware signing.
1491        // CNSSP-15 Annex B states "all parameters appropriate to protect to
1492        // TOP SECRET", recommending LMS-SHA-256/192, so every approved
1493        // parameter set the module implements is permitted rather than a
1494        // subset. Every non-LMS service outside the arms above still reaches
1495        // the `matches!` fail-safe default-block.
1496        || is_lms_service(service)
1497}
1498
1499/// CNSA 1.0 allowed set — CNSSP-15 (October 2016) Annex B.
1500///
1501/// Annex B names seven families with their parameters: AES with 256-bit
1502/// keys, ECDH on Curve P-384, ECDSA on Curve P-384, SHA-384, Diffie-Hellman
1503/// with a minimum 3072-bit modulus, RSA with a minimum 3072-bit modulus for
1504/// key establishment, and RSA with a minimum 3072-bit modulus for digital
1505/// signatures. It names no hash other than SHA-384 and no Keccak-family
1506/// algorithm at all — the policy does not cite FIPS PUB 202.
1507///
1508/// The suite names no DRBG, MAC or KDF, because it states what protects
1509/// information rather than every primitive a module needs in order to do
1510/// so. This gate therefore also admits the supporting services required to
1511/// operate the named algorithms **at matching strength**: the DRBGs that
1512/// seed their key generation, and the MAC and KDFs at the SHA-384 /
1513/// AES-256 security level. Anything weaker than the suite requires is
1514/// refused, SHA-256 and its HMAC, DRBG and KDF variants included.
1515///
1516/// Annex B's Additional Information paragraph permits RSA and Diffie-Hellman
1517/// at 2048, SHA-256, and P-256 "in the near term", but only for deployments
1518/// protecting UNCLASSIFIED NSS data or providing community-of-interest
1519/// separation, and it requires consulting NSA before deploying a curve other
1520/// than P-384. Those conditions turn on the classification of the data and on
1521/// an external approval, neither of which a module can evaluate, so this gate
1522/// refuses them and the Security Policy carries the allowance as an operator
1523/// obligation. [`AlgorithmProfile::Migration`] is the profile for a
1524/// deployment that needs both suites at once.
1525const fn is_cnsa1_allowed(service: Service) -> bool {
1526    matches!(
1527        service,
1528        // AES-256 — FIPS PUB 197, "Use 256 bit keys to protect up to TOP SECRET"
1529        Service::Aes256
1530            | Service::Aes256Ecb
1531            | Service::Aes256Cbc
1532            | Service::Aes256Ctr
1533            | Service::Aes256Gcm
1534            | Service::Aes256Ccm
1535            | Service::Aes256Kw
1536            | Service::Aes256Kwp
1537            // ECDH — SP 800-56A Rev 2, "Use Curve P-384 to protect up to TOP SECRET"
1538            | Service::EcdhP384
1539            // ECDSA — FIPS PUB 186-4, "Use Curve P-384 to protect up to TOP SECRET"
1540            | Service::EcdsaP384Keygen
1541            | Service::EcdsaP384Sign
1542            | Service::EcdsaP384Verify
1543            // SHA — FIPS PUB 180-4, "Use SHA-384 to protect up to TOP SECRET"
1544            | Service::Sha384
1545            // Diffie-Hellman — IETF RFC 3526, minimum 3072-bit modulus
1546            | Service::Dh3072
1547            // RSA key establishment — SP 800-56B Rev 1, minimum 3072-bit modulus
1548            | Service::RsaKeygen3072
1549            | Service::RsaKeygen4096
1550            | Service::RsaOaep3072
1551            | Service::RsaOaep4096
1552            // RSA digital signatures — FIPS PUB 186-4, minimum 3072-bit modulus
1553            | Service::RsaPkcs1v15Sign3072
1554            | Service::RsaPkcs1v15Sign4096
1555            | Service::RsaPkcs1v15Verify3072
1556            | Service::RsaPkcs1v15Verify4096
1557            | Service::RsaPssSign3072
1558            | Service::RsaPssSign4096
1559            | Service::RsaPssVerify3072
1560            | Service::RsaPssVerify4096
1561            // Supporting services at the suite's own strength: the DRBGs that
1562            // seed key generation for the algorithms above, and the MAC and
1563            // KDFs at the SHA-384 / AES-256 level.
1564            | Service::CtrDrbgAes256
1565            | Service::HashDrbgSha384
1566            | Service::HmacDrbgSha384
1567            | Service::HmacSha384
1568            | Service::CmacAes256
1569            | Service::HkdfSha384
1570            | Service::KbkdfHmacSha384
1571            | Service::KbkdfCmacAes256
1572    )
1573}
1574
1575// -------------------------------------------------------------------------
1576// Test-only utilities
1577// -------------------------------------------------------------------------
1578//
1579// The global `STATE` makes the normal lifecycle effectively run-once per
1580// process. Unit tests in this crate need to exercise multiple transitions,
1581// so we expose a test-only reset helper gated behind `cfg(test)`. It is
1582// **not** part of the public API and is not compiled into release builds.
1583
1584/// The test-only state lock, in its own module so the guard cannot be forged.
1585///
1586/// The globals are a process singleton and cargo runs unit tests on a parallel
1587/// thread pool, so without serialisation one test's reset lands inside
1588/// another's initialize-then-assert window and the second observes a profile it
1589/// did not install. Measured at 1 failure in 20 runs before this lock, on
1590/// `require_allowed_returns_restricted_error_under_cnsa2`.
1591///
1592/// Serialising is deliberate where per-test isolation would be easier: the
1593/// singleton is the thing under test, and giving each test private state would
1594/// stop exercising it.
1595///
1596/// **Why a module and not merely a private field.** `MutexGuard` carries no
1597/// identity tying it to a particular static, so a `StateGuard` wrapping *any*
1598/// mutex would satisfy the type and still reach `reset`. Confining the type
1599/// here — field private to this module — means the only way to obtain one is
1600/// [`reset_for_test`], which takes the real lock; a sibling module cannot build
1601/// a decoy. Editing this module still defeats it. That is the honest bound, and
1602/// it is a much smaller one than "every future test must remember".
1603#[cfg(test)]
1604pub(crate) mod state_lock {
1605    use super::{AlgorithmProfile, Ordering, PROFILE, STATE, State};
1606
1607    static STATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1608
1609    /// Proof that the holder owns the state lock. Constructible only by
1610    /// [`reset_for_test`], because the field is private to this module.
1611    #[must_use]
1612    pub(crate) struct StateGuard(
1613        /// Held for its `Drop`, never read: the lock releases when the guard
1614        /// leaves the test's scope.
1615        #[allow(dead_code)]
1616        std::sync::MutexGuard<'static, ()>,
1617    );
1618
1619    impl StateGuard {
1620        /// Re-reset inside a test that already holds the lock — a loop over
1621        /// profiles needs fresh state per iteration without releasing it.
1622        // `&self` is the point: it is the proof the caller holds the lock.
1623        #[allow(clippy::unused_self)]
1624        pub(crate) fn reset(&self) {
1625            STATE.store(State::PowerOff as u8, Ordering::Release);
1626            PROFILE.store(AlgorithmProfile::Unrestricted as u8, Ordering::Release);
1627        }
1628    }
1629
1630    /// Resets the module to power-off and returns the guard that makes the
1631    /// reset safe. The lock is not a convention a future test has to remember:
1632    /// this is the only way to obtain a `StateGuard`, and `StateGuard` is the
1633    /// only way to reach a reset.
1634    ///
1635    /// `StateGuard` is `#[must_use]`, so a bare `reset_for_test();` fails
1636    /// `clippy -D warnings`, which the workspace gate runs — dropping the guard
1637    /// early breaks the build rather than reintroducing the race quietly.
1638    ///
1639    /// Residual, stated rather than implied: `let _ = reset_for_test();` still
1640    /// drops the guard immediately and does not warn. That is a deliberate act,
1641    /// which is the most this construction can bound.
1642    ///
1643    /// A panicking test poisons the mutex. The poison carries no meaning here
1644    /// because the state it protects is unconditionally overwritten on the next
1645    /// line, so it is recovered rather than propagated.
1646    pub(crate) fn reset_for_test() -> StateGuard {
1647        let guard = StateGuard(
1648            STATE_LOCK
1649                .lock()
1650                .unwrap_or_else(std::sync::PoisonError::into_inner),
1651        );
1652        guard.reset();
1653        guard
1654    }
1655}
1656
1657#[cfg(test)]
1658extern crate alloc;
1659
1660#[cfg(test)]
1661extern crate std;
1662
1663#[cfg(test)]
1664#[allow(clippy::unwrap_used, clippy::panic)]
1665mod tests {
1666    use super::state_lock::reset_for_test;
1667    use super::{
1668        AlgorithmProfile, Error, KatEntry, SelfTest, SelfTestFailure, Service, State,
1669        active_profile, enter_error_state, initialize_with_profile, initialize_with_tests,
1670        is_allowed, is_lms_service, is_operational, lms_block_contains, require_allowed,
1671        require_operational, state,
1672    };
1673    use alloc::string::ToString;
1674
1675    /// Stands in for the pre-operational integrity test.
1676    ///
1677    /// A `cargo test` binary is never signed, so the real integrity test
1678    /// cannot pass inside one. The module requires an integrity group to
1679    /// initialise at all, so a test that needs a gated service declares
1680    /// this stub — visibly, at the call site — rather than the module
1681    /// offering any way to skip the requirement.
1682    const UNSIGNED_TEST_BINARY: &[KatEntry] = &[KatEntry {
1683        name: "integrity not verifiable in an unsigned test binary",
1684        run: || Ok(()),
1685    }];
1686
1687    // Both of these helpers are used as `fn() -> Result<(), SelfTestFailure>`
1688    // function pointers in `KatEntry`, so the `Result` wrapper is mandatory
1689    // even though clippy sees each body as always returning the same variant.
1690    #[allow(clippy::unnecessary_wraps)]
1691    fn always_pass() -> Result<(), SelfTestFailure> {
1692        Ok(())
1693    }
1694    fn always_fail() -> Result<(), SelfTestFailure> {
1695        Err(SelfTestFailure)
1696    }
1697
1698    // All tests in this module share the single global `STATE`, so they
1699    // cannot run in parallel. `cargo test` in this crate must be invoked
1700    // with `--test-threads=1` — enforced by the CI workflow.
1701
1702    #[test]
1703    fn initial_state_is_power_off() {
1704        let _guard = reset_for_test();
1705        assert_eq!(state(), State::PowerOff);
1706        assert!(!is_operational());
1707    }
1708
1709    #[test]
1710    fn initialize_transitions_to_operational() {
1711        let _guard = reset_for_test();
1712        initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
1713        assert_eq!(state(), State::Operational);
1714        assert!(is_operational());
1715        require_operational().unwrap();
1716    }
1717
1718    #[test]
1719    fn second_initialize_reports_already_initialized() {
1720        let _guard = reset_for_test();
1721        initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
1722        match initialize_with_tests(UNSIGNED_TEST_BINARY, &[]) {
1723            Err(Error::AlreadyInitialized) => {}
1724            other => panic!("expected AlreadyInitialized, got {other:?}"),
1725        }
1726    }
1727
1728    #[test]
1729    fn require_operational_rejects_power_off() {
1730        let _guard = reset_for_test();
1731        match require_operational() {
1732            Err(Error::NotOperational {
1733                current: State::PowerOff,
1734            }) => {}
1735            other => panic!("expected NotOperational{{PowerOff}}, got {other:?}"),
1736        }
1737    }
1738
1739    #[test]
1740    fn enter_error_state_is_terminal_for_require_operational() {
1741        let _guard = reset_for_test();
1742        initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
1743        assert!(is_operational());
1744        enter_error_state("unit-test forced failure");
1745        assert_eq!(state(), State::Error);
1746        match require_operational() {
1747            Err(Error::NotOperational {
1748                current: State::Error,
1749            }) => {}
1750            other => panic!("expected NotOperational{{Error}}, got {other:?}"),
1751        }
1752    }
1753
1754    #[test]
1755    fn state_display_is_stable() {
1756        // The exact strings appear in log output and audit trails,
1757        // so pin them.
1758        assert_eq!(State::PowerOff.to_string(), "PowerOff");
1759        assert_eq!(State::SelfTest.to_string(), "SelfTest");
1760        assert_eq!(State::Operational.to_string(), "Operational");
1761        assert_eq!(State::Error.to_string(), "Error");
1762    }
1763
1764    #[test]
1765    fn registry_runs_passing_tests_and_reaches_operational() {
1766        let _guard = reset_for_test();
1767        let tests = [
1768            KatEntry {
1769                name: "dummy-pass-a",
1770                run: always_pass,
1771            },
1772            KatEntry {
1773                name: "dummy-pass-b",
1774                run: always_pass,
1775            },
1776        ];
1777        initialize_with_tests(UNSIGNED_TEST_BINARY, &tests).unwrap();
1778        assert_eq!(state(), State::Operational);
1779    }
1780
1781    #[test]
1782    fn registry_failing_test_latches_error_and_returns_name() {
1783        let _guard = reset_for_test();
1784        let tests = [
1785            KatEntry {
1786                name: "dummy-pass",
1787                run: always_pass,
1788            },
1789            KatEntry {
1790                name: "dummy-fail",
1791                run: always_fail,
1792            },
1793            // This third entry must never run.
1794            KatEntry {
1795                name: "dummy-unreached",
1796                run: always_pass,
1797            },
1798        ];
1799        match initialize_with_tests(UNSIGNED_TEST_BINARY, &tests) {
1800            Err(Error::SelfTestFailed { test: "dummy-fail" }) => {}
1801            other => panic!("expected SelfTestFailed{{dummy-fail}}, got {other:?}"),
1802        }
1803        assert_eq!(state(), State::Error);
1804        // Operational-only guard must reject calls from here on.
1805        assert!(matches!(
1806            require_operational(),
1807            Err(Error::NotOperational {
1808                current: State::Error
1809            })
1810        ));
1811    }
1812
1813    // Trivial SelfTest implementation exercised outside the registry
1814    // so the trait stays part of the compiled public surface.
1815    struct DummyTest;
1816    impl SelfTest for DummyTest {
1817        const NAME: &'static str = "dummy-trait-vector";
1818        fn run() -> Result<(), SelfTestFailure> {
1819            Ok(())
1820        }
1821    }
1822
1823    #[test]
1824    fn dummy_self_test_trait_is_usable() {
1825        assert_eq!(DummyTest::NAME, "dummy-trait-vector");
1826        DummyTest::run().unwrap();
1827    }
1828
1829    // =====================================================================
1830    // Algorithm profile gating tests
1831    // =====================================================================
1832
1833    #[test]
1834    fn default_profile_is_unrestricted() {
1835        let _guard = reset_for_test();
1836        assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
1837    }
1838
1839    #[test]
1840    fn initialize_with_profile_sets_cnsa2() {
1841        let _guard = reset_for_test();
1842        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
1843        assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
1844        assert!(is_operational());
1845    }
1846
1847    #[test]
1848    fn initialize_with_profile_sets_cnsa1() {
1849        let _guard = reset_for_test();
1850        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa1).unwrap();
1851        assert_eq!(active_profile(), AlgorithmProfile::Cnsa1);
1852        assert!(is_operational());
1853    }
1854
1855    #[test]
1856    fn unrestricted_allows_all_services() {
1857        // Spot-check: every category should pass in Unrestricted.
1858        let spot = [
1859            Service::Sha1,
1860            Service::Sha256,
1861            Service::Aes128Ecb,
1862            Service::Aes256Gcm,
1863            Service::CtrDrbgAes128,
1864            Service::EcdsaP256Sign,
1865            Service::Ed25519Sign,
1866            Service::RsaPssSign2048,
1867            Service::MlKem1024Encaps,
1868            Service::LmsSha256M32H10W4Sign,
1869            Service::Dh3072,
1870            Service::SlhDsaSha2256sSign,
1871        ];
1872        for svc in spot {
1873            assert!(
1874                is_allowed(AlgorithmProfile::Unrestricted, svc),
1875                "{svc} should be allowed in Unrestricted"
1876            );
1877        }
1878    }
1879
1880    #[test]
1881    fn cnsa2_allows_aes256_and_pq() {
1882        let allowed = [
1883            Service::Aes256Gcm,
1884            Service::Aes256Cbc,
1885            Service::Sha384,
1886            Service::Sha512,
1887            Service::HmacSha384,
1888            Service::HmacSha512,
1889            Service::CtrDrbgAes256,
1890            Service::HashDrbgSha384,
1891            Service::HmacDrbgSha512,
1892            Service::MlKem1024Encaps,
1893            Service::MlKem1024Decaps,
1894            Service::MlKem1024Keygen,
1895            Service::MlDsa87Sign,
1896            Service::MlDsa87Verify,
1897            Service::MlDsa87Keygen,
1898            Service::LmsSha256M32H10W4Sign,
1899            Service::LmsSha256M32H10W4Verify,
1900            Service::XmssVerify,
1901        ];
1902        for svc in allowed {
1903            assert!(
1904                is_allowed(AlgorithmProfile::Cnsa2, svc),
1905                "{svc} should be allowed in CNSA 2.0"
1906            );
1907        }
1908    }
1909
1910    /// CNSSP-15 (March 2025) Annex B names no Keccak-family hash or XOF.
1911    /// SHA-3 appears only as "allowed for internal hardware functionality",
1912    /// and the policy states outright that algorithms using SHA3 as a
1913    /// component — LMS and ML-KEM are its examples — do not thereby make
1914    /// SHA3 an approved hash function. This module is software, so the whole
1915    /// family is refused under CNSA 2.0. Pinned as a property because the
1916    /// gate permitted all twelve until 2026-08-18.
1917    #[test]
1918    fn cnsa2_refuses_the_keccak_family() {
1919        let refused = [
1920            Service::Shake256,
1921            Service::CShake256,
1922            Service::Kmac256,
1923            Service::KmacXof256,
1924            Service::TupleHash256,
1925            Service::TupleHashXof256,
1926            Service::ParallelHash256,
1927            Service::ParallelHashXof256,
1928            Service::Sha3_384,
1929            Service::Sha3_512,
1930            Service::HmacSha3_384,
1931            Service::HmacSha3_512,
1932        ];
1933        for svc in refused {
1934            assert!(
1935                !is_allowed(AlgorithmProfile::Cnsa2, svc),
1936                "{svc} is not in CNSA 2.0 and must be refused"
1937            );
1938        }
1939        // Control: the suite's own hash must still pass, or this test would
1940        // also pass against a gate that refuses everything.
1941        assert!(is_allowed(AlgorithmProfile::Cnsa2, Service::Sha384));
1942        // Control: ML-KEM reaches Keccak internally and must be unaffected.
1943        assert!(is_allowed(
1944            AlgorithmProfile::Cnsa2,
1945            Service::MlKem1024Encaps
1946        ));
1947    }
1948
1949    #[test]
1950    fn cnsa2_blocks_classical_and_small_keys() {
1951        let blocked = [
1952            Service::Sha1,
1953            Service::Sha224,
1954            Service::Sha256,
1955            Service::Sha512_224,
1956            Service::Sha512_256,
1957            Service::Sha3_224,
1958            Service::Sha3_256,
1959            Service::Shake128,
1960            Service::Kmac128,
1961            Service::Aes128Ecb,
1962            Service::Aes128Gcm,
1963            Service::Aes192Cbc,
1964            Service::CtrDrbgAes128,
1965            Service::CtrDrbgAes192,
1966            Service::HashDrbgSha256,
1967            Service::HmacDrbgSha256,
1968            Service::HmacSha1,
1969            Service::HmacSha256,
1970            Service::CmacAes128,
1971            Service::CmacAes192,
1972            Service::EcdsaP256Sign,
1973            Service::EcdsaP256Verify,
1974            Service::EcdhP256,
1975            Service::Ed25519Sign,
1976            Service::Ed25519Verify,
1977            Service::RsaPssSign2048,
1978            Service::RsaOaep2048,
1979            // No SLH-DSA parameter set is in CNSA 2.0 — CNSSP-15 lists none.
1980            Service::SlhDsaSha2128sSign,
1981            Service::MlDsa44Sign,
1982            Service::MlDsa44Verify,
1983            Service::MlDsa44Keygen,
1984            Service::MlDsa65Sign,
1985            Service::MlDsa65Verify,
1986            Service::MlDsa65Keygen,
1987        ];
1988        for svc in blocked {
1989            assert!(
1990                !is_allowed(AlgorithmProfile::Cnsa2, svc),
1991                "{svc} should be BLOCKED in CNSA 2.0"
1992            );
1993        }
1994    }
1995
1996    /// Every `Service` variant, so a profile's permitted set can be computed
1997    /// rather than sampled.
1998    ///
1999    /// The earlier membership test asserted only that an expected list was a
2000    /// SUBSET of what the gate permits, with a length assertion on the test's
2001    /// own array — a constant. It therefore could not see a widening: adding
2002    /// `RsaKeygen2048` to the CNSA 1.0 gate left it green, which is precisely
2003    /// the drift the test existed to catch. Enumerating the enum makes the
2004    /// comparison exact in both directions.
2005    const ALL_SERVICES: [Service; 336] = [
2006        Service::Sha1,
2007        Service::Sha224,
2008        Service::Sha256,
2009        Service::Sha384,
2010        Service::Sha512,
2011        Service::Sha512_224,
2012        Service::Sha512_256,
2013        Service::Sha3_224,
2014        Service::Sha3_256,
2015        Service::Sha3_384,
2016        Service::Sha3_512,
2017        Service::Shake128,
2018        Service::Shake256,
2019        Service::CShake128,
2020        Service::CShake256,
2021        Service::Kmac128,
2022        Service::Kmac256,
2023        Service::KmacXof128,
2024        Service::KmacXof256,
2025        Service::TupleHash128,
2026        Service::TupleHash256,
2027        Service::TupleHashXof128,
2028        Service::TupleHashXof256,
2029        Service::ParallelHash128,
2030        Service::ParallelHash256,
2031        Service::ParallelHashXof128,
2032        Service::ParallelHashXof256,
2033        Service::HmacSha1,
2034        Service::HmacSha224,
2035        Service::HmacSha256,
2036        Service::HmacSha384,
2037        Service::HmacSha512,
2038        Service::HmacSha512_224,
2039        Service::HmacSha512_256,
2040        Service::HmacSha3_224,
2041        Service::HmacSha3_256,
2042        Service::HmacSha3_384,
2043        Service::HmacSha3_512,
2044        Service::CmacAes128,
2045        Service::CmacAes192,
2046        Service::CmacAes256,
2047        Service::Aes128Ecb,
2048        Service::Aes128Cbc,
2049        Service::Aes128Ctr,
2050        Service::Aes128Gcm,
2051        Service::Aes128Ccm,
2052        Service::Aes128Kw,
2053        Service::Aes128Kwp,
2054        Service::Aes192Ecb,
2055        Service::Aes192Cbc,
2056        Service::Aes192Ctr,
2057        Service::Aes192Gcm,
2058        Service::Aes192Ccm,
2059        Service::Aes192Kw,
2060        Service::Aes192Kwp,
2061        Service::Aes256Ecb,
2062        Service::Aes256Cbc,
2063        Service::Aes256Ctr,
2064        Service::Aes256Gcm,
2065        Service::Aes256Ccm,
2066        Service::Aes256Kw,
2067        Service::Aes256Kwp,
2068        Service::Aes128,
2069        Service::Aes192,
2070        Service::Aes256,
2071        Service::CtrDrbgAes128,
2072        Service::CtrDrbgAes192,
2073        Service::CtrDrbgAes256,
2074        Service::HashDrbgSha256,
2075        Service::HashDrbgSha384,
2076        Service::HashDrbgSha512,
2077        Service::HmacDrbgSha256,
2078        Service::HmacDrbgSha384,
2079        Service::HmacDrbgSha512,
2080        Service::HkdfSha1,
2081        Service::HkdfSha256,
2082        Service::HkdfSha384,
2083        Service::HkdfSha512,
2084        Service::KbkdfHmacSha256,
2085        Service::KbkdfHmacSha384,
2086        Service::KbkdfHmacSha512,
2087        Service::KbkdfCmacAes128,
2088        Service::KbkdfCmacAes192,
2089        Service::KbkdfCmacAes256,
2090        Service::Pbkdf2HmacSha1,
2091        Service::Pbkdf2HmacSha224,
2092        Service::Pbkdf2HmacSha256,
2093        Service::Pbkdf2HmacSha384,
2094        Service::Pbkdf2HmacSha512,
2095        Service::RsaKeygen2048,
2096        Service::RsaPkcs1v15Sign2048,
2097        Service::RsaPssSign2048,
2098        Service::RsaOaep2048,
2099        Service::RsaPkcs1v15Verify2048,
2100        Service::RsaPssVerify2048,
2101        Service::RsaKeygen3072,
2102        Service::RsaPkcs1v15Sign3072,
2103        Service::RsaPssSign3072,
2104        Service::RsaOaep3072,
2105        Service::RsaPkcs1v15Verify3072,
2106        Service::RsaPssVerify3072,
2107        Service::RsaKeygen4096,
2108        Service::RsaPkcs1v15Sign4096,
2109        Service::RsaPssSign4096,
2110        Service::RsaOaep4096,
2111        Service::RsaPkcs1v15Verify4096,
2112        Service::RsaPssVerify4096,
2113        Service::EcdsaP256Sign,
2114        Service::EcdsaP256Verify,
2115        Service::EcdsaP256Keygen,
2116        Service::EcdsaP384Sign,
2117        Service::EcdsaP384Verify,
2118        Service::EcdsaP384Keygen,
2119        Service::EcdhP256,
2120        Service::EcdhP384,
2121        Service::Ed25519Sign,
2122        Service::Ed25519Verify,
2123        Service::Ed25519Keygen,
2124        Service::Tls12Kdf,
2125        Service::Tls13Kdf,
2126        Service::MlKem1024Encaps,
2127        Service::MlKem1024Decaps,
2128        Service::MlKem1024Keygen,
2129        Service::MlKem512Encaps,
2130        Service::MlKem512Decaps,
2131        Service::MlKem512Keygen,
2132        Service::MlKem768Encaps,
2133        Service::MlKem768Decaps,
2134        Service::MlKem768Keygen,
2135        Service::MlDsa87Sign,
2136        Service::MlDsa87Verify,
2137        Service::MlDsa87Keygen,
2138        Service::MlDsa44Sign,
2139        Service::MlDsa44Verify,
2140        Service::MlDsa44Keygen,
2141        Service::MlDsa65Sign,
2142        Service::MlDsa65Verify,
2143        Service::MlDsa65Keygen,
2144        Service::SlhDsaSha2256sKeygen,
2145        Service::SlhDsaSha2256sSign,
2146        Service::SlhDsaSha2256sVerify,
2147        Service::SlhDsaSha2128sKeygen,
2148        Service::SlhDsaSha2128sSign,
2149        Service::SlhDsaSha2128sVerify,
2150        Service::SlhDsaSha2128fKeygen,
2151        Service::SlhDsaSha2128fSign,
2152        Service::SlhDsaSha2128fVerify,
2153        Service::SlhDsaSha2192sKeygen,
2154        Service::SlhDsaSha2192sSign,
2155        Service::SlhDsaSha2192sVerify,
2156        Service::SlhDsaSha2192fKeygen,
2157        Service::SlhDsaSha2192fSign,
2158        Service::SlhDsaSha2192fVerify,
2159        Service::SlhDsaSha2256fKeygen,
2160        Service::SlhDsaSha2256fSign,
2161        Service::SlhDsaSha2256fVerify,
2162        Service::SlhDsaShake128sKeygen,
2163        Service::SlhDsaShake128sSign,
2164        Service::SlhDsaShake128sVerify,
2165        Service::SlhDsaShake128fKeygen,
2166        Service::SlhDsaShake128fSign,
2167        Service::SlhDsaShake128fVerify,
2168        Service::SlhDsaShake192sKeygen,
2169        Service::SlhDsaShake192sSign,
2170        Service::SlhDsaShake192sVerify,
2171        Service::SlhDsaShake192fKeygen,
2172        Service::SlhDsaShake192fSign,
2173        Service::SlhDsaShake192fVerify,
2174        Service::SlhDsaShake256sKeygen,
2175        Service::SlhDsaShake256sSign,
2176        Service::SlhDsaShake256sVerify,
2177        Service::SlhDsaShake256fKeygen,
2178        Service::SlhDsaShake256fSign,
2179        Service::SlhDsaShake256fVerify,
2180        Service::LmsSha256M32H5W1Sign,
2181        Service::LmsSha256M32H5W1Verify,
2182        Service::LmsSha256M32H5W2Sign,
2183        Service::LmsSha256M32H5W2Verify,
2184        Service::LmsSha256M32H5W4Sign,
2185        Service::LmsSha256M32H5W4Verify,
2186        Service::LmsSha256M32H5W8Sign,
2187        Service::LmsSha256M32H5W8Verify,
2188        Service::LmsSha256M32H10W1Sign,
2189        Service::LmsSha256M32H10W1Verify,
2190        Service::LmsSha256M32H10W2Sign,
2191        Service::LmsSha256M32H10W2Verify,
2192        Service::LmsSha256M32H10W4Sign,
2193        Service::LmsSha256M32H10W4Verify,
2194        Service::LmsSha256M32H10W8Sign,
2195        Service::LmsSha256M32H10W8Verify,
2196        Service::LmsSha256M32H15W1Sign,
2197        Service::LmsSha256M32H15W1Verify,
2198        Service::LmsSha256M32H15W2Sign,
2199        Service::LmsSha256M32H15W2Verify,
2200        Service::LmsSha256M32H15W4Sign,
2201        Service::LmsSha256M32H15W4Verify,
2202        Service::LmsSha256M32H15W8Sign,
2203        Service::LmsSha256M32H15W8Verify,
2204        Service::LmsSha256M32H20W1Sign,
2205        Service::LmsSha256M32H20W1Verify,
2206        Service::LmsSha256M32H20W2Sign,
2207        Service::LmsSha256M32H20W2Verify,
2208        Service::LmsSha256M32H20W4Sign,
2209        Service::LmsSha256M32H20W4Verify,
2210        Service::LmsSha256M32H20W8Sign,
2211        Service::LmsSha256M32H20W8Verify,
2212        Service::LmsSha256M32H25W1Sign,
2213        Service::LmsSha256M32H25W1Verify,
2214        Service::LmsSha256M32H25W2Sign,
2215        Service::LmsSha256M32H25W2Verify,
2216        Service::LmsSha256M32H25W4Sign,
2217        Service::LmsSha256M32H25W4Verify,
2218        Service::LmsSha256M32H25W8Sign,
2219        Service::LmsSha256M32H25W8Verify,
2220        Service::LmsSha256M24H5W1Sign,
2221        Service::LmsSha256M24H5W1Verify,
2222        Service::LmsSha256M24H5W2Sign,
2223        Service::LmsSha256M24H5W2Verify,
2224        Service::LmsSha256M24H5W4Sign,
2225        Service::LmsSha256M24H5W4Verify,
2226        Service::LmsSha256M24H5W8Sign,
2227        Service::LmsSha256M24H5W8Verify,
2228        Service::LmsSha256M24H10W1Sign,
2229        Service::LmsSha256M24H10W1Verify,
2230        Service::LmsSha256M24H10W2Sign,
2231        Service::LmsSha256M24H10W2Verify,
2232        Service::LmsSha256M24H10W4Sign,
2233        Service::LmsSha256M24H10W4Verify,
2234        Service::LmsSha256M24H10W8Sign,
2235        Service::LmsSha256M24H10W8Verify,
2236        Service::LmsSha256M24H15W1Sign,
2237        Service::LmsSha256M24H15W1Verify,
2238        Service::LmsSha256M24H15W2Sign,
2239        Service::LmsSha256M24H15W2Verify,
2240        Service::LmsSha256M24H15W4Sign,
2241        Service::LmsSha256M24H15W4Verify,
2242        Service::LmsSha256M24H15W8Sign,
2243        Service::LmsSha256M24H15W8Verify,
2244        Service::LmsSha256M24H20W1Sign,
2245        Service::LmsSha256M24H20W1Verify,
2246        Service::LmsSha256M24H20W2Sign,
2247        Service::LmsSha256M24H20W2Verify,
2248        Service::LmsSha256M24H20W4Sign,
2249        Service::LmsSha256M24H20W4Verify,
2250        Service::LmsSha256M24H20W8Sign,
2251        Service::LmsSha256M24H20W8Verify,
2252        Service::LmsSha256M24H25W1Sign,
2253        Service::LmsSha256M24H25W1Verify,
2254        Service::LmsSha256M24H25W2Sign,
2255        Service::LmsSha256M24H25W2Verify,
2256        Service::LmsSha256M24H25W4Sign,
2257        Service::LmsSha256M24H25W4Verify,
2258        Service::LmsSha256M24H25W8Sign,
2259        Service::LmsSha256M24H25W8Verify,
2260        Service::LmsShakeM32H5W1Sign,
2261        Service::LmsShakeM32H5W1Verify,
2262        Service::LmsShakeM32H5W2Sign,
2263        Service::LmsShakeM32H5W2Verify,
2264        Service::LmsShakeM32H5W4Sign,
2265        Service::LmsShakeM32H5W4Verify,
2266        Service::LmsShakeM32H5W8Sign,
2267        Service::LmsShakeM32H5W8Verify,
2268        Service::LmsShakeM32H10W1Sign,
2269        Service::LmsShakeM32H10W1Verify,
2270        Service::LmsShakeM32H10W2Sign,
2271        Service::LmsShakeM32H10W2Verify,
2272        Service::LmsShakeM32H10W4Sign,
2273        Service::LmsShakeM32H10W4Verify,
2274        Service::LmsShakeM32H10W8Sign,
2275        Service::LmsShakeM32H10W8Verify,
2276        Service::LmsShakeM32H15W1Sign,
2277        Service::LmsShakeM32H15W1Verify,
2278        Service::LmsShakeM32H15W2Sign,
2279        Service::LmsShakeM32H15W2Verify,
2280        Service::LmsShakeM32H15W4Sign,
2281        Service::LmsShakeM32H15W4Verify,
2282        Service::LmsShakeM32H15W8Sign,
2283        Service::LmsShakeM32H15W8Verify,
2284        Service::LmsShakeM32H20W1Sign,
2285        Service::LmsShakeM32H20W1Verify,
2286        Service::LmsShakeM32H20W2Sign,
2287        Service::LmsShakeM32H20W2Verify,
2288        Service::LmsShakeM32H20W4Sign,
2289        Service::LmsShakeM32H20W4Verify,
2290        Service::LmsShakeM32H20W8Sign,
2291        Service::LmsShakeM32H20W8Verify,
2292        Service::LmsShakeM32H25W1Sign,
2293        Service::LmsShakeM32H25W1Verify,
2294        Service::LmsShakeM32H25W2Sign,
2295        Service::LmsShakeM32H25W2Verify,
2296        Service::LmsShakeM32H25W4Sign,
2297        Service::LmsShakeM32H25W4Verify,
2298        Service::LmsShakeM32H25W8Sign,
2299        Service::LmsShakeM32H25W8Verify,
2300        Service::LmsShakeM24H5W1Sign,
2301        Service::LmsShakeM24H5W1Verify,
2302        Service::LmsShakeM24H5W2Sign,
2303        Service::LmsShakeM24H5W2Verify,
2304        Service::LmsShakeM24H5W4Sign,
2305        Service::LmsShakeM24H5W4Verify,
2306        Service::LmsShakeM24H5W8Sign,
2307        Service::LmsShakeM24H5W8Verify,
2308        Service::LmsShakeM24H10W1Sign,
2309        Service::LmsShakeM24H10W1Verify,
2310        Service::LmsShakeM24H10W2Sign,
2311        Service::LmsShakeM24H10W2Verify,
2312        Service::LmsShakeM24H10W4Sign,
2313        Service::LmsShakeM24H10W4Verify,
2314        Service::LmsShakeM24H10W8Sign,
2315        Service::LmsShakeM24H10W8Verify,
2316        Service::LmsShakeM24H15W1Sign,
2317        Service::LmsShakeM24H15W1Verify,
2318        Service::LmsShakeM24H15W2Sign,
2319        Service::LmsShakeM24H15W2Verify,
2320        Service::LmsShakeM24H15W4Sign,
2321        Service::LmsShakeM24H15W4Verify,
2322        Service::LmsShakeM24H15W8Sign,
2323        Service::LmsShakeM24H15W8Verify,
2324        Service::LmsShakeM24H20W1Sign,
2325        Service::LmsShakeM24H20W1Verify,
2326        Service::LmsShakeM24H20W2Sign,
2327        Service::LmsShakeM24H20W2Verify,
2328        Service::LmsShakeM24H20W4Sign,
2329        Service::LmsShakeM24H20W4Verify,
2330        Service::LmsShakeM24H20W8Sign,
2331        Service::LmsShakeM24H20W8Verify,
2332        Service::LmsShakeM24H25W1Sign,
2333        Service::LmsShakeM24H25W1Verify,
2334        Service::LmsShakeM24H25W2Sign,
2335        Service::LmsShakeM24H25W2Verify,
2336        Service::LmsShakeM24H25W4Sign,
2337        Service::LmsShakeM24H25W4Verify,
2338        Service::LmsShakeM24H25W8Sign,
2339        Service::LmsShakeM24H25W8Verify,
2340        Service::XmssVerify,
2341        Service::Dh3072,
2342    ];
2343
2344    /// CNSA 1.0 permits exactly the CNSSP-15 (October 2016) Annex B suite and
2345    /// the supporting services it needs at matching strength.
2346    const EXPECTED_CNSA1: [Service; 34] = [
2347        Service::Sha384,
2348        Service::HmacSha384,
2349        Service::CmacAes256,
2350        Service::Aes256Ecb,
2351        Service::Aes256Cbc,
2352        Service::Aes256Ctr,
2353        Service::Aes256Gcm,
2354        Service::Aes256Ccm,
2355        Service::Aes256Kw,
2356        Service::Aes256Kwp,
2357        Service::Aes256,
2358        Service::CtrDrbgAes256,
2359        Service::HashDrbgSha384,
2360        Service::HmacDrbgSha384,
2361        Service::HkdfSha384,
2362        Service::KbkdfHmacSha384,
2363        Service::KbkdfCmacAes256,
2364        Service::RsaKeygen3072,
2365        Service::RsaPkcs1v15Sign3072,
2366        Service::RsaPssSign3072,
2367        Service::RsaOaep3072,
2368        Service::RsaPkcs1v15Verify3072,
2369        Service::RsaPssVerify3072,
2370        Service::RsaKeygen4096,
2371        Service::RsaPkcs1v15Sign4096,
2372        Service::RsaPssSign4096,
2373        Service::RsaOaep4096,
2374        Service::RsaPkcs1v15Verify4096,
2375        Service::RsaPssVerify4096,
2376        Service::EcdsaP384Sign,
2377        Service::EcdsaP384Verify,
2378        Service::EcdsaP384Keygen,
2379        Service::EcdhP384,
2380        Service::Dh3072,
2381    ];
2382
2383    /// CNSA 2.0's non-LMS membership. The 160 LMS services are asserted by
2384    /// count rather than listed, because `lms_all_160` already enumerates them.
2385    const EXPECTED_CNSA2_NON_LMS: [Service; 30] = [
2386        Service::Sha384,
2387        Service::Sha512,
2388        Service::HmacSha384,
2389        Service::HmacSha512,
2390        Service::CmacAes256,
2391        Service::Aes256Ecb,
2392        Service::Aes256Cbc,
2393        Service::Aes256Ctr,
2394        Service::Aes256Gcm,
2395        Service::Aes256Ccm,
2396        Service::Aes256Kw,
2397        Service::Aes256Kwp,
2398        Service::Aes256,
2399        Service::CtrDrbgAes256,
2400        Service::HashDrbgSha384,
2401        Service::HashDrbgSha512,
2402        Service::HmacDrbgSha384,
2403        Service::HmacDrbgSha512,
2404        Service::HkdfSha384,
2405        Service::HkdfSha512,
2406        Service::KbkdfHmacSha384,
2407        Service::KbkdfHmacSha512,
2408        Service::KbkdfCmacAes256,
2409        Service::MlKem1024Encaps,
2410        Service::MlKem1024Decaps,
2411        Service::MlKem1024Keygen,
2412        Service::MlDsa87Sign,
2413        Service::MlDsa87Verify,
2414        Service::MlDsa87Keygen,
2415        Service::XmssVerify,
2416    ];
2417
2418    /// Assert a profile permits EXACTLY `expected` (plus the LMS block when
2419    /// `allow_lms`), checked over every variant so a widening cannot hide.
2420    fn assert_exact_membership(profile: AlgorithmProfile, expected: &[Service], allow_lms: bool) {
2421        for svc in ALL_SERVICES {
2422            let want = expected.iter().any(|e| *e as u16 == svc as u16)
2423                || (allow_lms && is_lms_service(svc));
2424            assert_eq!(
2425                is_allowed(profile, svc),
2426                want,
2427                "{svc} under {profile}: gate says {}, suite says {want}",
2428                is_allowed(profile, svc)
2429            );
2430        }
2431    }
2432
2433    /// Every profile survives the round trip through the `PROFILE` atomic.
2434    ///
2435    /// `from_u8` had no test at all, and the omission was not visible from the
2436    /// membership tests: those call the private `is_allowed` directly, so a
2437    /// mutation mapping discriminant 3 to `Cnsa2` left the suite green while
2438    /// every Migration deployment silently ran as CNSA 2.0 — refusing ECDSA
2439    /// P-384, RSA and DH — and `oxi_active_profile()` reported `1` for a module
2440    /// initialised with `3`. This goes through the entry point, which is the
2441    /// only place that mapping is exercised.
2442    #[test]
2443    fn every_profile_round_trips_through_the_active_profile() {
2444        for profile in [
2445            AlgorithmProfile::Unrestricted,
2446            AlgorithmProfile::Cnsa2,
2447            AlgorithmProfile::Cnsa1,
2448            AlgorithmProfile::Migration,
2449        ] {
2450            assert_eq!(
2451                AlgorithmProfile::from_u8(profile as u8),
2452                profile,
2453                "{profile} did not survive the round trip through its discriminant"
2454            );
2455        }
2456
2457        // An unknown discriminant must not resolve to a permissive profile.
2458        // `Unrestricted` refuses nothing, so it is the one answer that would
2459        // turn a corrupted byte into an open module.
2460        for raw in [4u8, 9, 200, 255] {
2461            assert_ne!(
2462                AlgorithmProfile::from_u8(raw),
2463                AlgorithmProfile::Unrestricted,
2464                "unknown discriminant {raw} must not open the module"
2465            );
2466        }
2467    }
2468
2469    /// A profile initialised through the public entry point gates as itself.
2470    ///
2471    /// The membership tests reach `is_allowed` directly; this reaches it the
2472    /// way a caller does, through `initialize_with_profile` and the `PROFILE`
2473    /// atomic that `require_allowed` reads.
2474    #[test]
2475    fn migration_gates_as_itself_through_require_allowed() {
2476        let guard = reset_for_test();
2477        guard.reset();
2478        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Migration).unwrap();
2479        assert_eq!(active_profile(), AlgorithmProfile::Migration);
2480
2481        // Permitted by CNSA 1.0 only — refused if this silently ran as CNSA 2.0.
2482        assert!(
2483            require_allowed(Service::EcdsaP384Sign).is_ok(),
2484            "ECDSA P-384 is in CNSA 1.0 and must pass under Migration"
2485        );
2486        assert!(
2487            require_allowed(Service::Dh3072).is_ok(),
2488            "DH-3072 is in CNSA 1.0 and must pass under Migration"
2489        );
2490        // Permitted by CNSA 2.0 only — refused if this silently ran as CNSA 1.0.
2491        assert!(
2492            require_allowed(Service::MlKem1024Encaps).is_ok(),
2493            "ML-KEM-1024 is in CNSA 2.0 and must pass under Migration"
2494        );
2495        // Refused by both, so refused here: Migration is a gate, not Unrestricted.
2496        assert!(require_allowed(Service::Sha256).is_err());
2497    }
2498
2499    /// Exact membership, both directions. A service added to or removed from
2500    /// any gate fails here.
2501    #[test]
2502    fn profile_membership_is_exact() {
2503        assert_eq!(
2504            ALL_SERVICES.len(),
2505            336,
2506            "Service enumeration drift — a variant was added without listing it"
2507        );
2508
2509        assert_exact_membership(AlgorithmProfile::Cnsa1, &EXPECTED_CNSA1, false);
2510        assert_exact_membership(AlgorithmProfile::Cnsa2, &EXPECTED_CNSA2_NON_LMS, true);
2511
2512        // Migration is the union, so a service belongs iff either suite admits
2513        // it. Expressed against the two gates rather than a third list.
2514        for svc in ALL_SERVICES {
2515            let want = is_allowed(AlgorithmProfile::Cnsa1, svc)
2516                || is_allowed(AlgorithmProfile::Cnsa2, svc);
2517            assert_eq!(
2518                is_allowed(AlgorithmProfile::Migration, svc),
2519                want,
2520                "{svc}: Migration must permit exactly the union"
2521            );
2522        }
2523
2524        // Control: Unrestricted permits everything, so a gate that refused
2525        // everything could not have satisfied the assertions above.
2526        for svc in ALL_SERVICES {
2527            assert!(is_allowed(AlgorithmProfile::Unrestricted, svc));
2528        }
2529    }
2530
2531    /// Migration is the union of the two suites and nothing more.
2532    ///
2533    /// It is not a CNSA profile: no standard defines it, which is why it does
2534    /// not carry a CNSA name. Its contract is that it permits what either
2535    /// suite permits, refuses what both refuse, and is strictly narrower than
2536    /// `Unrestricted`.
2537    #[test]
2538    fn migration_is_the_union_of_both_suites() {
2539        // From CNSA 1.0 alone.
2540        for svc in [
2541            Service::EcdsaP384Sign,
2542            Service::EcdhP384,
2543            Service::RsaPssSign3072,
2544            Service::Dh3072,
2545        ] {
2546            assert!(
2547                is_allowed(AlgorithmProfile::Migration, svc),
2548                "{svc} (CNSA 1.0) must pass"
2549            );
2550            assert!(
2551                !is_allowed(AlgorithmProfile::Cnsa2, svc),
2552                "{svc} is not CNSA 2.0"
2553            );
2554        }
2555
2556        // From CNSA 2.0 alone.
2557        for svc in [
2558            Service::MlKem1024Encaps,
2559            Service::MlDsa87Sign,
2560            Service::XmssVerify,
2561            Service::Sha512,
2562            Service::LmsSha256M32H10W4Sign,
2563        ] {
2564            assert!(
2565                is_allowed(AlgorithmProfile::Migration, svc),
2566                "{svc} (CNSA 2.0) must pass"
2567            );
2568            assert!(
2569                !is_allowed(AlgorithmProfile::Cnsa1, svc),
2570                "{svc} is not CNSA 1.0"
2571            );
2572        }
2573
2574        // Shared by both — the overlap is AES-256 and SHA-384 with their
2575        // matching-strength support, and nothing else.
2576        for svc in [Service::Aes256Gcm, Service::Sha384, Service::HmacSha384] {
2577            assert!(is_allowed(AlgorithmProfile::Cnsa1, svc));
2578            assert!(is_allowed(AlgorithmProfile::Cnsa2, svc));
2579            assert!(is_allowed(AlgorithmProfile::Migration, svc));
2580        }
2581
2582        // Refused by both, so refused here. This is what makes Migration a
2583        // gate rather than a synonym for Unrestricted.
2584        for svc in [
2585            Service::Sha1,
2586            Service::Sha256,
2587            Service::Aes128Gcm,
2588            Service::RsaPssSign2048,
2589            Service::EcdsaP256Sign,
2590            Service::Ed25519Sign,
2591            // Named by neither suite for a software module.
2592            Service::Sha3_384,
2593            Service::Shake256,
2594            Service::Kmac256,
2595        ] {
2596            assert!(
2597                !is_allowed(AlgorithmProfile::Migration, svc),
2598                "{svc} is in neither suite and must be refused"
2599            );
2600            // Control: Unrestricted does permit it, so the refusal above is
2601            // Migration's doing and not a service that is gated everywhere.
2602            assert!(is_allowed(AlgorithmProfile::Unrestricted, svc));
2603        }
2604    }
2605
2606    #[test]
2607    fn cnsa1_allows_the_2016_suite() {
2608        let allowed = [
2609            Service::Aes256Gcm,
2610            Service::Sha384,
2611            Service::EcdsaP384Sign,
2612            Service::EcdsaP384Verify,
2613            Service::EcdsaP384Keygen,
2614            Service::EcdhP384,
2615            Service::RsaPssSign3072,
2616            Service::RsaOaep3072,
2617            Service::RsaPssSign4096,
2618            Service::Dh3072,
2619            // Supporting services at the suite's own strength.
2620            Service::HmacSha384,
2621            Service::CtrDrbgAes256,
2622            Service::HashDrbgSha384,
2623            Service::KbkdfHmacSha384,
2624        ];
2625        for svc in allowed {
2626            assert!(
2627                is_allowed(AlgorithmProfile::Cnsa1, svc),
2628                "{svc} should be allowed in CNSA 1.0"
2629            );
2630        }
2631    }
2632
2633    #[test]
2634    fn cnsa1_blocks_small_keys_and_ed25519() {
2635        let blocked = [
2636            Service::Sha1,
2637            Service::Sha224,
2638            Service::Aes128Ecb,
2639            Service::Aes192Gcm,
2640            Service::CtrDrbgAes128,
2641            Service::EcdsaP256Sign,
2642            Service::EcdhP256,
2643            Service::Ed25519Sign,
2644            Service::Ed25519Verify,
2645            Service::RsaPssSign2048,
2646            Service::RsaOaep2048,
2647            // Weaker than the suite requires. Annex B names SHA-384 alone;
2648            // SHA-256 appears only in its Additional Information paragraph,
2649            // as a near-term allowance scoped to UNCLASSIFIED data and
2650            // conditioned on NSA consultation for non-P-384 curves — neither
2651            // of which this module can evaluate.
2652            Service::Sha256,
2653            Service::HmacSha256,
2654            Service::HashDrbgSha256,
2655            Service::HkdfSha256,
2656            // SHA-512 belongs to CNSA 2.0; the 2016 suite does not name it.
2657            Service::Sha512,
2658            Service::HmacSha512,
2659            // Named by neither suite.
2660            Service::Sha3_256,
2661            Service::Shake256,
2662            // Post-quantum and stateful hash-based: CNSA 2.0, not CNSA 1.0.
2663            Service::MlKem1024Encaps,
2664            Service::MlDsa87Sign,
2665            Service::XmssVerify,
2666            Service::LmsSha256M32H10W4Sign,
2667        ];
2668        for svc in blocked {
2669            assert!(
2670                !is_allowed(AlgorithmProfile::Cnsa1, svc),
2671                "{svc} should be BLOCKED in CNSA 1.0"
2672            );
2673        }
2674    }
2675
2676    // The full LMS service enumeration, shared by the gating test and the
2677    // discriminant-block test. Explicit rather than generated: CMVP reviewers
2678    // read this list, and a macro would hide exactly what it must show.
2679    #[allow(clippy::too_many_lines)]
2680    fn lms_all_160() -> [Service; 160] {
2681        [
2682            // SHA-256 / N=32 family (20 pairs, 40 entries).
2683            Service::LmsSha256M32H5W1Sign,
2684            Service::LmsSha256M32H5W1Verify,
2685            Service::LmsSha256M32H5W2Sign,
2686            Service::LmsSha256M32H5W2Verify,
2687            Service::LmsSha256M32H5W4Sign,
2688            Service::LmsSha256M32H5W4Verify,
2689            Service::LmsSha256M32H5W8Sign,
2690            Service::LmsSha256M32H5W8Verify,
2691            Service::LmsSha256M32H10W1Sign,
2692            Service::LmsSha256M32H10W1Verify,
2693            Service::LmsSha256M32H10W2Sign,
2694            Service::LmsSha256M32H10W2Verify,
2695            Service::LmsSha256M32H10W4Sign,
2696            Service::LmsSha256M32H10W4Verify,
2697            Service::LmsSha256M32H10W8Sign,
2698            Service::LmsSha256M32H10W8Verify,
2699            Service::LmsSha256M32H15W1Sign,
2700            Service::LmsSha256M32H15W1Verify,
2701            Service::LmsSha256M32H15W2Sign,
2702            Service::LmsSha256M32H15W2Verify,
2703            Service::LmsSha256M32H15W4Sign,
2704            Service::LmsSha256M32H15W4Verify,
2705            Service::LmsSha256M32H15W8Sign,
2706            Service::LmsSha256M32H15W8Verify,
2707            Service::LmsSha256M32H20W1Sign,
2708            Service::LmsSha256M32H20W1Verify,
2709            Service::LmsSha256M32H20W2Sign,
2710            Service::LmsSha256M32H20W2Verify,
2711            Service::LmsSha256M32H20W4Sign,
2712            Service::LmsSha256M32H20W4Verify,
2713            Service::LmsSha256M32H20W8Sign,
2714            Service::LmsSha256M32H20W8Verify,
2715            Service::LmsSha256M32H25W1Sign,
2716            Service::LmsSha256M32H25W1Verify,
2717            Service::LmsSha256M32H25W2Sign,
2718            Service::LmsSha256M32H25W2Verify,
2719            Service::LmsSha256M32H25W4Sign,
2720            Service::LmsSha256M32H25W4Verify,
2721            Service::LmsSha256M32H25W8Sign,
2722            Service::LmsSha256M32H25W8Verify,
2723            // SHA-256 / N=24 family (20 pairs, 40 entries).
2724            Service::LmsSha256M24H5W1Sign,
2725            Service::LmsSha256M24H5W1Verify,
2726            Service::LmsSha256M24H5W2Sign,
2727            Service::LmsSha256M24H5W2Verify,
2728            Service::LmsSha256M24H5W4Sign,
2729            Service::LmsSha256M24H5W4Verify,
2730            Service::LmsSha256M24H5W8Sign,
2731            Service::LmsSha256M24H5W8Verify,
2732            Service::LmsSha256M24H10W1Sign,
2733            Service::LmsSha256M24H10W1Verify,
2734            Service::LmsSha256M24H10W2Sign,
2735            Service::LmsSha256M24H10W2Verify,
2736            Service::LmsSha256M24H10W4Sign,
2737            Service::LmsSha256M24H10W4Verify,
2738            Service::LmsSha256M24H10W8Sign,
2739            Service::LmsSha256M24H10W8Verify,
2740            Service::LmsSha256M24H15W1Sign,
2741            Service::LmsSha256M24H15W1Verify,
2742            Service::LmsSha256M24H15W2Sign,
2743            Service::LmsSha256M24H15W2Verify,
2744            Service::LmsSha256M24H15W4Sign,
2745            Service::LmsSha256M24H15W4Verify,
2746            Service::LmsSha256M24H15W8Sign,
2747            Service::LmsSha256M24H15W8Verify,
2748            Service::LmsSha256M24H20W1Sign,
2749            Service::LmsSha256M24H20W1Verify,
2750            Service::LmsSha256M24H20W2Sign,
2751            Service::LmsSha256M24H20W2Verify,
2752            Service::LmsSha256M24H20W4Sign,
2753            Service::LmsSha256M24H20W4Verify,
2754            Service::LmsSha256M24H20W8Sign,
2755            Service::LmsSha256M24H20W8Verify,
2756            Service::LmsSha256M24H25W1Sign,
2757            Service::LmsSha256M24H25W1Verify,
2758            Service::LmsSha256M24H25W2Sign,
2759            Service::LmsSha256M24H25W2Verify,
2760            Service::LmsSha256M24H25W4Sign,
2761            Service::LmsSha256M24H25W4Verify,
2762            Service::LmsSha256M24H25W8Sign,
2763            Service::LmsSha256M24H25W8Verify,
2764            // SHAKE-256 / N=32 family (20 pairs, 40 entries).
2765            Service::LmsShakeM32H5W1Sign,
2766            Service::LmsShakeM32H5W1Verify,
2767            Service::LmsShakeM32H5W2Sign,
2768            Service::LmsShakeM32H5W2Verify,
2769            Service::LmsShakeM32H5W4Sign,
2770            Service::LmsShakeM32H5W4Verify,
2771            Service::LmsShakeM32H5W8Sign,
2772            Service::LmsShakeM32H5W8Verify,
2773            Service::LmsShakeM32H10W1Sign,
2774            Service::LmsShakeM32H10W1Verify,
2775            Service::LmsShakeM32H10W2Sign,
2776            Service::LmsShakeM32H10W2Verify,
2777            Service::LmsShakeM32H10W4Sign,
2778            Service::LmsShakeM32H10W4Verify,
2779            Service::LmsShakeM32H10W8Sign,
2780            Service::LmsShakeM32H10W8Verify,
2781            Service::LmsShakeM32H15W1Sign,
2782            Service::LmsShakeM32H15W1Verify,
2783            Service::LmsShakeM32H15W2Sign,
2784            Service::LmsShakeM32H15W2Verify,
2785            Service::LmsShakeM32H15W4Sign,
2786            Service::LmsShakeM32H15W4Verify,
2787            Service::LmsShakeM32H15W8Sign,
2788            Service::LmsShakeM32H15W8Verify,
2789            Service::LmsShakeM32H20W1Sign,
2790            Service::LmsShakeM32H20W1Verify,
2791            Service::LmsShakeM32H20W2Sign,
2792            Service::LmsShakeM32H20W2Verify,
2793            Service::LmsShakeM32H20W4Sign,
2794            Service::LmsShakeM32H20W4Verify,
2795            Service::LmsShakeM32H20W8Sign,
2796            Service::LmsShakeM32H20W8Verify,
2797            Service::LmsShakeM32H25W1Sign,
2798            Service::LmsShakeM32H25W1Verify,
2799            Service::LmsShakeM32H25W2Sign,
2800            Service::LmsShakeM32H25W2Verify,
2801            Service::LmsShakeM32H25W4Sign,
2802            Service::LmsShakeM32H25W4Verify,
2803            Service::LmsShakeM32H25W8Sign,
2804            Service::LmsShakeM32H25W8Verify,
2805            // SHAKE-256 / N=24 family (20 pairs, 40 entries).
2806            Service::LmsShakeM24H5W1Sign,
2807            Service::LmsShakeM24H5W1Verify,
2808            Service::LmsShakeM24H5W2Sign,
2809            Service::LmsShakeM24H5W2Verify,
2810            Service::LmsShakeM24H5W4Sign,
2811            Service::LmsShakeM24H5W4Verify,
2812            Service::LmsShakeM24H5W8Sign,
2813            Service::LmsShakeM24H5W8Verify,
2814            Service::LmsShakeM24H10W1Sign,
2815            Service::LmsShakeM24H10W1Verify,
2816            Service::LmsShakeM24H10W2Sign,
2817            Service::LmsShakeM24H10W2Verify,
2818            Service::LmsShakeM24H10W4Sign,
2819            Service::LmsShakeM24H10W4Verify,
2820            Service::LmsShakeM24H10W8Sign,
2821            Service::LmsShakeM24H10W8Verify,
2822            Service::LmsShakeM24H15W1Sign,
2823            Service::LmsShakeM24H15W1Verify,
2824            Service::LmsShakeM24H15W2Sign,
2825            Service::LmsShakeM24H15W2Verify,
2826            Service::LmsShakeM24H15W4Sign,
2827            Service::LmsShakeM24H15W4Verify,
2828            Service::LmsShakeM24H15W8Sign,
2829            Service::LmsShakeM24H15W8Verify,
2830            Service::LmsShakeM24H20W1Sign,
2831            Service::LmsShakeM24H20W1Verify,
2832            Service::LmsShakeM24H20W2Sign,
2833            Service::LmsShakeM24H20W2Verify,
2834            Service::LmsShakeM24H20W4Sign,
2835            Service::LmsShakeM24H20W4Verify,
2836            Service::LmsShakeM24H20W8Sign,
2837            Service::LmsShakeM24H20W8Verify,
2838            Service::LmsShakeM24H25W1Sign,
2839            Service::LmsShakeM24H25W1Verify,
2840            Service::LmsShakeM24H25W2Sign,
2841            Service::LmsShakeM24H25W2Verify,
2842            Service::LmsShakeM24H25W4Sign,
2843            Service::LmsShakeM24H25W4Verify,
2844            Service::LmsShakeM24H25W8Sign,
2845            Service::LmsShakeM24H25W8Verify,
2846        ]
2847    }
2848
2849    #[test]
2850    fn lms_gating_is_exhaustive_across_all_160_variants() {
2851        // Enumerates every LMS Service variant (80 (LMS, LM-OTS) pairs ×
2852        // {Sign, Verify} = 160 entries) and verifies that all 160 are
2853        // gated as CNSA 2.0 permits them and CNSA 1.0 does not: the 2025
2854        // Annex B places no subset restriction on LMS, while the 2016 suite
2855        // names no stateful hash-based scheme at all.
2856        // If a new LMS variant is added without a matching entry in
2857        // `lms_all_160`, the length assertion catches the drift.
2858        let all_160 = lms_all_160();
2859        assert_eq!(all_160.len(), 160, "LMS enumeration drift");
2860
2861        for svc in all_160 {
2862            // Unrestricted: every LMS variant is permitted.
2863            assert!(
2864                is_allowed(AlgorithmProfile::Unrestricted, svc),
2865                "{svc} must be allowed in Unrestricted"
2866            );
2867
2868            // CNSA 2.0: every approved SP 800-208 parameter set is permitted.
2869            // CNSSP-15 Annex B places no subset restriction on LMS.
2870            assert!(
2871                is_allowed(AlgorithmProfile::Cnsa2, svc),
2872                "{svc} must be allowed in CNSA 2.0"
2873            );
2874
2875            // CNSA 1.0: refused. CNSSP-15 (October 2016) Annex B names no
2876            // stateful hash-based signature scheme; LMS arrives with CNSA 2.0.
2877            assert!(
2878                !is_allowed(AlgorithmProfile::Cnsa1, svc),
2879                "{svc} is not in CNSA 1.0 and must be refused"
2880            );
2881
2882            // Migration: permitted, because CNSA 2.0 permits it.
2883            assert!(
2884                is_allowed(AlgorithmProfile::Migration, svc),
2885                "{svc} must be allowed in Migration"
2886            );
2887        }
2888    }
2889
2890    #[test]
2891    fn lms_discriminant_block_is_contiguous_and_pure() {
2892        // `is_lms_service` range-tests `500..=659` instead of enumerating 160
2893        // arms, which is only sound while that block holds every LMS service
2894        // and nothing else. Enum discriminants are unique, so proving the 160
2895        // LMS variants occupy the range exactly also proves no other variant
2896        // can sit inside it.
2897        let mut discriminants = [0u16; 160];
2898        for (slot, svc) in discriminants.iter_mut().zip(lms_all_160()) {
2899            *slot = svc as u16;
2900        }
2901        discriminants.sort_unstable();
2902
2903        // Strictly increasing after sorting == all 160 are distinct. The
2904        // comparison count is asserted too, so an empty or short sweep
2905        // cannot pass this vacuously.
2906        let mut compared = 0usize;
2907        for pair in discriminants.windows(2) {
2908            let [lo, hi] = pair else {
2909                unreachable!("windows(2) yields two-element slices")
2910            };
2911            assert!(hi > lo, "duplicate LMS discriminant {lo}");
2912            compared += 1;
2913        }
2914        assert_eq!(compared, 159, "distinctness sweep did not run in full");
2915
2916        let (Some(&first), Some(&last)) = (discriminants.first(), discriminants.last()) else {
2917            unreachable!("the array holds 160 elements")
2918        };
2919        assert_eq!(first, 500, "LMS block does not start at 500");
2920        assert_eq!(last, 659, "LMS block does not end at 659");
2921        // 160 distinct values spanning an inclusive range of 160 is exactly
2922        // full occupancy — no gap a non-LMS variant could occupy.
2923        assert_eq!(last - first + 1, 160, "LMS block is not contiguous");
2924
2925        for svc in lms_all_160() {
2926            assert!(is_lms_service(svc), "{svc} must be inside the LMS range");
2927        }
2928
2929        // Positive control on the other side of the boundary: the nearest
2930        // non-LMS neighbours must not be swept in by a widened range.
2931        for svc in [
2932            Service::SlhDsaShake256fVerify,
2933            Service::XmssVerify,
2934            Service::Dh3072,
2935        ] {
2936            assert!(!is_lms_service(svc), "{svc} must be outside the LMS range");
2937        }
2938    }
2939
2940    #[test]
2941    fn slh_dsa_gating_is_exhaustive_across_all_36_variants() {
2942        // Enumerates every SLH-DSA Service variant (12 parameter sets ×
2943        // {Keygen, Sign, Verify} = 36 entries) and verifies the
2944        // fail-safe gating: all 36 are blocked under both CNSA profiles,
2945        // because CNSSP-15 lists no SLH-DSA parameter set at all; all 36
2946        // are permitted under Unrestricted.  If a new SLH-DSA variant is
2947        // added without a matching entry in this array, the assertion at
2948        // the end catches the gap.
2949        let all_36 = [
2950            // SHA-2 family (18 entries).
2951            Service::SlhDsaSha2256sKeygen,
2952            Service::SlhDsaSha2256sSign,
2953            Service::SlhDsaSha2256sVerify,
2954            Service::SlhDsaSha2128sKeygen,
2955            Service::SlhDsaSha2128sSign,
2956            Service::SlhDsaSha2128sVerify,
2957            Service::SlhDsaSha2128fKeygen,
2958            Service::SlhDsaSha2128fSign,
2959            Service::SlhDsaSha2128fVerify,
2960            Service::SlhDsaSha2192sKeygen,
2961            Service::SlhDsaSha2192sSign,
2962            Service::SlhDsaSha2192sVerify,
2963            Service::SlhDsaSha2192fKeygen,
2964            Service::SlhDsaSha2192fSign,
2965            Service::SlhDsaSha2192fVerify,
2966            Service::SlhDsaSha2256fKeygen,
2967            Service::SlhDsaSha2256fSign,
2968            Service::SlhDsaSha2256fVerify,
2969            // SHAKE family (18 entries).
2970            Service::SlhDsaShake128sKeygen,
2971            Service::SlhDsaShake128sSign,
2972            Service::SlhDsaShake128sVerify,
2973            Service::SlhDsaShake128fKeygen,
2974            Service::SlhDsaShake128fSign,
2975            Service::SlhDsaShake128fVerify,
2976            Service::SlhDsaShake192sKeygen,
2977            Service::SlhDsaShake192sSign,
2978            Service::SlhDsaShake192sVerify,
2979            Service::SlhDsaShake192fKeygen,
2980            Service::SlhDsaShake192fSign,
2981            Service::SlhDsaShake192fVerify,
2982            Service::SlhDsaShake256sKeygen,
2983            Service::SlhDsaShake256sSign,
2984            Service::SlhDsaShake256sVerify,
2985            Service::SlhDsaShake256fKeygen,
2986            Service::SlhDsaShake256fSign,
2987            Service::SlhDsaShake256fVerify,
2988        ];
2989        assert_eq!(all_36.len(), 36, "SLH-DSA enumeration drift");
2990
2991        for svc in all_36 {
2992            // Unrestricted: every SLH-DSA variant is permitted.
2993            assert!(
2994                is_allowed(AlgorithmProfile::Unrestricted, svc),
2995                "{svc} must be allowed in Unrestricted"
2996            );
2997
2998            // CNSA 1.0: no SLH-DSA variant is permitted.
2999            assert!(
3000                !is_allowed(AlgorithmProfile::Cnsa1, svc),
3001                "{svc} must be BLOCKED in CNSA 1.0"
3002            );
3003
3004            // CNSA 2.0: no SLH-DSA variant is permitted either. CNSSP-15
3005            // contains no SLH-DSA entry, so admitting one would be a
3006            // conformance claim the policy does not support.
3007            assert!(
3008                !is_allowed(AlgorithmProfile::Cnsa2, svc),
3009                "{svc} must be BLOCKED in CNSA 2.0"
3010            );
3011        }
3012    }
3013
3014    #[test]
3015    fn require_allowed_returns_restricted_error_under_cnsa2() {
3016        let _guard = reset_for_test();
3017        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
3018        match require_allowed(Service::Aes128Ecb) {
3019            Err(Error::AlgorithmRestricted {
3020                service: Service::Aes128Ecb,
3021            }) => {}
3022            other => panic!("expected AlgorithmRestricted(Aes128Ecb), got {other:?}"),
3023        }
3024    }
3025
3026    #[test]
3027    fn require_allowed_passes_permitted_services() {
3028        let _guard = reset_for_test();
3029        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
3030        require_allowed(Service::Aes256Gcm).unwrap();
3031        require_allowed(Service::Sha384).unwrap();
3032        require_allowed(Service::MlKem1024Encaps).unwrap();
3033    }
3034
3035    #[test]
3036    fn require_allowed_passes_everything_in_unrestricted() {
3037        let _guard = reset_for_test();
3038        initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
3039        // Spot-check a few from each end of the spectrum.
3040        require_allowed(Service::Sha1).unwrap();
3041        require_allowed(Service::Aes128Ecb).unwrap();
3042        require_allowed(Service::Ed25519Sign).unwrap();
3043        require_allowed(Service::MlKem1024Encaps).unwrap();
3044    }
3045
3046    /// Entry-point coverage for the SLH-DSA correction. The exhaustive
3047    /// `slh_dsa_gating_*` test calls the private `is_allowed` helper directly,
3048    /// so it says nothing about whether the public gate reaches that helper.
3049    /// This one goes through `require_allowed`, which is what every approved
3050    /// service actually calls.
3051    #[test]
3052    fn require_allowed_refuses_every_slh_dsa_service_under_cnsa2() {
3053        let guard = reset_for_test();
3054        for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Cnsa1] {
3055            guard.reset();
3056            initialize_with_profile(UNSIGNED_TEST_BINARY, &[], profile).unwrap();
3057            for svc in [
3058                Service::SlhDsaSha2256sKeygen,
3059                Service::SlhDsaSha2256sSign,
3060                Service::SlhDsaSha2256sVerify,
3061                Service::SlhDsaShake128fVerify,
3062            ] {
3063                match require_allowed(svc) {
3064                    Err(Error::AlgorithmRestricted { service }) => assert_eq!(service, svc),
3065                    other => {
3066                        panic!("expected AlgorithmRestricted({svc}) under {profile}, got {other:?}")
3067                    }
3068                }
3069            }
3070        }
3071    }
3072
3073    /// Entry-point coverage for the LMS correction, on the `M=24` sets.
3074    /// CNSSP-15 (March 2025) Annex B recommends LMS-SHA-256/192, which is
3075    /// `M=24`, so these are the parameter sets the policy names by name.
3076    ///
3077    /// CNSA 1.0 is absent from this list deliberately: the 2016 suite names
3078    /// no stateful hash-based scheme, so LMS is refused there and permitted
3079    /// under Migration instead.
3080    #[test]
3081    fn require_allowed_admits_lms_m24_under_cnsa2_and_migration() {
3082        let guard = reset_for_test();
3083        for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Migration] {
3084            guard.reset();
3085            initialize_with_profile(UNSIGNED_TEST_BINARY, &[], profile).unwrap();
3086            for svc in [
3087                Service::LmsSha256M24H5W1Sign,
3088                Service::LmsSha256M24H25W8Verify,
3089                Service::LmsShakeM24H10W4Sign,
3090                Service::LmsShakeM24H25W8Verify,
3091            ] {
3092                require_allowed(svc)
3093                    .unwrap_or_else(|e| panic!("{svc} must be allowed under {profile}: {e:?}"));
3094            }
3095        }
3096    }
3097
3098    /// Both edges of the LMS discriminant block, over raw values.
3099    ///
3100    /// The block sits at the very top of the enum, so a widened *upper* bound
3101    /// admits nothing today and no `Service`-typed probe can see it — moving
3102    /// the bound to 709 leaves the whole suite green. Testing the arithmetic
3103    /// directly is what makes the upper edge checkable at all.
3104    #[test]
3105    fn lms_block_boundaries_are_exact() {
3106        assert!(!lms_block_contains(499), "499 is below the LMS block");
3107        assert!(lms_block_contains(500), "500 is the first LMS discriminant");
3108        assert!(lms_block_contains(659), "659 is the last LMS discriminant");
3109        assert!(!lms_block_contains(660), "660 is above the LMS block");
3110
3111        // ...and the block the arithmetic describes is the one the enum has.
3112        assert_eq!(Service::LmsSha256M32H5W1Sign as u16, 500);
3113        assert_eq!(Service::LmsShakeM24H25W8Verify as u16, 659);
3114    }
3115
3116    /// The two CNSA profiles must be told apart at the entry point. Every other
3117    /// `require_allowed` test uses a service the profiles treat alike, so a bug
3118    /// wiring `Cnsa1` through as `Cnsa2` would survive them all. P-384 ECDSA is
3119    /// the discriminator: CNSA 1.0 permits it, CNSA 2.0 does not.
3120    #[test]
3121    fn require_allowed_distinguishes_cnsa1_from_cnsa2() {
3122        let guard = reset_for_test();
3123
3124        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa1).unwrap();
3125        require_allowed(Service::EcdsaP384Sign)
3126            .unwrap_or_else(|e| panic!("P-384 ECDSA must be permitted under CNSA 1.0: {e:?}"));
3127
3128        guard.reset();
3129        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
3130        match require_allowed(Service::EcdsaP384Sign) {
3131            Err(Error::AlgorithmRestricted {
3132                service: Service::EcdsaP384Sign,
3133            }) => {}
3134            other => panic!("expected P-384 ECDSA refused under CNSA 2.0, got {other:?}"),
3135        }
3136    }
3137
3138    #[test]
3139    fn algorithm_profile_display() {
3140        assert_eq!(AlgorithmProfile::Unrestricted.to_string(), "Unrestricted");
3141        assert_eq!(AlgorithmProfile::Cnsa2.to_string(), "CNSA 2.0");
3142        assert_eq!(AlgorithmProfile::Cnsa1.to_string(), "CNSA 1.0");
3143    }
3144
3145    #[test]
3146    fn algorithm_restricted_error_display() {
3147        let _guard = reset_for_test();
3148        initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
3149        let err = Error::AlgorithmRestricted {
3150            service: Service::Aes128Ecb,
3151        };
3152        let msg = err.to_string();
3153        assert!(msg.contains("AES-128-ECB"), "got: {msg}");
3154        assert!(msg.contains("CNSA 2.0"), "got: {msg}");
3155    }
3156
3157    #[test]
3158    fn not_implemented_error_display() {
3159        let err = Error::NotImplemented;
3160        assert_eq!(err.to_string(), "algorithm not yet implemented");
3161    }
3162
3163    #[test]
3164    fn service_display_is_stable() {
3165        // Pin a few representative Display strings.
3166        assert_eq!(Service::Sha256.to_string(), "SHA-256");
3167        assert_eq!(Service::Aes256Gcm.to_string(), "AES-256-GCM");
3168        assert_eq!(Service::MlKem1024Encaps.to_string(), "ML-KEM-1024 encaps");
3169        assert_eq!(Service::Ed25519Sign.to_string(), "Ed25519 sign");
3170    }
3171}