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() ┌───────────┐  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 every approved
33//!   algorithm's power-up KAT before opening the service
34//!   interface. [`initialize`] is a thin wrapper for the empty
35//!   registry case (used only by this crate's own unit tests).
36//! - [`require_operational`] — the gate that every approved
37//!   service in every other crate calls on entry. Returns
38//!   [`Error::NotOperational`] (with the observed state) on any
39//!   pre-`Operational` or post-`Error` call.
40//! - [`enter_error_state`] — irreversible transition into the
41//!   terminal error state, called by conditional self-tests
42//!   (pairwise consistency, DRBG health) when they detect a
43//!   failure.
44//! - [`KatEntry`] and the [`SelfTest`] trait — the registry
45//!   shape that algorithm crates use to expose their power-up
46//!   KATs.
47//! - [`AlgorithmProfile`] — runtime selection of an algorithm
48//!   restriction policy (Unrestricted, CNSA 2.0, CNSA 1.0).
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 validated 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//! Three 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) — only quantum-resistant
77//!   algorithms: AES-256, SHA-384/512, ML-KEM-1024, ML-DSA-87,
78//!   LMS, XMSS, plus SHA3-384/512 and 256-bit SP 800-185
79//!   variants.
80//! - **CNSA 1.0** — classical algorithms for the transition
81//!   period: AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072,
82//!   DH >= 3072.
83//!
84//! Profiles nest: CNSA 2.0 is the most restrictive, CNSA 1.0
85//! is intermediate, Unrestricted allows everything. KATs still
86//! run all algorithms regardless of profile — the profile
87//! only restricts post-initialization service access.
88//!
89//! # Sensitive security parameters (SSPs)
90//!
91//! This crate holds **no SSPs** of its own. It only gates
92//! access to other crates that do. Secret material lives in the
93//! algorithm crates that own it (e.g. `oxicrypt-rsa`'s
94//! `RsaPrivateKey2048`, `oxicrypt-drbg`'s DRBG states). The error
95//! latch here does not perform SSP zeroization on its own — each
96//! owning crate is responsible for ensuring its SSPs are
97//! dropped when the process restarts following a transition into
98//! the `Error` state.
99//!
100//! # FIPS 140-3 / SP 800-140B mapping
101//!
102//! | SP 800-140B / IG clause | Implementation |
103//! |-------------------------|----------------|
104//! | §7.10 power-up self-tests | [`initialize_with_tests`] runs every registered [`KatEntry`] sequentially; the first failure latches [`State::Error`]. |
105//! | §7.10 conditional self-tests | Algorithm crates call [`enter_error_state`] on detecting a pairwise-consistency or DRBG-health failure. |
106//! | IG 9.5.A approved-mode indicator | [`is_operational`] / [`state`] — queryable at runtime. |
107//! | IG 10.3.A software integrity | Delegated to `oxicrypt-integrity` (separate crate), wired in as the first KAT by the top-level caller. |
108//!
109//! # Thread safety
110//!
111//! State is stored in a single `AtomicU8` and is safe to read
112//! from any thread. [`initialize_with_tests`] uses a
113//! compare-and-swap to guarantee the self-test phase runs
114//! exactly once per process lifetime even under concurrent
115//! first-calls; racing losers spin on the `SelfTest` state byte
116//! so they do not observe the transient phase and then receive
117//! [`Error::AlreadyInitialized`].
118
119#![no_std]
120#![forbid(unsafe_code)]
121
122use core::fmt;
123use core::sync::atomic::{AtomicU8, Ordering};
124
125/// Module lifecycle state per FIPS 140-3 Section 7.2.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127#[repr(u8)]
128pub enum State {
129    /// Module has not yet been initialized. No services are available.
130    PowerOff = 0,
131    /// Power-up self-tests are currently executing. No services are
132    /// available. This state is transient.
133    SelfTest = 1,
134    /// All power-up self-tests have passed. Approved services are
135    /// available.
136    Operational = 2,
137    /// A self-test has failed (either a power-up KAT or a conditional
138    /// test). This state is **terminal** within a process: no further
139    /// services are offered and all cached secret material must be
140    /// treated as zeroized. The only recovery is to restart the
141    /// containing process.
142    Error = 3,
143}
144
145impl State {
146    const fn from_u8(raw: u8) -> Self {
147        match raw {
148            0 => Self::PowerOff,
149            1 => Self::SelfTest,
150            2 => Self::Operational,
151            // Any unknown discriminant collapses to `Error` so that a
152            // corrupted state byte can never be interpreted as
153            // `Operational`. This is defence-in-depth: `AtomicU8`
154            // should only ever hold values written through this
155            // module.
156            _ => Self::Error,
157        }
158    }
159}
160
161impl fmt::Display for State {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        let s = match self {
164            Self::PowerOff => "PowerOff",
165            Self::SelfTest => "SelfTest",
166            Self::Operational => "Operational",
167            Self::Error => "Error",
168        };
169        f.write_str(s)
170    }
171}
172
173/// Global module state. Written only by [`initialize`] and
174/// [`enter_error_state`]; read by [`state`] and [`require_operational`].
175static STATE: AtomicU8 = AtomicU8::new(State::PowerOff as u8);
176
177/// Returns the current module state.
178pub fn state() -> State {
179    State::from_u8(STATE.load(Ordering::Acquire))
180}
181
182/// Returns `true` iff the module has passed its power-up self-tests and is
183/// currently in the `Operational` state.
184pub fn is_operational() -> bool {
185    state() == State::Operational
186}
187
188/// Guard used at the entry point of every approved service. Returns
189/// `Ok(())` only when the module is `Operational`; otherwise returns a
190/// [`Error::NotOperational`] describing the current state so callers
191/// cannot accidentally ignore it.
192pub fn require_operational() -> Result<(), Error> {
193    let current = state();
194    if current == State::Operational {
195        Ok(())
196    } else {
197        Err(Error::NotOperational { current })
198    }
199}
200
201/// Errors surfaced by the module boundary.
202///
203/// Every variant is constructed with a `&'static str` description so
204/// that formatting is allocation-free; this lets the module report
205/// failures even from `no_std` contexts and from within the self-test
206/// runner itself.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum Error {
209    /// A service was invoked while the module was not in the
210    /// `Operational` state.
211    NotOperational {
212        /// The state the module was actually in when the call arrived.
213        current: State,
214    },
215    /// A power-up self-test failed. The module is now in the `Error`
216    /// state.
217    SelfTestFailed {
218        /// Name of the failing test, supplied by its
219        /// [`SelfTest`] implementation.
220        test: &'static str,
221    },
222    /// A conditional self-test (e.g. DRBG CRNGT, pairwise consistency)
223    /// failed during operation. The module is now in the `Error` state.
224    ConditionalTestFailed {
225        /// Short description of the failing check.
226        reason: &'static str,
227    },
228    /// [`initialize`] was called while the module was already past
229    /// `PowerOff`. This is not itself a FIPS violation — it just means
230    /// a second caller raced with the first. The first call's outcome
231    /// is authoritative; the race loser should read [`state`] to find
232    /// out.
233    AlreadyInitialized,
234    /// A cryptographic service was called with input bytes that do not
235    /// encode a valid domain element (for example, a scalar that is
236    /// zero or not less than the group order, or a point that is not
237    /// a canonical encoding on the curve). This is distinct from a
238    /// self-test failure: the module is still operational; the caller
239    /// supplied out-of-range bytes. Algorithm primitives should return
240    /// this rather than silently substituting a default.
241    InvalidInput,
242    /// A service was invoked that is not permitted under the active
243    /// [`AlgorithmProfile`]. The module is still operational — the
244    /// algorithm exists but is restricted by policy. Switch to a
245    /// less restrictive profile (which requires a process restart)
246    /// or use an alternative algorithm that is permitted.
247    AlgorithmRestricted {
248        /// The specific service that was blocked.
249        service: Service,
250    },
251    /// A service was invoked for an algorithm that exists in the
252    /// [`Service`] enum but whose implementation has not yet been
253    /// completed. Stub crates return this. The algorithm is
254    /// recognized by the profile system and its gate will pass if
255    /// the profile permits it, but the actual cryptographic
256    /// operation is not yet available.
257    NotImplemented,
258}
259
260impl fmt::Display for Error {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            Self::NotOperational { current } => {
264                write!(f, "FIPS module not operational (state = {current})")
265            }
266            Self::SelfTestFailed { test } => {
267                write!(f, "FIPS power-up self-test failed: {test}")
268            }
269            Self::ConditionalTestFailed { reason } => {
270                write!(f, "FIPS conditional self-test failed: {reason}")
271            }
272            Self::AlreadyInitialized => f.write_str("FIPS module already initialized"),
273            Self::InvalidInput => f.write_str("invalid input to FIPS service"),
274            Self::AlgorithmRestricted { service } => {
275                write!(
276                    f,
277                    "algorithm {service} is not permitted under the {} profile",
278                    active_profile()
279                )
280            }
281            Self::NotImplemented => f.write_str("algorithm not yet implemented"),
282        }
283    }
284}
285
286/// Marker returned by a failing [`SelfTest::run`].
287///
288/// Self-tests are allowed to describe *what* failed, but they are not
289/// allowed to exfiltrate the intermediate values that triggered the
290/// failure — including them in an error payload would potentially leak
291/// information about internal state. The runner treats the mere
292/// presence of this value as "test failed" and converts it into
293/// [`Error::SelfTestFailed`] using the test's own [`SelfTest::NAME`].
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub struct SelfTestFailure;
296
297impl fmt::Display for SelfTestFailure {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        f.write_str("self-test failure")
300    }
301}
302
303/// A single power-up self-test.
304///
305/// Each approved algorithm crate will implement this trait for at least
306/// one test vector, per SP 800-140B. The [`initialize`] routine runs
307/// every registered test sequentially before the module can leave the
308/// `SelfTest` state.
309///
310/// Implementations **must** be deterministic, **must not** allocate,
311/// and **must not** read from any input the caller controls: the
312/// whole point of a KAT is that its inputs and expected outputs are
313/// baked into the module binary.
314pub trait SelfTest {
315    /// Stable, human-readable identifier for this test. Appears in
316    /// [`Error::SelfTestFailed`] and in audit logs.
317    const NAME: &'static str;
318
319    /// Runs the test. Returns `Ok(())` on success, `Err(SelfTestFailure)`
320    /// on any mismatch. The runner converts the `Err` into
321    /// [`Error::SelfTestFailed`] using [`Self::NAME`].
322    fn run() -> Result<(), SelfTestFailure>;
323}
324
325/// A power-up KAT registered with the module.
326///
327/// Algorithm crates expose one or more `KatEntry` values that the
328/// top-level caller passes into [`initialize_with_tests`] at startup.
329/// The runner executes every entry sequentially before the module
330/// leaves the `SelfTest` state; a single failure latches the module
331/// into `Error` for the remainder of the process lifetime, per
332/// FIPS 140-3 Section 7.10 and SP 800-140B.
333///
334/// The function pointer signature takes no inputs on purpose: a KAT
335/// must run against compile-time-baked vectors only, never against
336/// caller-supplied data.
337#[derive(Debug, Clone, Copy)]
338pub struct KatEntry {
339    /// Stable identifier for the test. Surfaces in
340    /// [`Error::SelfTestFailed`] and audit logs. Must match the
341    /// [`SelfTest::NAME`] used for the underlying vector.
342    pub name: &'static str,
343    /// The test routine itself.
344    pub run: fn() -> Result<(), SelfTestFailure>,
345}
346
347/// Initializes the module with an empty KAT registry.
348///
349/// Convenience wrapper around [`initialize_with_tests`] for contexts
350/// that want to exercise only the state machine (notably
351/// `fips-module`'s own unit tests). Production callers of this crate
352/// must use [`initialize_with_tests`] with the full approved-service
353/// KAT set — a FIPS module that ships no self-tests is not compliant
354/// with SP 800-140B regardless of whether the state machine runs.
355pub fn initialize() -> Result<(), Error> {
356    initialize_with_tests(&[])
357}
358
359/// Initializes the module and runs every supplied power-up KAT.
360///
361/// This is the canonical entry point. It transitions
362/// `PowerOff -> SelfTest`, executes every entry in `tests` in order,
363/// and then transitions to `Operational` on success or to the
364/// terminal `Error` state on the first failure. The returned
365/// [`Error::SelfTestFailed`] carries the name of the failing test.
366///
367/// Concurrent calls are safe: exactly one caller wins the
368/// `PowerOff -> SelfTest` CAS; losers receive
369/// [`Error::AlreadyInitialized`] and should consult [`state`] to
370/// discover whether the winning call succeeded.
371///
372/// Note: the slice is passed **by reference**, not built up by magic.
373/// This repository deliberately avoids linker-section registration
374/// tricks: the full set of power-up tests is visible in source at
375/// every call site, which makes it straightforward to audit the
376/// module's test inventory against the Security Policy.
377pub fn initialize_with_tests(tests: &[KatEntry]) -> Result<(), Error> {
378    // Try to claim the SelfTest phase. Only the first caller in the
379    // process lifetime succeeds.
380    let cas = STATE.compare_exchange(
381        State::PowerOff as u8,
382        State::SelfTest as u8,
383        Ordering::AcqRel,
384        Ordering::Acquire,
385    );
386    if cas.is_err() {
387        // Another thread is running (or has run) the self-tests. If
388        // it is still inside `SelfTest`, spin until it publishes a
389        // terminal state so racing callers don't observe the
390        // transient phase and trip `require_operational`.
391        while STATE.load(Ordering::Acquire) == State::SelfTest as u8 {
392            core::hint::spin_loop();
393        }
394        return Err(Error::AlreadyInitialized);
395    }
396
397    for entry in tests {
398        let result = (entry.run)();
399        if result.is_err() {
400            // Latch the module into Error before returning. Any
401            // subsequent service call will be rejected by
402            // require_operational().
403            STATE.store(State::Error as u8, Ordering::Release);
404            return Err(Error::SelfTestFailed { test: entry.name });
405        }
406    }
407
408    STATE.store(State::Operational as u8, Ordering::Release);
409    Ok(())
410}
411
412/// Forces the module into the terminal `Error` state.
413///
414/// Called by conditional self-tests (DRBG CRNGT, pairwise consistency
415/// on keygen, etc.) when they detect a failure. Once invoked the
416/// module will reject every subsequent [`require_operational`] call
417/// for the remainder of the process lifetime.
418///
419/// This function is intentionally irreversible: FIPS 140-3 requires
420/// that a module which has entered an error condition stay there
421/// until it is re-initialized, and our "re-initialization" is a
422/// process restart.
423pub fn enter_error_state(_reason: &'static str) {
424    STATE.store(State::Error as u8, Ordering::Release);
425}
426
427// =========================================================================
428// Algorithm profile gating (CNSA 2.0 / CNSA 1.0)
429// =========================================================================
430
431/// Algorithm restriction profile selected at module initialization.
432///
433/// A single validated binary can serve general FIPS 140-3 consumers
434/// (`Unrestricted`) and CNSA-restricted deployments (`Cnsa2`,
435/// `Cnsa1`) by choosing the appropriate profile at init time.
436///
437/// The profile is immutable once set: it is stored alongside the
438/// module `State` before the first KAT runs and cannot be changed
439/// without restarting the process (which resets the state machine).
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441#[repr(u8)]
442pub enum AlgorithmProfile {
443    /// All FIPS-approved algorithms are available. This is the
444    /// default and preserves backward compatibility with callers
445    /// that use [`initialize_with_tests`].
446    Unrestricted = 0,
447    /// CNSA 2.0 (CNSSP 15): quantum-resistant algorithms only.
448    /// AES-256, SHA-384/512, ML-KEM-1024, ML-DSA-87, LMS, XMSS.
449    /// SHA3-384/512 and 256-bit SP 800-185 variants are also
450    /// allowed. All other algorithms return
451    /// [`Error::AlgorithmRestricted`].
452    Cnsa2 = 1,
453    /// CNSA 1.0: classical algorithms for the transition period.
454    /// AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072, DH >= 3072.
455    Cnsa1 = 2,
456}
457
458impl AlgorithmProfile {
459    const fn from_u8(raw: u8) -> Self {
460        match raw {
461            0 => Self::Unrestricted,
462            2 => Self::Cnsa1,
463            // Unknown discriminant (including 1 = Cnsa2) defaults to
464            // the most restrictive profile as defence-in-depth.
465            _ => Self::Cnsa2,
466        }
467    }
468}
469
470impl fmt::Display for AlgorithmProfile {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        let s = match self {
473            Self::Unrestricted => "Unrestricted",
474            Self::Cnsa2 => "CNSA 2.0",
475            Self::Cnsa1 => "CNSA 1.0",
476        };
477        f.write_str(s)
478    }
479}
480
481/// Global algorithm profile. Written once by [`initialize_with_profile`];
482/// read by [`require_allowed`] and [`active_profile`].
483static PROFILE: AtomicU8 = AtomicU8::new(AlgorithmProfile::Unrestricted as u8);
484
485/// Returns the active algorithm profile.
486pub fn active_profile() -> AlgorithmProfile {
487    AlgorithmProfile::from_u8(PROFILE.load(Ordering::Acquire))
488}
489
490/// Initializes the module with a specific algorithm profile and runs
491/// every supplied power-up KAT.
492///
493/// This is the profile-aware entry point. It stores the selected
494/// profile before running KATs, then transitions to `Operational` on
495/// success. KATs always run all algorithms regardless of the profile —
496/// the profile only restricts post-initialization service access via
497/// [`require_allowed`].
498///
499/// See [`initialize_with_tests`] for the backward-compatible wrapper
500/// that uses [`AlgorithmProfile::Unrestricted`].
501pub fn initialize_with_profile(tests: &[KatEntry], profile: AlgorithmProfile) -> Result<(), Error> {
502    // Store the profile before running KATs. This is safe even if
503    // initialization fails: a failed init latches State::Error, so
504    // no service call can reach require_allowed() anyway.
505    PROFILE.store(profile as u8, Ordering::Release);
506    initialize_with_tests(tests)
507}
508
509/// Enumeration of every approved service in the module, at the
510/// algorithm-and-parameter level.
511///
512/// Each variant represents a specific algorithm instantiation (e.g.
513/// `Aes128Ecb` not just `Aes`). This granularity is necessary
514/// because CNSA restrictions operate at the key-size and hash-size
515/// level.
516///
517/// Algorithm crates pass the appropriate `Service` variant to
518/// [`require_allowed`] at their public entry points.
519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520#[repr(u16)]
521#[allow(missing_docs)]
522pub enum Service {
523    // ----- oxicrypt-sha: FIPS 180-4 / FIPS 202 -----
524    Sha1 = 0,
525    Sha224 = 1,
526    Sha256 = 2,
527    Sha384 = 3,
528    Sha512 = 4,
529    Sha512_224 = 5,
530    Sha512_256 = 6,
531
532    Sha3_224 = 10,
533    Sha3_256 = 11,
534    Sha3_384 = 12,
535    Sha3_512 = 13,
536
537    // ----- oxicrypt-xof: FIPS 202 / SP 800-185 -----
538    Shake128 = 20,
539    Shake256 = 21,
540    CShake128 = 22,
541    CShake256 = 23,
542    Kmac128 = 24,
543    Kmac256 = 25,
544    KmacXof128 = 26,
545    KmacXof256 = 27,
546    TupleHash128 = 28,
547    TupleHash256 = 29,
548    TupleHashXof128 = 30,
549    TupleHashXof256 = 31,
550    ParallelHash128 = 32,
551    ParallelHash256 = 33,
552    ParallelHashXof128 = 34,
553    ParallelHashXof256 = 35,
554
555    // ----- oxicrypt-hmac: FIPS 198-1 -----
556    HmacSha1 = 40,
557    HmacSha224 = 41,
558    HmacSha256 = 42,
559    HmacSha384 = 43,
560    HmacSha512 = 44,
561    HmacSha512_224 = 45,
562    HmacSha512_256 = 46,
563    HmacSha3_224 = 47,
564    HmacSha3_256 = 48,
565    HmacSha3_384 = 49,
566    HmacSha3_512 = 50,
567
568    // ----- oxicrypt-cmac: SP 800-38B -----
569    CmacAes128 = 60,
570    CmacAes192 = 61,
571    CmacAes256 = 62,
572
573    // ----- oxicrypt-aes: FIPS 197 / SP 800-38A/D/C/F/Fp -----
574    Aes128Ecb = 70,
575    Aes128Cbc = 71,
576    Aes128Ctr = 72,
577    Aes128Gcm = 73,
578    Aes128Ccm = 74,
579    Aes128Kw = 75,
580    Aes128Kwp = 76,
581    Aes192Ecb = 80,
582    Aes192Cbc = 81,
583    Aes192Ctr = 82,
584    Aes192Gcm = 83,
585    Aes192Ccm = 84,
586    Aes192Kw = 85,
587    Aes192Kwp = 86,
588    Aes256Ecb = 90,
589    Aes256Cbc = 91,
590    Aes256Ctr = 92,
591    Aes256Gcm = 93,
592    Aes256Ccm = 94,
593    Aes256Kw = 95,
594    Aes256Kwp = 96,
595
596    // Key-construction gates — checked when building an AES key
597    // object before the caller selects a mode.  All per-mode
598    // variants of the same key size share identical profile
599    // permissions, so a single gate at key construction is
600    // sufficient.
601    /// AES-128 key construction (gates all AES-128 mode services).
602    Aes128 = 97,
603    /// AES-192 key construction (gates all AES-192 mode services).
604    Aes192 = 98,
605    /// AES-256 key construction (gates all AES-256 mode services).
606    Aes256 = 99,
607
608    // ----- oxicrypt-drbg: SP 800-90A -----
609    CtrDrbgAes128 = 100,
610    CtrDrbgAes192 = 101,
611    CtrDrbgAes256 = 102,
612    HashDrbgSha256 = 103,
613    HashDrbgSha384 = 104,
614    HashDrbgSha512 = 105,
615    HmacDrbgSha256 = 106,
616    HmacDrbgSha384 = 107,
617    HmacDrbgSha512 = 108,
618
619    // ----- oxicrypt-kdf: SP 800-56C / SP 800-108 / SP 800-132 -----
620    HkdfSha1 = 120,
621    HkdfSha256 = 121,
622    HkdfSha384 = 122,
623    HkdfSha512 = 123,
624    KbkdfHmacSha256 = 130,
625    KbkdfHmacSha384 = 131,
626    KbkdfHmacSha512 = 132,
627    KbkdfCmacAes128 = 133,
628    KbkdfCmacAes192 = 134,
629    KbkdfCmacAes256 = 135,
630    Pbkdf2HmacSha1 = 140,
631    Pbkdf2HmacSha224 = 141,
632    Pbkdf2HmacSha256 = 142,
633    Pbkdf2HmacSha384 = 143,
634    Pbkdf2HmacSha512 = 144,
635
636    // ----- oxicrypt-rsa: FIPS 186-5 / SP 800-56Br2 -----
637    RsaKeygen2048 = 150,
638    RsaPkcs1v15Sign2048 = 151,
639    RsaPssSign2048 = 152,
640    RsaOaep2048 = 153,
641    RsaPkcs1v15Verify2048 = 154,
642    RsaPssVerify2048 = 155,
643    RsaKeygen3072 = 160,
644    RsaPkcs1v15Sign3072 = 161,
645    RsaPssSign3072 = 162,
646    RsaOaep3072 = 163,
647    RsaPkcs1v15Verify3072 = 164,
648    RsaPssVerify3072 = 165,
649    RsaKeygen4096 = 170,
650    RsaPkcs1v15Sign4096 = 171,
651    RsaPssSign4096 = 172,
652    RsaOaep4096 = 173,
653    RsaPkcs1v15Verify4096 = 174,
654    RsaPssVerify4096 = 175,
655
656    // ----- oxicrypt-ecdsa: FIPS 186-5 -----
657    EcdsaP256Sign = 200,
658    EcdsaP256Verify = 201,
659    EcdsaP256Keygen = 202,
660    EcdsaP384Sign = 210,
661    EcdsaP384Verify = 211,
662    EcdsaP384Keygen = 212,
663
664    // ----- oxicrypt-ecdh: SP 800-56Ar3 -----
665    EcdhP256 = 220,
666    EcdhP384 = 221,
667
668    // ----- oxicrypt-eddsa: FIPS 186-5 / RFC 8032 -----
669    Ed25519Sign = 230,
670    Ed25519Verify = 231,
671    Ed25519Keygen = 232,
672
673    // ----- oxicrypt-tls-kdf: SP 800-135r1 + RFC 8446 §7.1 -----
674    Tls12Kdf = 240,
675    Tls13Kdf = 241,
676
677    // ----- oxicrypt-ml-kem: FIPS 203 -----
678    MlKem1024Encaps = 300,
679    MlKem1024Decaps = 301,
680    MlKem1024Keygen = 302,
681    MlKem512Encaps = 303,
682    MlKem512Decaps = 304,
683    MlKem512Keygen = 305,
684    MlKem768Encaps = 306,
685    MlKem768Decaps = 307,
686    MlKem768Keygen = 308,
687
688    // ----- oxicrypt-ml-dsa: FIPS 204 -----
689    MlDsa87Sign = 310,
690    MlDsa87Verify = 311,
691    MlDsa87Keygen = 312,
692    MlDsa44Sign = 313,
693    MlDsa44Verify = 314,
694    MlDsa44Keygen = 315,
695    MlDsa65Sign = 316,
696    MlDsa65Verify = 317,
697    MlDsa65Keygen = 318,
698
699    // ----- oxicrypt-slh-dsa: FIPS 205 (12 parameter sets × 3 ops = 36 variants) -----
700    // CNSA 2.0 baseline first: SHA-2 256s (CNSSP-15 mandate) claims 320-322,
701    // then the rest of the SHA-2 family (128s/f, 192s/f, 256f), then SHAKE family.
702    SlhDsaSha2256sKeygen = 320,
703    SlhDsaSha2256sSign = 321,
704    SlhDsaSha2256sVerify = 322,
705    SlhDsaSha2128sKeygen = 323,
706    SlhDsaSha2128sSign = 324,
707    SlhDsaSha2128sVerify = 325,
708    SlhDsaSha2128fKeygen = 326,
709    SlhDsaSha2128fSign = 327,
710    SlhDsaSha2128fVerify = 328,
711    SlhDsaSha2192sKeygen = 329,
712    SlhDsaSha2192sSign = 330,
713    SlhDsaSha2192sVerify = 331,
714    SlhDsaSha2192fKeygen = 332,
715    SlhDsaSha2192fSign = 333,
716    SlhDsaSha2192fVerify = 334,
717    SlhDsaSha2256fKeygen = 335,
718    SlhDsaSha2256fSign = 336,
719    SlhDsaSha2256fVerify = 337,
720    SlhDsaShake128sKeygen = 338,
721    SlhDsaShake128sSign = 339,
722    SlhDsaShake128sVerify = 340,
723    SlhDsaShake128fKeygen = 341,
724    SlhDsaShake128fSign = 342,
725    SlhDsaShake128fVerify = 343,
726    SlhDsaShake192sKeygen = 344,
727    SlhDsaShake192sSign = 345,
728    SlhDsaShake192sVerify = 346,
729    SlhDsaShake192fKeygen = 347,
730    SlhDsaShake192fSign = 348,
731    SlhDsaShake192fVerify = 349,
732    SlhDsaShake256sKeygen = 350,
733    SlhDsaShake256sSign = 351,
734    SlhDsaShake256sVerify = 352,
735    SlhDsaShake256fKeygen = 353,
736    SlhDsaShake256fSign = 354,
737    SlhDsaShake256fVerify = 355,
738
739    // ----- oxicrypt-lms: SP 800-208 (RFC 8554 / RFC 8708) -----
740    // 160 per-pair entries (80 pairs × Sign + Verify), discriminants 500-659.
741    // Layout: 500-539 SHA-256/M=32, 540-579 SHA-256/M=24,
742    // 580-619 SHAKE/M=32, 620-659 SHAKE/M=24.
743    // Within each family: (H ascending, W ascending), Sign/Verify alternating.
744    // The 8 CNSA-2 permitted pairs (SHA-256/M=32 H{10,15,20,25} W{4,8}) are
745    // flagged inline; all other 72 pairs are Unrestricted-only.
746
747    // SHA-256 / N=32 family (RFC 8554 §A.1+§A.2)
748    LmsSha256M32H5W1Sign = 500,
749    LmsSha256M32H5W1Verify = 501,
750    LmsSha256M32H5W2Sign = 502,
751    LmsSha256M32H5W2Verify = 503,
752    LmsSha256M32H5W4Sign = 504,
753    LmsSha256M32H5W4Verify = 505,
754    LmsSha256M32H5W8Sign = 506,
755    LmsSha256M32H5W8Verify = 507,
756    LmsSha256M32H10W1Sign = 508,
757    LmsSha256M32H10W1Verify = 509,
758    LmsSha256M32H10W2Sign = 510,
759    LmsSha256M32H10W2Verify = 511,
760    LmsSha256M32H10W4Sign = 512,   // CNSA 2.0
761    LmsSha256M32H10W4Verify = 513, // CNSA 2.0
762    LmsSha256M32H10W8Sign = 514,   // CNSA 2.0
763    LmsSha256M32H10W8Verify = 515, // CNSA 2.0
764    LmsSha256M32H15W1Sign = 516,
765    LmsSha256M32H15W1Verify = 517,
766    LmsSha256M32H15W2Sign = 518,
767    LmsSha256M32H15W2Verify = 519,
768    LmsSha256M32H15W4Sign = 520,   // CNSA 2.0
769    LmsSha256M32H15W4Verify = 521, // CNSA 2.0
770    LmsSha256M32H15W8Sign = 522,   // CNSA 2.0
771    LmsSha256M32H15W8Verify = 523, // CNSA 2.0
772    LmsSha256M32H20W1Sign = 524,
773    LmsSha256M32H20W1Verify = 525,
774    LmsSha256M32H20W2Sign = 526,
775    LmsSha256M32H20W2Verify = 527,
776    LmsSha256M32H20W4Sign = 528,   // CNSA 2.0
777    LmsSha256M32H20W4Verify = 529, // CNSA 2.0
778    LmsSha256M32H20W8Sign = 530,   // CNSA 2.0
779    LmsSha256M32H20W8Verify = 531, // CNSA 2.0
780    LmsSha256M32H25W1Sign = 532,
781    LmsSha256M32H25W1Verify = 533,
782    LmsSha256M32H25W2Sign = 534,
783    LmsSha256M32H25W2Verify = 535,
784    LmsSha256M32H25W4Sign = 536,   // CNSA 2.0
785    LmsSha256M32H25W4Verify = 537, // CNSA 2.0
786    LmsSha256M32H25W8Sign = 538,   // CNSA 2.0
787    LmsSha256M32H25W8Verify = 539, // CNSA 2.0
788
789    // SHA-256 / N=24 family (RFC 8708 §4.1)
790    LmsSha256M24H5W1Sign = 540,
791    LmsSha256M24H5W1Verify = 541,
792    LmsSha256M24H5W2Sign = 542,
793    LmsSha256M24H5W2Verify = 543,
794    LmsSha256M24H5W4Sign = 544,
795    LmsSha256M24H5W4Verify = 545,
796    LmsSha256M24H5W8Sign = 546,
797    LmsSha256M24H5W8Verify = 547,
798    LmsSha256M24H10W1Sign = 548,
799    LmsSha256M24H10W1Verify = 549,
800    LmsSha256M24H10W2Sign = 550,
801    LmsSha256M24H10W2Verify = 551,
802    LmsSha256M24H10W4Sign = 552,
803    LmsSha256M24H10W4Verify = 553,
804    LmsSha256M24H10W8Sign = 554,
805    LmsSha256M24H10W8Verify = 555,
806    LmsSha256M24H15W1Sign = 556,
807    LmsSha256M24H15W1Verify = 557,
808    LmsSha256M24H15W2Sign = 558,
809    LmsSha256M24H15W2Verify = 559,
810    LmsSha256M24H15W4Sign = 560,
811    LmsSha256M24H15W4Verify = 561,
812    LmsSha256M24H15W8Sign = 562,
813    LmsSha256M24H15W8Verify = 563,
814    LmsSha256M24H20W1Sign = 564,
815    LmsSha256M24H20W1Verify = 565,
816    LmsSha256M24H20W2Sign = 566,
817    LmsSha256M24H20W2Verify = 567,
818    LmsSha256M24H20W4Sign = 568,
819    LmsSha256M24H20W4Verify = 569,
820    LmsSha256M24H20W8Sign = 570,
821    LmsSha256M24H20W8Verify = 571,
822    LmsSha256M24H25W1Sign = 572,
823    LmsSha256M24H25W1Verify = 573,
824    LmsSha256M24H25W2Sign = 574,
825    LmsSha256M24H25W2Verify = 575,
826    LmsSha256M24H25W4Sign = 576,
827    LmsSha256M24H25W4Verify = 577,
828    LmsSha256M24H25W8Sign = 578,
829    LmsSha256M24H25W8Verify = 579,
830
831    // SHAKE-256 / N=32 family (RFC 8708 §3.1)
832    LmsShakeM32H5W1Sign = 580,
833    LmsShakeM32H5W1Verify = 581,
834    LmsShakeM32H5W2Sign = 582,
835    LmsShakeM32H5W2Verify = 583,
836    LmsShakeM32H5W4Sign = 584,
837    LmsShakeM32H5W4Verify = 585,
838    LmsShakeM32H5W8Sign = 586,
839    LmsShakeM32H5W8Verify = 587,
840    LmsShakeM32H10W1Sign = 588,
841    LmsShakeM32H10W1Verify = 589,
842    LmsShakeM32H10W2Sign = 590,
843    LmsShakeM32H10W2Verify = 591,
844    LmsShakeM32H10W4Sign = 592,
845    LmsShakeM32H10W4Verify = 593,
846    LmsShakeM32H10W8Sign = 594,
847    LmsShakeM32H10W8Verify = 595,
848    LmsShakeM32H15W1Sign = 596,
849    LmsShakeM32H15W1Verify = 597,
850    LmsShakeM32H15W2Sign = 598,
851    LmsShakeM32H15W2Verify = 599,
852    LmsShakeM32H15W4Sign = 600,
853    LmsShakeM32H15W4Verify = 601,
854    LmsShakeM32H15W8Sign = 602,
855    LmsShakeM32H15W8Verify = 603,
856    LmsShakeM32H20W1Sign = 604,
857    LmsShakeM32H20W1Verify = 605,
858    LmsShakeM32H20W2Sign = 606,
859    LmsShakeM32H20W2Verify = 607,
860    LmsShakeM32H20W4Sign = 608,
861    LmsShakeM32H20W4Verify = 609,
862    LmsShakeM32H20W8Sign = 610,
863    LmsShakeM32H20W8Verify = 611,
864    LmsShakeM32H25W1Sign = 612,
865    LmsShakeM32H25W1Verify = 613,
866    LmsShakeM32H25W2Sign = 614,
867    LmsShakeM32H25W2Verify = 615,
868    LmsShakeM32H25W4Sign = 616,
869    LmsShakeM32H25W4Verify = 617,
870    LmsShakeM32H25W8Sign = 618,
871    LmsShakeM32H25W8Verify = 619,
872
873    // SHAKE-256 / N=24 family (RFC 8708 §4.2)
874    LmsShakeM24H5W1Sign = 620,
875    LmsShakeM24H5W1Verify = 621,
876    LmsShakeM24H5W2Sign = 622,
877    LmsShakeM24H5W2Verify = 623,
878    LmsShakeM24H5W4Sign = 624,
879    LmsShakeM24H5W4Verify = 625,
880    LmsShakeM24H5W8Sign = 626,
881    LmsShakeM24H5W8Verify = 627,
882    LmsShakeM24H10W1Sign = 628,
883    LmsShakeM24H10W1Verify = 629,
884    LmsShakeM24H10W2Sign = 630,
885    LmsShakeM24H10W2Verify = 631,
886    LmsShakeM24H10W4Sign = 632,
887    LmsShakeM24H10W4Verify = 633,
888    LmsShakeM24H10W8Sign = 634,
889    LmsShakeM24H10W8Verify = 635,
890    LmsShakeM24H15W1Sign = 636,
891    LmsShakeM24H15W1Verify = 637,
892    LmsShakeM24H15W2Sign = 638,
893    LmsShakeM24H15W2Verify = 639,
894    LmsShakeM24H15W4Sign = 640,
895    LmsShakeM24H15W4Verify = 641,
896    LmsShakeM24H15W8Sign = 642,
897    LmsShakeM24H15W8Verify = 643,
898    LmsShakeM24H20W1Sign = 644,
899    LmsShakeM24H20W1Verify = 645,
900    LmsShakeM24H20W2Sign = 646,
901    LmsShakeM24H20W2Verify = 647,
902    LmsShakeM24H20W4Sign = 648,
903    LmsShakeM24H20W4Verify = 649,
904    LmsShakeM24H20W8Sign = 650,
905    LmsShakeM24H20W8Verify = 651,
906    LmsShakeM24H25W1Sign = 652,
907    LmsShakeM24H25W1Verify = 653,
908    LmsShakeM24H25W2Sign = 654,
909    LmsShakeM24H25W2Verify = 655,
910    LmsShakeM24H25W4Sign = 656,
911    LmsShakeM24H25W4Verify = 657,
912    LmsShakeM24H25W8Sign = 658,
913    LmsShakeM24H25W8Verify = 659,
914
915    // ----- oxicrypt-xmss: SP 800-208 (stub) -----
916    // Renumbered 340/341 → 370/371 in Batch 4 to make room for the SLH-DSA block.
917    XmssSign = 370,
918    XmssVerify = 371,
919
920    // ----- oxicrypt-dh: RFC 3526 (stub) -----
921    // Renumbered 350 → 380 in Batch 4 to make room for the SLH-DSA block.
922    Dh3072 = 380,
923}
924
925impl fmt::Display for Service {
926    // The match arm count is proportional to the number of approved
927    // services in the module; there is no natural factoring that
928    // would reduce it.
929    #[allow(clippy::too_many_lines)]
930    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931        let name = match self {
932            Self::Sha1 => "SHA-1",
933            Self::Sha224 => "SHA-224",
934            Self::Sha256 => "SHA-256",
935            Self::Sha384 => "SHA-384",
936            Self::Sha512 => "SHA-512",
937            Self::Sha512_224 => "SHA-512/224",
938            Self::Sha512_256 => "SHA-512/256",
939            Self::Sha3_224 => "SHA3-224",
940            Self::Sha3_256 => "SHA3-256",
941            Self::Sha3_384 => "SHA3-384",
942            Self::Sha3_512 => "SHA3-512",
943            Self::Shake128 => "SHAKE128",
944            Self::Shake256 => "SHAKE256",
945            Self::CShake128 => "cSHAKE128",
946            Self::CShake256 => "cSHAKE256",
947            Self::Kmac128 => "KMAC128",
948            Self::Kmac256 => "KMAC256",
949            Self::KmacXof128 => "KMACXOF128",
950            Self::KmacXof256 => "KMACXOF256",
951            Self::TupleHash128 => "TupleHash128",
952            Self::TupleHash256 => "TupleHash256",
953            Self::TupleHashXof128 => "TupleHashXOF128",
954            Self::TupleHashXof256 => "TupleHashXOF256",
955            Self::ParallelHash128 => "ParallelHash128",
956            Self::ParallelHash256 => "ParallelHash256",
957            Self::ParallelHashXof128 => "ParallelHashXOF128",
958            Self::ParallelHashXof256 => "ParallelHashXOF256",
959            Self::HmacSha1 => "HMAC-SHA-1",
960            Self::HmacSha224 => "HMAC-SHA-224",
961            Self::HmacSha256 => "HMAC-SHA-256",
962            Self::HmacSha384 => "HMAC-SHA-384",
963            Self::HmacSha512 => "HMAC-SHA-512",
964            Self::HmacSha512_224 => "HMAC-SHA-512/224",
965            Self::HmacSha512_256 => "HMAC-SHA-512/256",
966            Self::HmacSha3_224 => "HMAC-SHA3-224",
967            Self::HmacSha3_256 => "HMAC-SHA3-256",
968            Self::HmacSha3_384 => "HMAC-SHA3-384",
969            Self::HmacSha3_512 => "HMAC-SHA3-512",
970            Self::CmacAes128 => "CMAC-AES-128",
971            Self::CmacAes192 => "CMAC-AES-192",
972            Self::CmacAes256 => "CMAC-AES-256",
973            Self::Aes128Ecb => "AES-128-ECB",
974            Self::Aes128Cbc => "AES-128-CBC",
975            Self::Aes128Ctr => "AES-128-CTR",
976            Self::Aes128Gcm => "AES-128-GCM",
977            Self::Aes128Ccm => "AES-128-CCM",
978            Self::Aes128Kw => "AES-128-KW",
979            Self::Aes128Kwp => "AES-128-KWP",
980            Self::Aes192Ecb => "AES-192-ECB",
981            Self::Aes192Cbc => "AES-192-CBC",
982            Self::Aes192Ctr => "AES-192-CTR",
983            Self::Aes192Gcm => "AES-192-GCM",
984            Self::Aes192Ccm => "AES-192-CCM",
985            Self::Aes192Kw => "AES-192-KW",
986            Self::Aes192Kwp => "AES-192-KWP",
987            Self::Aes256Ecb => "AES-256-ECB",
988            Self::Aes256Cbc => "AES-256-CBC",
989            Self::Aes256Ctr => "AES-256-CTR",
990            Self::Aes256Gcm => "AES-256-GCM",
991            Self::Aes256Ccm => "AES-256-CCM",
992            Self::Aes256Kw => "AES-256-KW",
993            Self::Aes256Kwp => "AES-256-KWP",
994            Self::Aes128 => "AES-128",
995            Self::Aes192 => "AES-192",
996            Self::Aes256 => "AES-256",
997            Self::CtrDrbgAes128 => "CTR_DRBG-AES-128",
998            Self::CtrDrbgAes192 => "CTR_DRBG-AES-192",
999            Self::CtrDrbgAes256 => "CTR_DRBG-AES-256",
1000            Self::HashDrbgSha256 => "Hash_DRBG-SHA-256",
1001            Self::HashDrbgSha384 => "Hash_DRBG-SHA-384",
1002            Self::HashDrbgSha512 => "Hash_DRBG-SHA-512",
1003            Self::HmacDrbgSha256 => "HMAC_DRBG-SHA-256",
1004            Self::HmacDrbgSha384 => "HMAC_DRBG-SHA-384",
1005            Self::HmacDrbgSha512 => "HMAC_DRBG-SHA-512",
1006            Self::HkdfSha1 => "HKDF-SHA-1",
1007            Self::HkdfSha256 => "HKDF-SHA-256",
1008            Self::HkdfSha384 => "HKDF-SHA-384",
1009            Self::HkdfSha512 => "HKDF-SHA-512",
1010            Self::KbkdfHmacSha256 => "KBKDF-HMAC-SHA-256",
1011            Self::KbkdfHmacSha384 => "KBKDF-HMAC-SHA-384",
1012            Self::KbkdfHmacSha512 => "KBKDF-HMAC-SHA-512",
1013            Self::KbkdfCmacAes128 => "KBKDF-CMAC-AES-128",
1014            Self::KbkdfCmacAes192 => "KBKDF-CMAC-AES-192",
1015            Self::KbkdfCmacAes256 => "KBKDF-CMAC-AES-256",
1016            Self::Pbkdf2HmacSha1 => "PBKDF2-HMAC-SHA-1",
1017            Self::Pbkdf2HmacSha224 => "PBKDF2-HMAC-SHA-224",
1018            Self::Pbkdf2HmacSha256 => "PBKDF2-HMAC-SHA-256",
1019            Self::Pbkdf2HmacSha384 => "PBKDF2-HMAC-SHA-384",
1020            Self::Pbkdf2HmacSha512 => "PBKDF2-HMAC-SHA-512",
1021            Self::RsaKeygen2048 => "RSA-2048 keygen",
1022            Self::RsaPkcs1v15Sign2048 => "RSA-PKCS1v1.5-Sign-2048",
1023            Self::RsaPssSign2048 => "RSA-PSS-Sign-2048",
1024            Self::RsaOaep2048 => "RSA-OAEP-2048",
1025            Self::RsaPkcs1v15Verify2048 => "RSA-PKCS1v1.5-Verify-2048",
1026            Self::RsaPssVerify2048 => "RSA-PSS-Verify-2048",
1027            Self::RsaKeygen3072 => "RSA-3072 keygen",
1028            Self::RsaPkcs1v15Sign3072 => "RSA-PKCS1v1.5-Sign-3072",
1029            Self::RsaPssSign3072 => "RSA-PSS-Sign-3072",
1030            Self::RsaOaep3072 => "RSA-OAEP-3072",
1031            Self::RsaPkcs1v15Verify3072 => "RSA-PKCS1v1.5-Verify-3072",
1032            Self::RsaPssVerify3072 => "RSA-PSS-Verify-3072",
1033            Self::RsaKeygen4096 => "RSA-4096 keygen",
1034            Self::RsaPkcs1v15Sign4096 => "RSA-PKCS1v1.5-Sign-4096",
1035            Self::RsaPssSign4096 => "RSA-PSS-Sign-4096",
1036            Self::RsaOaep4096 => "RSA-OAEP-4096",
1037            Self::RsaPkcs1v15Verify4096 => "RSA-PKCS1v1.5-Verify-4096",
1038            Self::RsaPssVerify4096 => "RSA-PSS-Verify-4096",
1039            Self::EcdsaP256Sign => "ECDSA-P256 sign",
1040            Self::EcdsaP256Verify => "ECDSA-P256 verify",
1041            Self::EcdsaP256Keygen => "ECDSA-P256 keygen",
1042            Self::EcdsaP384Sign => "ECDSA-P384 sign",
1043            Self::EcdsaP384Verify => "ECDSA-P384 verify",
1044            Self::EcdsaP384Keygen => "ECDSA-P384 keygen",
1045            Self::EcdhP256 => "ECDH-P256",
1046            Self::EcdhP384 => "ECDH-P384",
1047            Self::Ed25519Sign => "Ed25519 sign",
1048            Self::Ed25519Verify => "Ed25519 verify",
1049            Self::Ed25519Keygen => "Ed25519 keygen",
1050            Self::Tls12Kdf => "TLS 1.2 KDF",
1051            Self::Tls13Kdf => "TLS 1.3 KDF",
1052            Self::MlKem1024Encaps => "ML-KEM-1024 encaps",
1053            Self::MlKem1024Decaps => "ML-KEM-1024 decaps",
1054            Self::MlKem1024Keygen => "ML-KEM-1024 keygen",
1055            Self::MlKem512Encaps => "ML-KEM-512 encaps",
1056            Self::MlKem512Decaps => "ML-KEM-512 decaps",
1057            Self::MlKem512Keygen => "ML-KEM-512 keygen",
1058            Self::MlKem768Encaps => "ML-KEM-768 encaps",
1059            Self::MlKem768Decaps => "ML-KEM-768 decaps",
1060            Self::MlKem768Keygen => "ML-KEM-768 keygen",
1061            Self::MlDsa87Sign => "ML-DSA-87 sign",
1062            Self::MlDsa87Verify => "ML-DSA-87 verify",
1063            Self::MlDsa87Keygen => "ML-DSA-87 keygen",
1064            Self::MlDsa44Sign => "ML-DSA-44 sign",
1065            Self::MlDsa44Verify => "ML-DSA-44 verify",
1066            Self::MlDsa44Keygen => "ML-DSA-44 keygen",
1067            Self::MlDsa65Sign => "ML-DSA-65 sign",
1068            Self::MlDsa65Verify => "ML-DSA-65 verify",
1069            Self::MlDsa65Keygen => "ML-DSA-65 keygen",
1070            Self::SlhDsaSha2256sKeygen => "SLH-DSA-SHA2-256s keygen",
1071            Self::SlhDsaSha2256sSign => "SLH-DSA-SHA2-256s sign",
1072            Self::SlhDsaSha2256sVerify => "SLH-DSA-SHA2-256s verify",
1073            Self::SlhDsaSha2128sKeygen => "SLH-DSA-SHA2-128s keygen",
1074            Self::SlhDsaSha2128sSign => "SLH-DSA-SHA2-128s sign",
1075            Self::SlhDsaSha2128sVerify => "SLH-DSA-SHA2-128s verify",
1076            Self::SlhDsaSha2128fKeygen => "SLH-DSA-SHA2-128f keygen",
1077            Self::SlhDsaSha2128fSign => "SLH-DSA-SHA2-128f sign",
1078            Self::SlhDsaSha2128fVerify => "SLH-DSA-SHA2-128f verify",
1079            Self::SlhDsaSha2192sKeygen => "SLH-DSA-SHA2-192s keygen",
1080            Self::SlhDsaSha2192sSign => "SLH-DSA-SHA2-192s sign",
1081            Self::SlhDsaSha2192sVerify => "SLH-DSA-SHA2-192s verify",
1082            Self::SlhDsaSha2192fKeygen => "SLH-DSA-SHA2-192f keygen",
1083            Self::SlhDsaSha2192fSign => "SLH-DSA-SHA2-192f sign",
1084            Self::SlhDsaSha2192fVerify => "SLH-DSA-SHA2-192f verify",
1085            Self::SlhDsaSha2256fKeygen => "SLH-DSA-SHA2-256f keygen",
1086            Self::SlhDsaSha2256fSign => "SLH-DSA-SHA2-256f sign",
1087            Self::SlhDsaSha2256fVerify => "SLH-DSA-SHA2-256f verify",
1088            Self::SlhDsaShake128sKeygen => "SLH-DSA-SHAKE-128s keygen",
1089            Self::SlhDsaShake128sSign => "SLH-DSA-SHAKE-128s sign",
1090            Self::SlhDsaShake128sVerify => "SLH-DSA-SHAKE-128s verify",
1091            Self::SlhDsaShake128fKeygen => "SLH-DSA-SHAKE-128f keygen",
1092            Self::SlhDsaShake128fSign => "SLH-DSA-SHAKE-128f sign",
1093            Self::SlhDsaShake128fVerify => "SLH-DSA-SHAKE-128f verify",
1094            Self::SlhDsaShake192sKeygen => "SLH-DSA-SHAKE-192s keygen",
1095            Self::SlhDsaShake192sSign => "SLH-DSA-SHAKE-192s sign",
1096            Self::SlhDsaShake192sVerify => "SLH-DSA-SHAKE-192s verify",
1097            Self::SlhDsaShake192fKeygen => "SLH-DSA-SHAKE-192f keygen",
1098            Self::SlhDsaShake192fSign => "SLH-DSA-SHAKE-192f sign",
1099            Self::SlhDsaShake192fVerify => "SLH-DSA-SHAKE-192f verify",
1100            Self::SlhDsaShake256sKeygen => "SLH-DSA-SHAKE-256s keygen",
1101            Self::SlhDsaShake256sSign => "SLH-DSA-SHAKE-256s sign",
1102            Self::SlhDsaShake256sVerify => "SLH-DSA-SHAKE-256s verify",
1103            Self::SlhDsaShake256fKeygen => "SLH-DSA-SHAKE-256f keygen",
1104            Self::SlhDsaShake256fSign => "SLH-DSA-SHAKE-256f sign",
1105            Self::SlhDsaShake256fVerify => "SLH-DSA-SHAKE-256f verify",
1106            Self::LmsSha256M32H5W1Sign => "LMS SHA-256 M=32 H=5 W=1 sign",
1107            Self::LmsSha256M32H5W1Verify => "LMS SHA-256 M=32 H=5 W=1 verify",
1108            Self::LmsSha256M32H5W2Sign => "LMS SHA-256 M=32 H=5 W=2 sign",
1109            Self::LmsSha256M32H5W2Verify => "LMS SHA-256 M=32 H=5 W=2 verify",
1110            Self::LmsSha256M32H5W4Sign => "LMS SHA-256 M=32 H=5 W=4 sign",
1111            Self::LmsSha256M32H5W4Verify => "LMS SHA-256 M=32 H=5 W=4 verify",
1112            Self::LmsSha256M32H5W8Sign => "LMS SHA-256 M=32 H=5 W=8 sign",
1113            Self::LmsSha256M32H5W8Verify => "LMS SHA-256 M=32 H=5 W=8 verify",
1114            Self::LmsSha256M32H10W1Sign => "LMS SHA-256 M=32 H=10 W=1 sign",
1115            Self::LmsSha256M32H10W1Verify => "LMS SHA-256 M=32 H=10 W=1 verify",
1116            Self::LmsSha256M32H10W2Sign => "LMS SHA-256 M=32 H=10 W=2 sign",
1117            Self::LmsSha256M32H10W2Verify => "LMS SHA-256 M=32 H=10 W=2 verify",
1118            Self::LmsSha256M32H10W4Sign => "LMS SHA-256 M=32 H=10 W=4 sign",
1119            Self::LmsSha256M32H10W4Verify => "LMS SHA-256 M=32 H=10 W=4 verify",
1120            Self::LmsSha256M32H10W8Sign => "LMS SHA-256 M=32 H=10 W=8 sign",
1121            Self::LmsSha256M32H10W8Verify => "LMS SHA-256 M=32 H=10 W=8 verify",
1122            Self::LmsSha256M32H15W1Sign => "LMS SHA-256 M=32 H=15 W=1 sign",
1123            Self::LmsSha256M32H15W1Verify => "LMS SHA-256 M=32 H=15 W=1 verify",
1124            Self::LmsSha256M32H15W2Sign => "LMS SHA-256 M=32 H=15 W=2 sign",
1125            Self::LmsSha256M32H15W2Verify => "LMS SHA-256 M=32 H=15 W=2 verify",
1126            Self::LmsSha256M32H15W4Sign => "LMS SHA-256 M=32 H=15 W=4 sign",
1127            Self::LmsSha256M32H15W4Verify => "LMS SHA-256 M=32 H=15 W=4 verify",
1128            Self::LmsSha256M32H15W8Sign => "LMS SHA-256 M=32 H=15 W=8 sign",
1129            Self::LmsSha256M32H15W8Verify => "LMS SHA-256 M=32 H=15 W=8 verify",
1130            Self::LmsSha256M32H20W1Sign => "LMS SHA-256 M=32 H=20 W=1 sign",
1131            Self::LmsSha256M32H20W1Verify => "LMS SHA-256 M=32 H=20 W=1 verify",
1132            Self::LmsSha256M32H20W2Sign => "LMS SHA-256 M=32 H=20 W=2 sign",
1133            Self::LmsSha256M32H20W2Verify => "LMS SHA-256 M=32 H=20 W=2 verify",
1134            Self::LmsSha256M32H20W4Sign => "LMS SHA-256 M=32 H=20 W=4 sign",
1135            Self::LmsSha256M32H20W4Verify => "LMS SHA-256 M=32 H=20 W=4 verify",
1136            Self::LmsSha256M32H20W8Sign => "LMS SHA-256 M=32 H=20 W=8 sign",
1137            Self::LmsSha256M32H20W8Verify => "LMS SHA-256 M=32 H=20 W=8 verify",
1138            Self::LmsSha256M32H25W1Sign => "LMS SHA-256 M=32 H=25 W=1 sign",
1139            Self::LmsSha256M32H25W1Verify => "LMS SHA-256 M=32 H=25 W=1 verify",
1140            Self::LmsSha256M32H25W2Sign => "LMS SHA-256 M=32 H=25 W=2 sign",
1141            Self::LmsSha256M32H25W2Verify => "LMS SHA-256 M=32 H=25 W=2 verify",
1142            Self::LmsSha256M32H25W4Sign => "LMS SHA-256 M=32 H=25 W=4 sign",
1143            Self::LmsSha256M32H25W4Verify => "LMS SHA-256 M=32 H=25 W=4 verify",
1144            Self::LmsSha256M32H25W8Sign => "LMS SHA-256 M=32 H=25 W=8 sign",
1145            Self::LmsSha256M32H25W8Verify => "LMS SHA-256 M=32 H=25 W=8 verify",
1146            Self::LmsSha256M24H5W1Sign => "LMS SHA-256 M=24 H=5 W=1 sign",
1147            Self::LmsSha256M24H5W1Verify => "LMS SHA-256 M=24 H=5 W=1 verify",
1148            Self::LmsSha256M24H5W2Sign => "LMS SHA-256 M=24 H=5 W=2 sign",
1149            Self::LmsSha256M24H5W2Verify => "LMS SHA-256 M=24 H=5 W=2 verify",
1150            Self::LmsSha256M24H5W4Sign => "LMS SHA-256 M=24 H=5 W=4 sign",
1151            Self::LmsSha256M24H5W4Verify => "LMS SHA-256 M=24 H=5 W=4 verify",
1152            Self::LmsSha256M24H5W8Sign => "LMS SHA-256 M=24 H=5 W=8 sign",
1153            Self::LmsSha256M24H5W8Verify => "LMS SHA-256 M=24 H=5 W=8 verify",
1154            Self::LmsSha256M24H10W1Sign => "LMS SHA-256 M=24 H=10 W=1 sign",
1155            Self::LmsSha256M24H10W1Verify => "LMS SHA-256 M=24 H=10 W=1 verify",
1156            Self::LmsSha256M24H10W2Sign => "LMS SHA-256 M=24 H=10 W=2 sign",
1157            Self::LmsSha256M24H10W2Verify => "LMS SHA-256 M=24 H=10 W=2 verify",
1158            Self::LmsSha256M24H10W4Sign => "LMS SHA-256 M=24 H=10 W=4 sign",
1159            Self::LmsSha256M24H10W4Verify => "LMS SHA-256 M=24 H=10 W=4 verify",
1160            Self::LmsSha256M24H10W8Sign => "LMS SHA-256 M=24 H=10 W=8 sign",
1161            Self::LmsSha256M24H10W8Verify => "LMS SHA-256 M=24 H=10 W=8 verify",
1162            Self::LmsSha256M24H15W1Sign => "LMS SHA-256 M=24 H=15 W=1 sign",
1163            Self::LmsSha256M24H15W1Verify => "LMS SHA-256 M=24 H=15 W=1 verify",
1164            Self::LmsSha256M24H15W2Sign => "LMS SHA-256 M=24 H=15 W=2 sign",
1165            Self::LmsSha256M24H15W2Verify => "LMS SHA-256 M=24 H=15 W=2 verify",
1166            Self::LmsSha256M24H15W4Sign => "LMS SHA-256 M=24 H=15 W=4 sign",
1167            Self::LmsSha256M24H15W4Verify => "LMS SHA-256 M=24 H=15 W=4 verify",
1168            Self::LmsSha256M24H15W8Sign => "LMS SHA-256 M=24 H=15 W=8 sign",
1169            Self::LmsSha256M24H15W8Verify => "LMS SHA-256 M=24 H=15 W=8 verify",
1170            Self::LmsSha256M24H20W1Sign => "LMS SHA-256 M=24 H=20 W=1 sign",
1171            Self::LmsSha256M24H20W1Verify => "LMS SHA-256 M=24 H=20 W=1 verify",
1172            Self::LmsSha256M24H20W2Sign => "LMS SHA-256 M=24 H=20 W=2 sign",
1173            Self::LmsSha256M24H20W2Verify => "LMS SHA-256 M=24 H=20 W=2 verify",
1174            Self::LmsSha256M24H20W4Sign => "LMS SHA-256 M=24 H=20 W=4 sign",
1175            Self::LmsSha256M24H20W4Verify => "LMS SHA-256 M=24 H=20 W=4 verify",
1176            Self::LmsSha256M24H20W8Sign => "LMS SHA-256 M=24 H=20 W=8 sign",
1177            Self::LmsSha256M24H20W8Verify => "LMS SHA-256 M=24 H=20 W=8 verify",
1178            Self::LmsSha256M24H25W1Sign => "LMS SHA-256 M=24 H=25 W=1 sign",
1179            Self::LmsSha256M24H25W1Verify => "LMS SHA-256 M=24 H=25 W=1 verify",
1180            Self::LmsSha256M24H25W2Sign => "LMS SHA-256 M=24 H=25 W=2 sign",
1181            Self::LmsSha256M24H25W2Verify => "LMS SHA-256 M=24 H=25 W=2 verify",
1182            Self::LmsSha256M24H25W4Sign => "LMS SHA-256 M=24 H=25 W=4 sign",
1183            Self::LmsSha256M24H25W4Verify => "LMS SHA-256 M=24 H=25 W=4 verify",
1184            Self::LmsSha256M24H25W8Sign => "LMS SHA-256 M=24 H=25 W=8 sign",
1185            Self::LmsSha256M24H25W8Verify => "LMS SHA-256 M=24 H=25 W=8 verify",
1186            Self::LmsShakeM32H5W1Sign => "LMS SHAKE M=32 H=5 W=1 sign",
1187            Self::LmsShakeM32H5W1Verify => "LMS SHAKE M=32 H=5 W=1 verify",
1188            Self::LmsShakeM32H5W2Sign => "LMS SHAKE M=32 H=5 W=2 sign",
1189            Self::LmsShakeM32H5W2Verify => "LMS SHAKE M=32 H=5 W=2 verify",
1190            Self::LmsShakeM32H5W4Sign => "LMS SHAKE M=32 H=5 W=4 sign",
1191            Self::LmsShakeM32H5W4Verify => "LMS SHAKE M=32 H=5 W=4 verify",
1192            Self::LmsShakeM32H5W8Sign => "LMS SHAKE M=32 H=5 W=8 sign",
1193            Self::LmsShakeM32H5W8Verify => "LMS SHAKE M=32 H=5 W=8 verify",
1194            Self::LmsShakeM32H10W1Sign => "LMS SHAKE M=32 H=10 W=1 sign",
1195            Self::LmsShakeM32H10W1Verify => "LMS SHAKE M=32 H=10 W=1 verify",
1196            Self::LmsShakeM32H10W2Sign => "LMS SHAKE M=32 H=10 W=2 sign",
1197            Self::LmsShakeM32H10W2Verify => "LMS SHAKE M=32 H=10 W=2 verify",
1198            Self::LmsShakeM32H10W4Sign => "LMS SHAKE M=32 H=10 W=4 sign",
1199            Self::LmsShakeM32H10W4Verify => "LMS SHAKE M=32 H=10 W=4 verify",
1200            Self::LmsShakeM32H10W8Sign => "LMS SHAKE M=32 H=10 W=8 sign",
1201            Self::LmsShakeM32H10W8Verify => "LMS SHAKE M=32 H=10 W=8 verify",
1202            Self::LmsShakeM32H15W1Sign => "LMS SHAKE M=32 H=15 W=1 sign",
1203            Self::LmsShakeM32H15W1Verify => "LMS SHAKE M=32 H=15 W=1 verify",
1204            Self::LmsShakeM32H15W2Sign => "LMS SHAKE M=32 H=15 W=2 sign",
1205            Self::LmsShakeM32H15W2Verify => "LMS SHAKE M=32 H=15 W=2 verify",
1206            Self::LmsShakeM32H15W4Sign => "LMS SHAKE M=32 H=15 W=4 sign",
1207            Self::LmsShakeM32H15W4Verify => "LMS SHAKE M=32 H=15 W=4 verify",
1208            Self::LmsShakeM32H15W8Sign => "LMS SHAKE M=32 H=15 W=8 sign",
1209            Self::LmsShakeM32H15W8Verify => "LMS SHAKE M=32 H=15 W=8 verify",
1210            Self::LmsShakeM32H20W1Sign => "LMS SHAKE M=32 H=20 W=1 sign",
1211            Self::LmsShakeM32H20W1Verify => "LMS SHAKE M=32 H=20 W=1 verify",
1212            Self::LmsShakeM32H20W2Sign => "LMS SHAKE M=32 H=20 W=2 sign",
1213            Self::LmsShakeM32H20W2Verify => "LMS SHAKE M=32 H=20 W=2 verify",
1214            Self::LmsShakeM32H20W4Sign => "LMS SHAKE M=32 H=20 W=4 sign",
1215            Self::LmsShakeM32H20W4Verify => "LMS SHAKE M=32 H=20 W=4 verify",
1216            Self::LmsShakeM32H20W8Sign => "LMS SHAKE M=32 H=20 W=8 sign",
1217            Self::LmsShakeM32H20W8Verify => "LMS SHAKE M=32 H=20 W=8 verify",
1218            Self::LmsShakeM32H25W1Sign => "LMS SHAKE M=32 H=25 W=1 sign",
1219            Self::LmsShakeM32H25W1Verify => "LMS SHAKE M=32 H=25 W=1 verify",
1220            Self::LmsShakeM32H25W2Sign => "LMS SHAKE M=32 H=25 W=2 sign",
1221            Self::LmsShakeM32H25W2Verify => "LMS SHAKE M=32 H=25 W=2 verify",
1222            Self::LmsShakeM32H25W4Sign => "LMS SHAKE M=32 H=25 W=4 sign",
1223            Self::LmsShakeM32H25W4Verify => "LMS SHAKE M=32 H=25 W=4 verify",
1224            Self::LmsShakeM32H25W8Sign => "LMS SHAKE M=32 H=25 W=8 sign",
1225            Self::LmsShakeM32H25W8Verify => "LMS SHAKE M=32 H=25 W=8 verify",
1226            Self::LmsShakeM24H5W1Sign => "LMS SHAKE M=24 H=5 W=1 sign",
1227            Self::LmsShakeM24H5W1Verify => "LMS SHAKE M=24 H=5 W=1 verify",
1228            Self::LmsShakeM24H5W2Sign => "LMS SHAKE M=24 H=5 W=2 sign",
1229            Self::LmsShakeM24H5W2Verify => "LMS SHAKE M=24 H=5 W=2 verify",
1230            Self::LmsShakeM24H5W4Sign => "LMS SHAKE M=24 H=5 W=4 sign",
1231            Self::LmsShakeM24H5W4Verify => "LMS SHAKE M=24 H=5 W=4 verify",
1232            Self::LmsShakeM24H5W8Sign => "LMS SHAKE M=24 H=5 W=8 sign",
1233            Self::LmsShakeM24H5W8Verify => "LMS SHAKE M=24 H=5 W=8 verify",
1234            Self::LmsShakeM24H10W1Sign => "LMS SHAKE M=24 H=10 W=1 sign",
1235            Self::LmsShakeM24H10W1Verify => "LMS SHAKE M=24 H=10 W=1 verify",
1236            Self::LmsShakeM24H10W2Sign => "LMS SHAKE M=24 H=10 W=2 sign",
1237            Self::LmsShakeM24H10W2Verify => "LMS SHAKE M=24 H=10 W=2 verify",
1238            Self::LmsShakeM24H10W4Sign => "LMS SHAKE M=24 H=10 W=4 sign",
1239            Self::LmsShakeM24H10W4Verify => "LMS SHAKE M=24 H=10 W=4 verify",
1240            Self::LmsShakeM24H10W8Sign => "LMS SHAKE M=24 H=10 W=8 sign",
1241            Self::LmsShakeM24H10W8Verify => "LMS SHAKE M=24 H=10 W=8 verify",
1242            Self::LmsShakeM24H15W1Sign => "LMS SHAKE M=24 H=15 W=1 sign",
1243            Self::LmsShakeM24H15W1Verify => "LMS SHAKE M=24 H=15 W=1 verify",
1244            Self::LmsShakeM24H15W2Sign => "LMS SHAKE M=24 H=15 W=2 sign",
1245            Self::LmsShakeM24H15W2Verify => "LMS SHAKE M=24 H=15 W=2 verify",
1246            Self::LmsShakeM24H15W4Sign => "LMS SHAKE M=24 H=15 W=4 sign",
1247            Self::LmsShakeM24H15W4Verify => "LMS SHAKE M=24 H=15 W=4 verify",
1248            Self::LmsShakeM24H15W8Sign => "LMS SHAKE M=24 H=15 W=8 sign",
1249            Self::LmsShakeM24H15W8Verify => "LMS SHAKE M=24 H=15 W=8 verify",
1250            Self::LmsShakeM24H20W1Sign => "LMS SHAKE M=24 H=20 W=1 sign",
1251            Self::LmsShakeM24H20W1Verify => "LMS SHAKE M=24 H=20 W=1 verify",
1252            Self::LmsShakeM24H20W2Sign => "LMS SHAKE M=24 H=20 W=2 sign",
1253            Self::LmsShakeM24H20W2Verify => "LMS SHAKE M=24 H=20 W=2 verify",
1254            Self::LmsShakeM24H20W4Sign => "LMS SHAKE M=24 H=20 W=4 sign",
1255            Self::LmsShakeM24H20W4Verify => "LMS SHAKE M=24 H=20 W=4 verify",
1256            Self::LmsShakeM24H20W8Sign => "LMS SHAKE M=24 H=20 W=8 sign",
1257            Self::LmsShakeM24H20W8Verify => "LMS SHAKE M=24 H=20 W=8 verify",
1258            Self::LmsShakeM24H25W1Sign => "LMS SHAKE M=24 H=25 W=1 sign",
1259            Self::LmsShakeM24H25W1Verify => "LMS SHAKE M=24 H=25 W=1 verify",
1260            Self::LmsShakeM24H25W2Sign => "LMS SHAKE M=24 H=25 W=2 sign",
1261            Self::LmsShakeM24H25W2Verify => "LMS SHAKE M=24 H=25 W=2 verify",
1262            Self::LmsShakeM24H25W4Sign => "LMS SHAKE M=24 H=25 W=4 sign",
1263            Self::LmsShakeM24H25W4Verify => "LMS SHAKE M=24 H=25 W=4 verify",
1264            Self::LmsShakeM24H25W8Sign => "LMS SHAKE M=24 H=25 W=8 sign",
1265            Self::LmsShakeM24H25W8Verify => "LMS SHAKE M=24 H=25 W=8 verify",
1266            Self::XmssSign => "XMSS sign",
1267            Self::XmssVerify => "XMSS verify",
1268            Self::Dh3072 => "DH-3072",
1269        };
1270        f.write_str(name)
1271    }
1272}
1273
1274/// Guard used at the entry point of every approved service, after
1275/// [`require_operational`], to enforce the active algorithm profile.
1276///
1277/// Returns `Ok(())` if the service is permitted under the profile
1278/// selected at initialization, or
1279/// `Err(Error::AlgorithmRestricted { .. })` otherwise.
1280///
1281/// This gate does **not** check [`State`] — callers must call
1282/// [`require_operational`] first. The two-gate pattern is:
1283///
1284/// ```ignore
1285/// require_operational()?;
1286/// require_allowed(Service::Sha256)?;
1287/// // ... perform the operation
1288/// ```
1289pub fn require_allowed(service: Service) -> Result<(), Error> {
1290    let profile = active_profile();
1291    if is_allowed(profile, service) {
1292        Ok(())
1293    } else {
1294        Err(Error::AlgorithmRestricted { service })
1295    }
1296}
1297
1298/// Single source of truth for which services are allowed in each
1299/// profile.
1300const fn is_allowed(profile: AlgorithmProfile, service: Service) -> bool {
1301    match profile {
1302        AlgorithmProfile::Unrestricted => true,
1303        AlgorithmProfile::Cnsa2 => is_cnsa2_allowed(service),
1304        AlgorithmProfile::Cnsa1 => is_cnsa1_allowed(service),
1305    }
1306}
1307
1308/// CNSA 2.0 allowed set. Only quantum-resistant algorithms plus
1309/// AES-256, SHA-384/512, SHA3-384/512, and 256-bit SP 800-185.
1310const fn is_cnsa2_allowed(service: Service) -> bool {
1311    matches!(
1312        service,
1313        // AES-256 all modes
1314        Service::Aes256Ecb
1315            | Service::Aes256Cbc
1316            | Service::Aes256Ctr
1317            | Service::Aes256Gcm
1318            | Service::Aes256Ccm
1319            | Service::Aes256Kw
1320            | Service::Aes256Kwp
1321            | Service::Aes256
1322            // SHA-384, SHA-512
1323            | Service::Sha384
1324            | Service::Sha512
1325            // SHA3-384, SHA3-512 (for internal / hardware — see plan note)
1326            | Service::Sha3_384
1327            | Service::Sha3_512
1328            // SHAKE-256
1329            | Service::Shake256
1330            // 256-bit SP 800-185 variants
1331            | Service::CShake256
1332            | Service::Kmac256
1333            | Service::KmacXof256
1334            | Service::TupleHash256
1335            | Service::TupleHashXof256
1336            | Service::ParallelHash256
1337            | Service::ParallelHashXof256
1338            // HMAC with allowed hashes
1339            | Service::HmacSha384
1340            | Service::HmacSha512
1341            | Service::HmacSha3_384
1342            | Service::HmacSha3_512
1343            // CMAC-AES-256
1344            | Service::CmacAes256
1345            // DRBGs backed by AES-256 or SHA-384/512
1346            | Service::CtrDrbgAes256
1347            | Service::HashDrbgSha384
1348            | Service::HashDrbgSha512
1349            | Service::HmacDrbgSha384
1350            | Service::HmacDrbgSha512
1351            // KDFs with allowed backing
1352            | Service::HkdfSha384
1353            | Service::HkdfSha512
1354            | Service::KbkdfHmacSha384
1355            | Service::KbkdfHmacSha512
1356            | Service::KbkdfCmacAes256
1357            | Service::Pbkdf2HmacSha384
1358            | Service::Pbkdf2HmacSha512
1359            // TLS 1.3 KDF — mandatory transport in CNSA 2.0
1360            | Service::Tls13Kdf
1361            // Post-quantum (CNSA 2.0 core)
1362            | Service::MlKem1024Encaps
1363            | Service::MlKem1024Decaps
1364            | Service::MlKem1024Keygen
1365            | Service::MlDsa87Sign
1366            | Service::MlDsa87Verify
1367            | Service::MlDsa87Keygen
1368            // SLH-DSA-SHA2-256s — CNSSP-15 CNSA 2.0 mandate (only SLH-DSA
1369            // parameter set permitted under CNSA 2.0; the other 11 parameter
1370            // sets are intentionally excluded and fall through to the
1371            // fail-safe default-block).
1372            | Service::SlhDsaSha2256sKeygen
1373            | Service::SlhDsaSha2256sSign
1374            | Service::SlhDsaSha2256sVerify
1375            // LMS — SP 800-208 stateful HBS for software/firmware signing.
1376            // CNSA 2.0 permits the 8 SHA-256/M=32 pairs at H{10,15,20,25}×W{4,8}
1377            // per CNSSP 15. All other 72 pairs default-block via the `matches!`
1378            // fail-safe (see CMVP gem in security-policy.md).
1379            | Service::LmsSha256M32H10W4Sign
1380            | Service::LmsSha256M32H10W4Verify
1381            | Service::LmsSha256M32H10W8Sign
1382            | Service::LmsSha256M32H10W8Verify
1383            | Service::LmsSha256M32H15W4Sign
1384            | Service::LmsSha256M32H15W4Verify
1385            | Service::LmsSha256M32H15W8Sign
1386            | Service::LmsSha256M32H15W8Verify
1387            | Service::LmsSha256M32H20W4Sign
1388            | Service::LmsSha256M32H20W4Verify
1389            | Service::LmsSha256M32H20W8Sign
1390            | Service::LmsSha256M32H20W8Verify
1391            | Service::LmsSha256M32H25W4Sign
1392            | Service::LmsSha256M32H25W4Verify
1393            | Service::LmsSha256M32H25W8Sign
1394            | Service::LmsSha256M32H25W8Verify
1395            | Service::XmssSign
1396            | Service::XmssVerify
1397    )
1398}
1399
1400/// CNSA 1.0 allowed set. Classical algorithms for the transition
1401/// period: AES-256, SHA-384, ECDSA/ECDH P-384, RSA >= 3072,
1402/// DH >= 3072. Also includes SHA-256 (widely needed for
1403/// interoperability and certificate verification) and the
1404/// post-quantum algorithms allowed in CNSA 2.0 (CNSA 1.0 is
1405/// the transition profile; PQ algorithms are allowed if present).
1406const fn is_cnsa1_allowed(service: Service) -> bool {
1407    matches!(
1408        service,
1409        // AES-256 all modes
1410        Service::Aes256Ecb
1411            | Service::Aes256Cbc
1412            | Service::Aes256Ctr
1413            | Service::Aes256Gcm
1414            | Service::Aes256Ccm
1415            | Service::Aes256Kw
1416            | Service::Aes256Kwp
1417            | Service::Aes256
1418            // SHA-256, SHA-384, SHA-512 (SHA-256 needed for certs)
1419            | Service::Sha256
1420            | Service::Sha384
1421            | Service::Sha512
1422            // SHA3-256, SHA3-384, SHA3-512
1423            | Service::Sha3_256
1424            | Service::Sha3_384
1425            | Service::Sha3_512
1426            // SHAKE-256
1427            | Service::Shake256
1428            // 256-bit SP 800-185 variants
1429            | Service::CShake256
1430            | Service::Kmac256
1431            | Service::KmacXof256
1432            | Service::TupleHash256
1433            | Service::TupleHashXof256
1434            | Service::ParallelHash256
1435            | Service::ParallelHashXof256
1436            // HMAC with allowed hashes
1437            | Service::HmacSha256
1438            | Service::HmacSha384
1439            | Service::HmacSha512
1440            | Service::HmacSha3_256
1441            | Service::HmacSha3_384
1442            | Service::HmacSha3_512
1443            // CMAC-AES-256
1444            | Service::CmacAes256
1445            // DRBGs backed by AES-256 or allowed hashes
1446            | Service::CtrDrbgAes256
1447            | Service::HashDrbgSha256
1448            | Service::HashDrbgSha384
1449            | Service::HashDrbgSha512
1450            | Service::HmacDrbgSha256
1451            | Service::HmacDrbgSha384
1452            | Service::HmacDrbgSha512
1453            // KDFs with allowed backing
1454            | Service::HkdfSha256
1455            | Service::HkdfSha384
1456            | Service::HkdfSha512
1457            | Service::KbkdfHmacSha256
1458            | Service::KbkdfHmacSha384
1459            | Service::KbkdfHmacSha512
1460            | Service::KbkdfCmacAes256
1461            | Service::Pbkdf2HmacSha256
1462            | Service::Pbkdf2HmacSha384
1463            | Service::Pbkdf2HmacSha512
1464            // ECDSA/ECDH P-384
1465            | Service::EcdsaP384Sign
1466            | Service::EcdsaP384Verify
1467            | Service::EcdsaP384Keygen
1468            | Service::EcdhP384
1469            // RSA >= 3072
1470            | Service::RsaKeygen3072
1471            | Service::RsaPkcs1v15Sign3072
1472            | Service::RsaPssSign3072
1473            | Service::RsaOaep3072
1474            | Service::RsaPkcs1v15Verify3072
1475            | Service::RsaPssVerify3072
1476            | Service::RsaKeygen4096
1477            | Service::RsaPkcs1v15Sign4096
1478            | Service::RsaPssSign4096
1479            | Service::RsaOaep4096
1480            | Service::RsaPkcs1v15Verify4096
1481            | Service::RsaPssVerify4096
1482            // DH >= 3072
1483            | Service::Dh3072
1484            // TLS 1.3 KDF — accepted as the modern transport KDF in the
1485            // transition profile too (TLS 1.3 with classical-only ciphers
1486            // is CNSA-1.0-compatible)
1487            | Service::Tls13Kdf
1488            // PQ algorithms (allowed during transition for hybrid use)
1489            | Service::MlKem1024Encaps
1490            | Service::MlKem1024Decaps
1491            | Service::MlKem1024Keygen
1492            | Service::MlDsa87Sign
1493            | Service::MlDsa87Verify
1494            | Service::MlDsa87Keygen
1495            // LMS — mirrors CNSA 2.0's 8-pair subset during the transition.
1496            // Stateful HBS is permitted under both profiles for software/firmware
1497            // signing per CNSSP 15; the 72 non-listed pairs default-block.
1498            | Service::LmsSha256M32H10W4Sign
1499            | Service::LmsSha256M32H10W4Verify
1500            | Service::LmsSha256M32H10W8Sign
1501            | Service::LmsSha256M32H10W8Verify
1502            | Service::LmsSha256M32H15W4Sign
1503            | Service::LmsSha256M32H15W4Verify
1504            | Service::LmsSha256M32H15W8Sign
1505            | Service::LmsSha256M32H15W8Verify
1506            | Service::LmsSha256M32H20W4Sign
1507            | Service::LmsSha256M32H20W4Verify
1508            | Service::LmsSha256M32H20W8Sign
1509            | Service::LmsSha256M32H20W8Verify
1510            | Service::LmsSha256M32H25W4Sign
1511            | Service::LmsSha256M32H25W4Verify
1512            | Service::LmsSha256M32H25W8Sign
1513            | Service::LmsSha256M32H25W8Verify
1514            | Service::XmssSign
1515            | Service::XmssVerify
1516    )
1517}
1518
1519// -------------------------------------------------------------------------
1520// Test-only utilities
1521// -------------------------------------------------------------------------
1522//
1523// The global `STATE` makes the normal lifecycle effectively run-once per
1524// process. Unit tests in this crate need to exercise multiple transitions,
1525// so we expose a test-only reset helper gated behind `cfg(test)`. It is
1526// **not** part of the public API and is not compiled into release builds.
1527
1528#[cfg(test)]
1529fn reset_for_test() {
1530    STATE.store(State::PowerOff as u8, Ordering::Release);
1531    PROFILE.store(AlgorithmProfile::Unrestricted as u8, Ordering::Release);
1532}
1533
1534#[cfg(test)]
1535extern crate alloc;
1536
1537#[cfg(test)]
1538#[allow(clippy::unwrap_used, clippy::panic)]
1539mod tests {
1540    use super::{
1541        AlgorithmProfile, Error, KatEntry, SelfTest, SelfTestFailure, Service, State,
1542        active_profile, enter_error_state, initialize, initialize_with_profile,
1543        initialize_with_tests, is_allowed, is_operational, require_allowed, require_operational,
1544        reset_for_test, state,
1545    };
1546    use alloc::string::ToString;
1547
1548    // Both of these helpers are used as `fn() -> Result<(), SelfTestFailure>`
1549    // function pointers in `KatEntry`, so the `Result` wrapper is mandatory
1550    // even though clippy sees each body as always returning the same variant.
1551    #[allow(clippy::unnecessary_wraps)]
1552    fn always_pass() -> Result<(), SelfTestFailure> {
1553        Ok(())
1554    }
1555    fn always_fail() -> Result<(), SelfTestFailure> {
1556        Err(SelfTestFailure)
1557    }
1558
1559    // All tests in this module share the single global `STATE`, so they
1560    // cannot run in parallel. `cargo test` in this crate must be invoked
1561    // with `--test-threads=1` — enforced by the CI workflow.
1562
1563    #[test]
1564    fn initial_state_is_power_off() {
1565        reset_for_test();
1566        assert_eq!(state(), State::PowerOff);
1567        assert!(!is_operational());
1568    }
1569
1570    #[test]
1571    fn initialize_transitions_to_operational() {
1572        reset_for_test();
1573        initialize().unwrap();
1574        assert_eq!(state(), State::Operational);
1575        assert!(is_operational());
1576        require_operational().unwrap();
1577    }
1578
1579    #[test]
1580    fn second_initialize_reports_already_initialized() {
1581        reset_for_test();
1582        initialize().unwrap();
1583        match initialize() {
1584            Err(Error::AlreadyInitialized) => {}
1585            other => panic!("expected AlreadyInitialized, got {other:?}"),
1586        }
1587    }
1588
1589    #[test]
1590    fn require_operational_rejects_power_off() {
1591        reset_for_test();
1592        match require_operational() {
1593            Err(Error::NotOperational {
1594                current: State::PowerOff,
1595            }) => {}
1596            other => panic!("expected NotOperational{{PowerOff}}, got {other:?}"),
1597        }
1598    }
1599
1600    #[test]
1601    fn enter_error_state_is_terminal_for_require_operational() {
1602        reset_for_test();
1603        initialize().unwrap();
1604        assert!(is_operational());
1605        enter_error_state("unit-test forced failure");
1606        assert_eq!(state(), State::Error);
1607        match require_operational() {
1608            Err(Error::NotOperational {
1609                current: State::Error,
1610            }) => {}
1611            other => panic!("expected NotOperational{{Error}}, got {other:?}"),
1612        }
1613    }
1614
1615    #[test]
1616    fn state_display_is_stable() {
1617        // The exact strings appear in log output and audit trails,
1618        // so pin them.
1619        assert_eq!(State::PowerOff.to_string(), "PowerOff");
1620        assert_eq!(State::SelfTest.to_string(), "SelfTest");
1621        assert_eq!(State::Operational.to_string(), "Operational");
1622        assert_eq!(State::Error.to_string(), "Error");
1623    }
1624
1625    #[test]
1626    fn registry_runs_passing_tests_and_reaches_operational() {
1627        reset_for_test();
1628        let tests = [
1629            KatEntry {
1630                name: "dummy-pass-a",
1631                run: always_pass,
1632            },
1633            KatEntry {
1634                name: "dummy-pass-b",
1635                run: always_pass,
1636            },
1637        ];
1638        initialize_with_tests(&tests).unwrap();
1639        assert_eq!(state(), State::Operational);
1640    }
1641
1642    #[test]
1643    fn registry_failing_test_latches_error_and_returns_name() {
1644        reset_for_test();
1645        let tests = [
1646            KatEntry {
1647                name: "dummy-pass",
1648                run: always_pass,
1649            },
1650            KatEntry {
1651                name: "dummy-fail",
1652                run: always_fail,
1653            },
1654            // This third entry must never run.
1655            KatEntry {
1656                name: "dummy-unreached",
1657                run: always_pass,
1658            },
1659        ];
1660        match initialize_with_tests(&tests) {
1661            Err(Error::SelfTestFailed { test: "dummy-fail" }) => {}
1662            other => panic!("expected SelfTestFailed{{dummy-fail}}, got {other:?}"),
1663        }
1664        assert_eq!(state(), State::Error);
1665        // Operational-only guard must reject calls from here on.
1666        assert!(matches!(
1667            require_operational(),
1668            Err(Error::NotOperational {
1669                current: State::Error
1670            })
1671        ));
1672    }
1673
1674    // Trivial SelfTest implementation exercised outside the registry
1675    // so the trait stays part of the compiled public surface.
1676    struct DummyTest;
1677    impl SelfTest for DummyTest {
1678        const NAME: &'static str = "dummy-trait-vector";
1679        fn run() -> Result<(), SelfTestFailure> {
1680            Ok(())
1681        }
1682    }
1683
1684    #[test]
1685    fn dummy_self_test_trait_is_usable() {
1686        assert_eq!(DummyTest::NAME, "dummy-trait-vector");
1687        DummyTest::run().unwrap();
1688    }
1689
1690    // =====================================================================
1691    // Algorithm profile gating tests
1692    // =====================================================================
1693
1694    #[test]
1695    fn default_profile_is_unrestricted() {
1696        reset_for_test();
1697        assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
1698    }
1699
1700    #[test]
1701    fn initialize_with_profile_sets_cnsa2() {
1702        reset_for_test();
1703        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
1704        assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
1705        assert!(is_operational());
1706    }
1707
1708    #[test]
1709    fn initialize_with_profile_sets_cnsa1() {
1710        reset_for_test();
1711        initialize_with_profile(&[], AlgorithmProfile::Cnsa1).unwrap();
1712        assert_eq!(active_profile(), AlgorithmProfile::Cnsa1);
1713        assert!(is_operational());
1714    }
1715
1716    #[test]
1717    fn unrestricted_allows_all_services() {
1718        // Spot-check: every category should pass in Unrestricted.
1719        let spot = [
1720            Service::Sha1,
1721            Service::Sha256,
1722            Service::Aes128Ecb,
1723            Service::Aes256Gcm,
1724            Service::CtrDrbgAes128,
1725            Service::EcdsaP256Sign,
1726            Service::Ed25519Sign,
1727            Service::RsaPssSign2048,
1728            Service::MlKem1024Encaps,
1729            Service::LmsSha256M32H10W4Sign,
1730            Service::Dh3072,
1731            Service::SlhDsaSha2256sSign,
1732            Service::Tls12Kdf,
1733            Service::Tls13Kdf,
1734        ];
1735        for svc in spot {
1736            assert!(
1737                is_allowed(AlgorithmProfile::Unrestricted, svc),
1738                "{svc} should be allowed in Unrestricted"
1739            );
1740        }
1741    }
1742
1743    #[test]
1744    fn cnsa2_allows_aes256_and_pq() {
1745        let allowed = [
1746            Service::Aes256Gcm,
1747            Service::Aes256Cbc,
1748            Service::Sha384,
1749            Service::Sha512,
1750            Service::Sha3_384,
1751            Service::Sha3_512,
1752            Service::HmacSha384,
1753            Service::HmacSha512,
1754            Service::CtrDrbgAes256,
1755            Service::HashDrbgSha384,
1756            Service::HmacDrbgSha512,
1757            Service::Kmac256,
1758            Service::KmacXof256,
1759            Service::MlKem1024Encaps,
1760            Service::MlKem1024Decaps,
1761            Service::MlKem1024Keygen,
1762            Service::MlDsa87Sign,
1763            Service::MlDsa87Verify,
1764            Service::MlDsa87Keygen,
1765            Service::LmsSha256M32H10W4Sign,
1766            Service::LmsSha256M32H10W4Verify,
1767            Service::XmssSign,
1768            Service::XmssVerify,
1769            Service::Tls13Kdf,
1770        ];
1771        for svc in allowed {
1772            assert!(
1773                is_allowed(AlgorithmProfile::Cnsa2, svc),
1774                "{svc} should be allowed in CNSA 2.0"
1775            );
1776        }
1777    }
1778
1779    #[test]
1780    fn cnsa2_blocks_classical_and_small_keys() {
1781        let blocked = [
1782            Service::Sha1,
1783            Service::Sha224,
1784            Service::Sha256,
1785            Service::Sha512_224,
1786            Service::Sha512_256,
1787            Service::Sha3_224,
1788            Service::Sha3_256,
1789            Service::Shake128,
1790            Service::Kmac128,
1791            Service::Aes128Ecb,
1792            Service::Aes128Gcm,
1793            Service::Aes192Cbc,
1794            Service::CtrDrbgAes128,
1795            Service::CtrDrbgAes192,
1796            Service::HashDrbgSha256,
1797            Service::HmacDrbgSha256,
1798            Service::HmacSha1,
1799            Service::HmacSha256,
1800            Service::CmacAes128,
1801            Service::CmacAes192,
1802            Service::EcdsaP256Sign,
1803            Service::EcdsaP256Verify,
1804            Service::EcdhP256,
1805            Service::Ed25519Sign,
1806            Service::Ed25519Verify,
1807            Service::RsaPssSign2048,
1808            Service::RsaOaep2048,
1809            Service::Tls12Kdf,
1810            // SLH-DSA-SHA2-128s is not in CNSA 2.0 (only SHA-2 256s is the
1811            // CNSSP-15 mandate; the other 11 parameter sets are excluded).
1812            Service::SlhDsaSha2128sSign,
1813            Service::MlDsa44Sign,
1814            Service::MlDsa44Verify,
1815            Service::MlDsa44Keygen,
1816            Service::MlDsa65Sign,
1817            Service::MlDsa65Verify,
1818            Service::MlDsa65Keygen,
1819        ];
1820        for svc in blocked {
1821            assert!(
1822                !is_allowed(AlgorithmProfile::Cnsa2, svc),
1823                "{svc} should be BLOCKED in CNSA 2.0"
1824            );
1825        }
1826    }
1827
1828    #[test]
1829    fn cnsa1_allows_p384_rsa3072_and_pq() {
1830        let allowed = [
1831            Service::Aes256Gcm,
1832            Service::Sha384,
1833            Service::Sha256,
1834            Service::EcdsaP384Sign,
1835            Service::EcdsaP384Verify,
1836            Service::EcdsaP384Keygen,
1837            Service::EcdhP384,
1838            Service::RsaPssSign3072,
1839            Service::RsaOaep3072,
1840            Service::RsaPssSign4096,
1841            Service::Dh3072,
1842            Service::MlKem1024Encaps,
1843            Service::LmsSha256M32H10W4Sign,
1844            Service::Tls13Kdf,
1845        ];
1846        for svc in allowed {
1847            assert!(
1848                is_allowed(AlgorithmProfile::Cnsa1, svc),
1849                "{svc} should be allowed in CNSA 1.0"
1850            );
1851        }
1852    }
1853
1854    #[test]
1855    fn cnsa1_blocks_small_keys_and_ed25519() {
1856        let blocked = [
1857            Service::Sha1,
1858            Service::Sha224,
1859            Service::Aes128Ecb,
1860            Service::Aes192Gcm,
1861            Service::CtrDrbgAes128,
1862            Service::EcdsaP256Sign,
1863            Service::EcdhP256,
1864            Service::Ed25519Sign,
1865            Service::Ed25519Verify,
1866            Service::RsaPssSign2048,
1867            Service::RsaOaep2048,
1868            Service::Tls12Kdf,
1869        ];
1870        for svc in blocked {
1871            assert!(
1872                !is_allowed(AlgorithmProfile::Cnsa1, svc),
1873                "{svc} should be BLOCKED in CNSA 1.0"
1874            );
1875        }
1876    }
1877
1878    #[test]
1879    // 160 enumerated variants + 16 CNSA-allowed entries is naturally long;
1880    // any "natural factoring" (helper to build the array, macro to emit it)
1881    // would obscure the audit-readable enumeration. CMVP reviewers benefit
1882    // from the explicit, line-by-line variant list.
1883    #[allow(clippy::too_many_lines)]
1884    fn lms_gating_is_exhaustive_across_all_160_variants() {
1885        // Enumerates every LMS Service variant (80 (LMS, LM-OTS) pairs ×
1886        // {Sign, Verify} = 160 entries) and verifies the fail-safe gating:
1887        // only the 8 CNSA-2-permitted pairs (SHA-256/M=32, H{10,15,20,25}
1888        // × W{4,8}) × 2 ops = 16 entries are allowed under CNSA 2.0;
1889        // CNSA 1.0 mirrors that same 16-entry subset (transition profile);
1890        // all 160 are permitted under Unrestricted. If a new LMS variant
1891        // is added without a matching entry in this array, the
1892        // length assertion catches the drift.
1893        let all_160 = [
1894            // SHA-256 / N=32 family (20 pairs, 40 entries).
1895            Service::LmsSha256M32H5W1Sign,
1896            Service::LmsSha256M32H5W1Verify,
1897            Service::LmsSha256M32H5W2Sign,
1898            Service::LmsSha256M32H5W2Verify,
1899            Service::LmsSha256M32H5W4Sign,
1900            Service::LmsSha256M32H5W4Verify,
1901            Service::LmsSha256M32H5W8Sign,
1902            Service::LmsSha256M32H5W8Verify,
1903            Service::LmsSha256M32H10W1Sign,
1904            Service::LmsSha256M32H10W1Verify,
1905            Service::LmsSha256M32H10W2Sign,
1906            Service::LmsSha256M32H10W2Verify,
1907            Service::LmsSha256M32H10W4Sign,
1908            Service::LmsSha256M32H10W4Verify,
1909            Service::LmsSha256M32H10W8Sign,
1910            Service::LmsSha256M32H10W8Verify,
1911            Service::LmsSha256M32H15W1Sign,
1912            Service::LmsSha256M32H15W1Verify,
1913            Service::LmsSha256M32H15W2Sign,
1914            Service::LmsSha256M32H15W2Verify,
1915            Service::LmsSha256M32H15W4Sign,
1916            Service::LmsSha256M32H15W4Verify,
1917            Service::LmsSha256M32H15W8Sign,
1918            Service::LmsSha256M32H15W8Verify,
1919            Service::LmsSha256M32H20W1Sign,
1920            Service::LmsSha256M32H20W1Verify,
1921            Service::LmsSha256M32H20W2Sign,
1922            Service::LmsSha256M32H20W2Verify,
1923            Service::LmsSha256M32H20W4Sign,
1924            Service::LmsSha256M32H20W4Verify,
1925            Service::LmsSha256M32H20W8Sign,
1926            Service::LmsSha256M32H20W8Verify,
1927            Service::LmsSha256M32H25W1Sign,
1928            Service::LmsSha256M32H25W1Verify,
1929            Service::LmsSha256M32H25W2Sign,
1930            Service::LmsSha256M32H25W2Verify,
1931            Service::LmsSha256M32H25W4Sign,
1932            Service::LmsSha256M32H25W4Verify,
1933            Service::LmsSha256M32H25W8Sign,
1934            Service::LmsSha256M32H25W8Verify,
1935            // SHA-256 / N=24 family (20 pairs, 40 entries).
1936            Service::LmsSha256M24H5W1Sign,
1937            Service::LmsSha256M24H5W1Verify,
1938            Service::LmsSha256M24H5W2Sign,
1939            Service::LmsSha256M24H5W2Verify,
1940            Service::LmsSha256M24H5W4Sign,
1941            Service::LmsSha256M24H5W4Verify,
1942            Service::LmsSha256M24H5W8Sign,
1943            Service::LmsSha256M24H5W8Verify,
1944            Service::LmsSha256M24H10W1Sign,
1945            Service::LmsSha256M24H10W1Verify,
1946            Service::LmsSha256M24H10W2Sign,
1947            Service::LmsSha256M24H10W2Verify,
1948            Service::LmsSha256M24H10W4Sign,
1949            Service::LmsSha256M24H10W4Verify,
1950            Service::LmsSha256M24H10W8Sign,
1951            Service::LmsSha256M24H10W8Verify,
1952            Service::LmsSha256M24H15W1Sign,
1953            Service::LmsSha256M24H15W1Verify,
1954            Service::LmsSha256M24H15W2Sign,
1955            Service::LmsSha256M24H15W2Verify,
1956            Service::LmsSha256M24H15W4Sign,
1957            Service::LmsSha256M24H15W4Verify,
1958            Service::LmsSha256M24H15W8Sign,
1959            Service::LmsSha256M24H15W8Verify,
1960            Service::LmsSha256M24H20W1Sign,
1961            Service::LmsSha256M24H20W1Verify,
1962            Service::LmsSha256M24H20W2Sign,
1963            Service::LmsSha256M24H20W2Verify,
1964            Service::LmsSha256M24H20W4Sign,
1965            Service::LmsSha256M24H20W4Verify,
1966            Service::LmsSha256M24H20W8Sign,
1967            Service::LmsSha256M24H20W8Verify,
1968            Service::LmsSha256M24H25W1Sign,
1969            Service::LmsSha256M24H25W1Verify,
1970            Service::LmsSha256M24H25W2Sign,
1971            Service::LmsSha256M24H25W2Verify,
1972            Service::LmsSha256M24H25W4Sign,
1973            Service::LmsSha256M24H25W4Verify,
1974            Service::LmsSha256M24H25W8Sign,
1975            Service::LmsSha256M24H25W8Verify,
1976            // SHAKE-256 / N=32 family (20 pairs, 40 entries).
1977            Service::LmsShakeM32H5W1Sign,
1978            Service::LmsShakeM32H5W1Verify,
1979            Service::LmsShakeM32H5W2Sign,
1980            Service::LmsShakeM32H5W2Verify,
1981            Service::LmsShakeM32H5W4Sign,
1982            Service::LmsShakeM32H5W4Verify,
1983            Service::LmsShakeM32H5W8Sign,
1984            Service::LmsShakeM32H5W8Verify,
1985            Service::LmsShakeM32H10W1Sign,
1986            Service::LmsShakeM32H10W1Verify,
1987            Service::LmsShakeM32H10W2Sign,
1988            Service::LmsShakeM32H10W2Verify,
1989            Service::LmsShakeM32H10W4Sign,
1990            Service::LmsShakeM32H10W4Verify,
1991            Service::LmsShakeM32H10W8Sign,
1992            Service::LmsShakeM32H10W8Verify,
1993            Service::LmsShakeM32H15W1Sign,
1994            Service::LmsShakeM32H15W1Verify,
1995            Service::LmsShakeM32H15W2Sign,
1996            Service::LmsShakeM32H15W2Verify,
1997            Service::LmsShakeM32H15W4Sign,
1998            Service::LmsShakeM32H15W4Verify,
1999            Service::LmsShakeM32H15W8Sign,
2000            Service::LmsShakeM32H15W8Verify,
2001            Service::LmsShakeM32H20W1Sign,
2002            Service::LmsShakeM32H20W1Verify,
2003            Service::LmsShakeM32H20W2Sign,
2004            Service::LmsShakeM32H20W2Verify,
2005            Service::LmsShakeM32H20W4Sign,
2006            Service::LmsShakeM32H20W4Verify,
2007            Service::LmsShakeM32H20W8Sign,
2008            Service::LmsShakeM32H20W8Verify,
2009            Service::LmsShakeM32H25W1Sign,
2010            Service::LmsShakeM32H25W1Verify,
2011            Service::LmsShakeM32H25W2Sign,
2012            Service::LmsShakeM32H25W2Verify,
2013            Service::LmsShakeM32H25W4Sign,
2014            Service::LmsShakeM32H25W4Verify,
2015            Service::LmsShakeM32H25W8Sign,
2016            Service::LmsShakeM32H25W8Verify,
2017            // SHAKE-256 / N=24 family (20 pairs, 40 entries).
2018            Service::LmsShakeM24H5W1Sign,
2019            Service::LmsShakeM24H5W1Verify,
2020            Service::LmsShakeM24H5W2Sign,
2021            Service::LmsShakeM24H5W2Verify,
2022            Service::LmsShakeM24H5W4Sign,
2023            Service::LmsShakeM24H5W4Verify,
2024            Service::LmsShakeM24H5W8Sign,
2025            Service::LmsShakeM24H5W8Verify,
2026            Service::LmsShakeM24H10W1Sign,
2027            Service::LmsShakeM24H10W1Verify,
2028            Service::LmsShakeM24H10W2Sign,
2029            Service::LmsShakeM24H10W2Verify,
2030            Service::LmsShakeM24H10W4Sign,
2031            Service::LmsShakeM24H10W4Verify,
2032            Service::LmsShakeM24H10W8Sign,
2033            Service::LmsShakeM24H10W8Verify,
2034            Service::LmsShakeM24H15W1Sign,
2035            Service::LmsShakeM24H15W1Verify,
2036            Service::LmsShakeM24H15W2Sign,
2037            Service::LmsShakeM24H15W2Verify,
2038            Service::LmsShakeM24H15W4Sign,
2039            Service::LmsShakeM24H15W4Verify,
2040            Service::LmsShakeM24H15W8Sign,
2041            Service::LmsShakeM24H15W8Verify,
2042            Service::LmsShakeM24H20W1Sign,
2043            Service::LmsShakeM24H20W1Verify,
2044            Service::LmsShakeM24H20W2Sign,
2045            Service::LmsShakeM24H20W2Verify,
2046            Service::LmsShakeM24H20W4Sign,
2047            Service::LmsShakeM24H20W4Verify,
2048            Service::LmsShakeM24H20W8Sign,
2049            Service::LmsShakeM24H20W8Verify,
2050            Service::LmsShakeM24H25W1Sign,
2051            Service::LmsShakeM24H25W1Verify,
2052            Service::LmsShakeM24H25W2Sign,
2053            Service::LmsShakeM24H25W2Verify,
2054            Service::LmsShakeM24H25W4Sign,
2055            Service::LmsShakeM24H25W4Verify,
2056            Service::LmsShakeM24H25W8Sign,
2057            Service::LmsShakeM24H25W8Verify,
2058        ];
2059        assert_eq!(all_160.len(), 160, "LMS enumeration drift");
2060
2061        // CNSA 2.0 permitted: SHA-256/M=32, H{10,15,20,25} × W{4,8} = 8 pairs.
2062        let cnsa_allowed_set = [
2063            Service::LmsSha256M32H10W4Sign,
2064            Service::LmsSha256M32H10W4Verify,
2065            Service::LmsSha256M32H10W8Sign,
2066            Service::LmsSha256M32H10W8Verify,
2067            Service::LmsSha256M32H15W4Sign,
2068            Service::LmsSha256M32H15W4Verify,
2069            Service::LmsSha256M32H15W8Sign,
2070            Service::LmsSha256M32H15W8Verify,
2071            Service::LmsSha256M32H20W4Sign,
2072            Service::LmsSha256M32H20W4Verify,
2073            Service::LmsSha256M32H20W8Sign,
2074            Service::LmsSha256M32H20W8Verify,
2075            Service::LmsSha256M32H25W4Sign,
2076            Service::LmsSha256M32H25W4Verify,
2077            Service::LmsSha256M32H25W8Sign,
2078            Service::LmsSha256M32H25W8Verify,
2079        ];
2080        assert_eq!(cnsa_allowed_set.len(), 16, "CNSA permitted-set drift");
2081
2082        for svc in all_160 {
2083            // Unrestricted: every LMS variant is permitted.
2084            assert!(
2085                is_allowed(AlgorithmProfile::Unrestricted, svc),
2086                "{svc} must be allowed in Unrestricted"
2087            );
2088
2089            let expected = cnsa_allowed_set.contains(&svc);
2090
2091            // CNSA 2.0: only the 16 CNSSP-15-conformant entries pass;
2092            // the other 144 default-block via the `matches!` fail-safe.
2093            let actual_cnsa2 = is_allowed(AlgorithmProfile::Cnsa2, svc);
2094            assert_eq!(
2095                actual_cnsa2, expected,
2096                "{svc} CNSA 2.0 gating mismatch (expected={expected}, actual={actual_cnsa2})"
2097            );
2098
2099            // CNSA 1.0: mirrors CNSA 2.0 on LMS (transition profile).
2100            let actual_cnsa1 = is_allowed(AlgorithmProfile::Cnsa1, svc);
2101            assert_eq!(
2102                actual_cnsa1, expected,
2103                "{svc} CNSA 1.0 gating mismatch (expected={expected}, actual={actual_cnsa1})"
2104            );
2105        }
2106    }
2107
2108    #[test]
2109    fn slh_dsa_gating_is_exhaustive_across_all_36_variants() {
2110        // Enumerates every SLH-DSA Service variant (12 parameter sets ×
2111        // {Keygen, Sign, Verify} = 36 entries) and verifies the
2112        // fail-safe gating: only the CNSSP-15 mandate
2113        // (SLH-DSA-SHA2-256s) is allowed under CNSA 2.0; all 36 are
2114        // blocked under CNSA 1.0; all 36 are permitted under
2115        // Unrestricted.  If a new SLH-DSA variant is added without a
2116        // matching entry in this array, the assertion at the end
2117        // catches the gap.
2118        let all_36 = [
2119            // SHA-2 family (18 entries).
2120            Service::SlhDsaSha2256sKeygen,
2121            Service::SlhDsaSha2256sSign,
2122            Service::SlhDsaSha2256sVerify,
2123            Service::SlhDsaSha2128sKeygen,
2124            Service::SlhDsaSha2128sSign,
2125            Service::SlhDsaSha2128sVerify,
2126            Service::SlhDsaSha2128fKeygen,
2127            Service::SlhDsaSha2128fSign,
2128            Service::SlhDsaSha2128fVerify,
2129            Service::SlhDsaSha2192sKeygen,
2130            Service::SlhDsaSha2192sSign,
2131            Service::SlhDsaSha2192sVerify,
2132            Service::SlhDsaSha2192fKeygen,
2133            Service::SlhDsaSha2192fSign,
2134            Service::SlhDsaSha2192fVerify,
2135            Service::SlhDsaSha2256fKeygen,
2136            Service::SlhDsaSha2256fSign,
2137            Service::SlhDsaSha2256fVerify,
2138            // SHAKE family (18 entries).
2139            Service::SlhDsaShake128sKeygen,
2140            Service::SlhDsaShake128sSign,
2141            Service::SlhDsaShake128sVerify,
2142            Service::SlhDsaShake128fKeygen,
2143            Service::SlhDsaShake128fSign,
2144            Service::SlhDsaShake128fVerify,
2145            Service::SlhDsaShake192sKeygen,
2146            Service::SlhDsaShake192sSign,
2147            Service::SlhDsaShake192sVerify,
2148            Service::SlhDsaShake192fKeygen,
2149            Service::SlhDsaShake192fSign,
2150            Service::SlhDsaShake192fVerify,
2151            Service::SlhDsaShake256sKeygen,
2152            Service::SlhDsaShake256sSign,
2153            Service::SlhDsaShake256sVerify,
2154            Service::SlhDsaShake256fKeygen,
2155            Service::SlhDsaShake256fSign,
2156            Service::SlhDsaShake256fVerify,
2157        ];
2158        assert_eq!(all_36.len(), 36, "SLH-DSA enumeration drift");
2159
2160        let cnsa2_allowed_set = [
2161            Service::SlhDsaSha2256sKeygen,
2162            Service::SlhDsaSha2256sSign,
2163            Service::SlhDsaSha2256sVerify,
2164        ];
2165
2166        for svc in all_36 {
2167            // Unrestricted: every SLH-DSA variant is permitted.
2168            assert!(
2169                is_allowed(AlgorithmProfile::Unrestricted, svc),
2170                "{svc} must be allowed in Unrestricted"
2171            );
2172
2173            // CNSA 1.0: no SLH-DSA variant is permitted.
2174            assert!(
2175                !is_allowed(AlgorithmProfile::Cnsa1, svc),
2176                "{svc} must be BLOCKED in CNSA 1.0"
2177            );
2178
2179            // CNSA 2.0: only the three CNSSP-15-mandated entries pass;
2180            // all 33 others are blocked.
2181            let expected_cnsa2 = cnsa2_allowed_set.contains(&svc);
2182            let actual_cnsa2 = is_allowed(AlgorithmProfile::Cnsa2, svc);
2183            assert_eq!(
2184                actual_cnsa2, expected_cnsa2,
2185                "{svc} CNSA 2.0 gating mismatch (expected={expected_cnsa2}, actual={actual_cnsa2})"
2186            );
2187        }
2188    }
2189
2190    #[test]
2191    fn require_allowed_returns_restricted_error_under_cnsa2() {
2192        reset_for_test();
2193        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2194        match require_allowed(Service::Aes128Ecb) {
2195            Err(Error::AlgorithmRestricted {
2196                service: Service::Aes128Ecb,
2197            }) => {}
2198            other => panic!("expected AlgorithmRestricted(Aes128Ecb), got {other:?}"),
2199        }
2200    }
2201
2202    #[test]
2203    fn require_allowed_passes_permitted_services() {
2204        reset_for_test();
2205        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2206        require_allowed(Service::Aes256Gcm).unwrap();
2207        require_allowed(Service::Sha384).unwrap();
2208        require_allowed(Service::MlKem1024Encaps).unwrap();
2209    }
2210
2211    #[test]
2212    fn require_allowed_passes_everything_in_unrestricted() {
2213        reset_for_test();
2214        initialize().unwrap();
2215        // Spot-check a few from each end of the spectrum.
2216        require_allowed(Service::Sha1).unwrap();
2217        require_allowed(Service::Aes128Ecb).unwrap();
2218        require_allowed(Service::Ed25519Sign).unwrap();
2219        require_allowed(Service::MlKem1024Encaps).unwrap();
2220    }
2221
2222    #[test]
2223    fn algorithm_profile_display() {
2224        assert_eq!(AlgorithmProfile::Unrestricted.to_string(), "Unrestricted");
2225        assert_eq!(AlgorithmProfile::Cnsa2.to_string(), "CNSA 2.0");
2226        assert_eq!(AlgorithmProfile::Cnsa1.to_string(), "CNSA 1.0");
2227    }
2228
2229    #[test]
2230    fn algorithm_restricted_error_display() {
2231        reset_for_test();
2232        initialize_with_profile(&[], AlgorithmProfile::Cnsa2).unwrap();
2233        let err = Error::AlgorithmRestricted {
2234            service: Service::Aes128Ecb,
2235        };
2236        let msg = err.to_string();
2237        assert!(msg.contains("AES-128-ECB"), "got: {msg}");
2238        assert!(msg.contains("CNSA 2.0"), "got: {msg}");
2239    }
2240
2241    #[test]
2242    fn not_implemented_error_display() {
2243        let err = Error::NotImplemented;
2244        assert_eq!(err.to_string(), "algorithm not yet implemented");
2245    }
2246
2247    #[test]
2248    fn service_display_is_stable() {
2249        // Pin a few representative Display strings.
2250        assert_eq!(Service::Sha256.to_string(), "SHA-256");
2251        assert_eq!(Service::Aes256Gcm.to_string(), "AES-256-GCM");
2252        assert_eq!(Service::MlKem1024Encaps.to_string(), "ML-KEM-1024 encaps");
2253        assert_eq!(Service::Ed25519Sign.to_string(), "Ed25519 sign");
2254    }
2255}