#![no_std]
#![forbid(unsafe_code)]
#[cfg(not(target_has_atomic = "64"))]
compile_error!(
"oxicrypt-module needs 64-bit atomics: the module state and the thread running the \
power-up self-tests are published as one AtomicU64"
);
use core::fmt;
use core::sync::atomic::{AtomicU8, AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum State {
PowerOff = 0,
SelfTest = 1,
Operational = 2,
Error = 3,
IntegrityUnverified = 4,
}
impl State {
const fn from_u8(raw: u8) -> Self {
match raw {
0 => Self::PowerOff,
1 => Self::SelfTest,
2 => Self::Operational,
4 => Self::IntegrityUnverified,
_ => Self::Error,
}
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::PowerOff => "PowerOff",
Self::SelfTest => "SelfTest",
Self::Operational => "Operational",
Self::Error => "Error",
Self::IntegrityUnverified => "IntegrityUnverified",
};
f.write_str(s)
}
}
static STATE: AtomicU64 = AtomicU64::new(POWER_OFF);
const STATE_BITS: u32 = 8;
const POWER_OFF: u64 = State::PowerOff as u64;
const OPERATIONAL: u64 = State::Operational as u64;
const ERROR: u64 = State::Error as u64;
const INTEGRITY_UNVERIFIED: u64 = State::IntegrityUnverified as u64;
const fn self_test_word(owner: u64) -> u64 {
owner.wrapping_shl(STATE_BITS) | State::SelfTest as u64
}
const fn decode(word: u64) -> State {
let [discriminant, ..] = word.to_le_bytes();
let owned = word.wrapping_shr(STATE_BITS) != 0;
match (State::from_u8(discriminant), owned) {
(State::SelfTest, true) => State::SelfTest,
(State::SelfTest, false) | (_, true) => State::Error,
(state, false) => state,
}
}
fn caller_owner_id() -> Option<u64> {
owner_id(os_thread_id())
}
fn owner_id(raw: Option<u64>) -> Option<u64> {
raw.filter(|&id| id != 0 && id.leading_zeros() >= STATE_BITS)
}
pub const THREAD_ID_CHECK: &str = "initialising thread id: none the module can record";
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
windows
))]
fn os_thread_id() -> Option<u64> {
oxicrypt_imageread::current_thread_id().ok()
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
windows
)))]
fn os_thread_id() -> Option<u64> {
None
}
pub fn state() -> State {
decode(STATE.load(Ordering::Acquire))
}
pub fn is_operational() -> bool {
STATE.load(Ordering::Acquire) == OPERATIONAL
}
pub fn require_operational() -> Result<(), Error> {
let word = STATE.load(Ordering::Acquire);
if word == OPERATIONAL {
Ok(())
} else {
Err(Error::NotOperational {
current: decode(word),
})
}
}
pub fn require_self_test_started() -> Result<(), Error> {
let word = STATE.load(Ordering::Acquire);
if word == OPERATIONAL {
return Ok(());
}
let current = decode(word);
if current == State::SelfTest && caller_owner_id().map(self_test_word) == Some(word) {
return Ok(());
}
Err(Error::NotOperational { current })
}
#[derive(Debug)]
pub struct SelfTestStarted {
_private: (),
}
const _: () = {
const fn assert_sync<T: Sync>() {}
assert_sync::<SelfTestStarted>();
};
pub fn require_self_test_started_token() -> Result<SelfTestStarted, Error> {
require_self_test_started()?;
Ok(SelfTestStarted { _private: () })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
NotOperational {
current: State,
},
SelfTestFailed {
test: &'static str,
},
ConditionalTestFailed {
reason: &'static str,
},
IntegrityNotAttested,
AlreadyInitialized,
AlreadyFailed,
InvalidInput,
AlgorithmRestricted {
service: Service,
},
NotImplemented,
EntropyInputTooShort,
NonceTooShort,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotOperational { current } => {
write!(f, "FIPS module not operational (state = {current})")
}
Self::SelfTestFailed { test } => {
write!(f, "FIPS power-up self-test failed: {test}")
}
Self::ConditionalTestFailed { reason } => {
write!(f, "FIPS conditional self-test failed: {reason}")
}
Self::IntegrityNotAttested => f.write_str(
"this module did not check itself at startup, so it cannot tell whether this \
copy is the one that was built and signed. That does not mean the module is \
damaged: the program that started it skipped the check.",
),
Self::AlreadyInitialized => f.write_str("FIPS module already initialized"),
Self::AlreadyFailed => f.write_str(
"FIPS module initialization already failed; restart the process to retry",
),
Self::InvalidInput => f.write_str("invalid input to FIPS service"),
Self::AlgorithmRestricted { service } => {
write!(
f,
"algorithm {service} is not permitted under the {} profile",
active_profile()
)
}
Self::NotImplemented => f.write_str("algorithm not yet implemented"),
Self::EntropyInputTooShort => f.write_str(
"DRBG entropy input is shorter than the minimum for the instantiated \
security strength (SP 800-90A Rev. 1 Tables 2 and 3)",
),
Self::NonceTooShort => f.write_str(
"DRBG nonce is shorter than half the instantiated security strength, \
the module's nonce floor (SP 800-90A Rev. 1 §9.1 step 8)",
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SelfTestFailure;
impl fmt::Display for SelfTestFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("self-test failure")
}
}
pub trait SelfTest {
const NAME: &'static str;
fn run() -> Result<(), SelfTestFailure>;
}
#[derive(Debug, Clone, Copy)]
pub struct KatEntry {
pub name: &'static str,
pub run: fn() -> Result<(), SelfTestFailure>,
}
pub fn initialize_with_tests(integrity: &[KatEntry], tests: &[KatEntry]) -> Result<(), Error> {
initialize(integrity, tests, AlgorithmProfile::Unrestricted)
}
fn initialize(
integrity: &[KatEntry],
tests: &[KatEntry],
profile: AlgorithmProfile,
) -> Result<(), Error> {
initialize_as(caller_owner_id(), integrity, tests, profile)
}
fn initialize_as(
owner: Option<u64>,
integrity: &[KatEntry],
tests: &[KatEntry],
profile: AlgorithmProfile,
) -> Result<(), Error> {
let claim = owner.map_or(ERROR, self_test_word);
if let Err(observed) =
STATE.compare_exchange(POWER_OFF, claim, Ordering::AcqRel, Ordering::Acquire)
{
return Err(already_left_power_off(observed, claim));
}
if owner.is_none() {
return Err(Error::SelfTestFailed {
test: THREAD_ID_CHECK,
});
}
let _unwind = UnwindToError { claim };
if integrity.is_empty() {
settle(claim, INTEGRITY_UNVERIFIED);
return Err(Error::IntegrityNotAttested);
}
for entry in integrity.iter().chain(tests) {
if (entry.run)().is_err() {
settle(claim, ERROR);
return Err(Error::SelfTestFailed { test: entry.name });
}
}
PROFILE.store(profile as u8, Ordering::Release);
if STATE
.compare_exchange(claim, OPERATIONAL, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
PROFILE.store(AlgorithmProfile::Unrestricted as u8, Ordering::Release);
return Err(Error::NotOperational { current: state() });
}
Ok(())
}
fn settle(claim: u64, to: u64) {
let _ = STATE.compare_exchange(claim, to, Ordering::AcqRel, Ordering::Acquire);
}
fn already_left_power_off(mut observed: u64, claim: u64) -> Error {
#[cfg(test)]
if decode(observed) == State::SelfTest && observed != claim {
WAITING_LOSERS.fetch_add(1, Ordering::AcqRel);
}
while decode(observed) == State::SelfTest && observed != claim {
core::hint::spin_loop();
observed = STATE.load(Ordering::Acquire);
}
match decode(observed) {
State::Operational => Error::AlreadyInitialized,
State::SelfTest => Error::NotOperational {
current: State::SelfTest,
},
State::PowerOff | State::Error | State::IntegrityUnverified => Error::AlreadyFailed,
}
}
#[cfg(test)]
static WAITING_LOSERS: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
struct UnwindToError {
claim: u64,
}
impl Drop for UnwindToError {
fn drop(&mut self) {
settle(self.claim, ERROR);
}
}
pub fn enter_error_state(_reason: &'static str) {
STATE.store(ERROR, Ordering::Release);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AlgorithmProfile {
Unrestricted = 0,
Cnsa2 = 1,
Cnsa1 = 2,
Migration = 3,
}
impl AlgorithmProfile {
const fn from_u8(raw: u8) -> Self {
match raw {
0 => Self::Unrestricted,
2 => Self::Cnsa1,
3 => Self::Migration,
_ => Self::Cnsa2,
}
}
}
impl fmt::Display for AlgorithmProfile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Unrestricted => "Unrestricted",
Self::Cnsa2 => "CNSA 2.0",
Self::Cnsa1 => "CNSA 1.0",
Self::Migration => "Migration (CNSA 1.0 + CNSA 2.0)",
};
f.write_str(s)
}
}
static PROFILE: AtomicU8 = AtomicU8::new(AlgorithmProfile::Unrestricted as u8);
pub fn active_profile() -> AlgorithmProfile {
AlgorithmProfile::from_u8(PROFILE.load(Ordering::Acquire))
}
pub fn initialize_with_profile(
integrity: &[KatEntry],
tests: &[KatEntry],
profile: AlgorithmProfile,
) -> Result<(), Error> {
initialize(integrity, tests, profile)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
#[allow(missing_docs)]
pub enum Service {
Sha1 = 0,
Sha224 = 1,
Sha256 = 2,
Sha384 = 3,
Sha512 = 4,
Sha512_224 = 5,
Sha512_256 = 6,
Sha3_224 = 10,
Sha3_256 = 11,
Sha3_384 = 12,
Sha3_512 = 13,
Shake128 = 20,
Shake256 = 21,
CShake128 = 22,
CShake256 = 23,
Kmac128 = 24,
Kmac256 = 25,
KmacXof128 = 26,
KmacXof256 = 27,
TupleHash128 = 28,
TupleHash256 = 29,
TupleHashXof128 = 30,
TupleHashXof256 = 31,
ParallelHash128 = 32,
ParallelHash256 = 33,
ParallelHashXof128 = 34,
ParallelHashXof256 = 35,
HmacSha1 = 40,
HmacSha224 = 41,
HmacSha256 = 42,
HmacSha384 = 43,
HmacSha512 = 44,
HmacSha512_224 = 45,
HmacSha512_256 = 46,
HmacSha3_224 = 47,
HmacSha3_256 = 48,
HmacSha3_384 = 49,
HmacSha3_512 = 50,
CmacAes128 = 60,
CmacAes192 = 61,
CmacAes256 = 62,
Aes128Ecb = 70,
Aes128Cbc = 71,
Aes128Ctr = 72,
Aes128Gcm = 73,
Aes128Ccm = 74,
Aes128Kw = 75,
Aes128Kwp = 76,
Aes192Ecb = 80,
Aes192Cbc = 81,
Aes192Ctr = 82,
Aes192Gcm = 83,
Aes192Ccm = 84,
Aes192Kw = 85,
Aes192Kwp = 86,
Aes256Ecb = 90,
Aes256Cbc = 91,
Aes256Ctr = 92,
Aes256Gcm = 93,
Aes256Ccm = 94,
Aes256Kw = 95,
Aes256Kwp = 96,
Aes128 = 97,
Aes192 = 98,
Aes256 = 99,
CtrDrbgAes128 = 100,
CtrDrbgAes192 = 101,
CtrDrbgAes256 = 102,
HashDrbgSha256 = 103,
HashDrbgSha384 = 104,
HashDrbgSha512 = 105,
HmacDrbgSha256 = 106,
HmacDrbgSha384 = 107,
HmacDrbgSha512 = 108,
HkdfSha1 = 120,
HkdfSha256 = 121,
HkdfSha384 = 122,
HkdfSha512 = 123,
KbkdfHmacSha256 = 130,
KbkdfHmacSha384 = 131,
KbkdfHmacSha512 = 132,
KbkdfCmacAes128 = 133,
KbkdfCmacAes192 = 134,
KbkdfCmacAes256 = 135,
Pbkdf2HmacSha1 = 140,
Pbkdf2HmacSha224 = 141,
Pbkdf2HmacSha256 = 142,
Pbkdf2HmacSha384 = 143,
Pbkdf2HmacSha512 = 144,
RsaKeygen2048 = 150,
RsaPkcs1v15Sign2048 = 151,
RsaPssSign2048 = 152,
RsaOaep2048 = 153,
RsaPkcs1v15Verify2048 = 154,
RsaPssVerify2048 = 155,
RsaKeygen3072 = 160,
RsaPkcs1v15Sign3072 = 161,
RsaPssSign3072 = 162,
RsaOaep3072 = 163,
RsaPkcs1v15Verify3072 = 164,
RsaPssVerify3072 = 165,
RsaKeygen4096 = 170,
RsaPkcs1v15Sign4096 = 171,
RsaPssSign4096 = 172,
RsaOaep4096 = 173,
RsaPkcs1v15Verify4096 = 174,
RsaPssVerify4096 = 175,
EcdsaP256Sign = 200,
EcdsaP256Verify = 201,
EcdsaP256Keygen = 202,
EcdsaP384Sign = 210,
EcdsaP384Verify = 211,
EcdsaP384Keygen = 212,
EcdhP256 = 220,
EcdhP384 = 221,
Ed25519Sign = 230,
Ed25519Verify = 231,
Ed25519Keygen = 232,
Tls12Kdf = 240,
Tls13Kdf = 241,
MlKem1024Encaps = 300,
MlKem1024Decaps = 301,
MlKem1024Keygen = 302,
MlKem512Encaps = 303,
MlKem512Decaps = 304,
MlKem512Keygen = 305,
MlKem768Encaps = 306,
MlKem768Decaps = 307,
MlKem768Keygen = 308,
MlDsa87Sign = 310,
MlDsa87Verify = 311,
MlDsa87Keygen = 312,
MlDsa44Sign = 313,
MlDsa44Verify = 314,
MlDsa44Keygen = 315,
MlDsa65Sign = 316,
MlDsa65Verify = 317,
MlDsa65Keygen = 318,
SlhDsaSha2256sKeygen = 320,
SlhDsaSha2256sSign = 321,
SlhDsaSha2256sVerify = 322,
SlhDsaSha2128sKeygen = 323,
SlhDsaSha2128sSign = 324,
SlhDsaSha2128sVerify = 325,
SlhDsaSha2128fKeygen = 326,
SlhDsaSha2128fSign = 327,
SlhDsaSha2128fVerify = 328,
SlhDsaSha2192sKeygen = 329,
SlhDsaSha2192sSign = 330,
SlhDsaSha2192sVerify = 331,
SlhDsaSha2192fKeygen = 332,
SlhDsaSha2192fSign = 333,
SlhDsaSha2192fVerify = 334,
SlhDsaSha2256fKeygen = 335,
SlhDsaSha2256fSign = 336,
SlhDsaSha2256fVerify = 337,
SlhDsaShake128sKeygen = 338,
SlhDsaShake128sSign = 339,
SlhDsaShake128sVerify = 340,
SlhDsaShake128fKeygen = 341,
SlhDsaShake128fSign = 342,
SlhDsaShake128fVerify = 343,
SlhDsaShake192sKeygen = 344,
SlhDsaShake192sSign = 345,
SlhDsaShake192sVerify = 346,
SlhDsaShake192fKeygen = 347,
SlhDsaShake192fSign = 348,
SlhDsaShake192fVerify = 349,
SlhDsaShake256sKeygen = 350,
SlhDsaShake256sSign = 351,
SlhDsaShake256sVerify = 352,
SlhDsaShake256fKeygen = 353,
SlhDsaShake256fSign = 354,
SlhDsaShake256fVerify = 355,
LmsSha256M32H5W1Verify = 501,
LmsSha256M32H5W2Verify = 503,
LmsSha256M32H5W4Verify = 505,
LmsSha256M32H5W8Verify = 507,
LmsSha256M32H10W1Verify = 509,
LmsSha256M32H10W2Verify = 511,
LmsSha256M32H10W4Verify = 513,
LmsSha256M32H10W8Verify = 515,
LmsSha256M32H15W1Verify = 517,
LmsSha256M32H15W2Verify = 519,
LmsSha256M32H15W4Verify = 521,
LmsSha256M32H15W8Verify = 523,
LmsSha256M32H20W1Verify = 525,
LmsSha256M32H20W2Verify = 527,
LmsSha256M32H20W4Verify = 529,
LmsSha256M32H20W8Verify = 531,
LmsSha256M32H25W1Verify = 533,
LmsSha256M32H25W2Verify = 535,
LmsSha256M32H25W4Verify = 537,
LmsSha256M32H25W8Verify = 539,
LmsSha256M24H5W1Verify = 541,
LmsSha256M24H5W2Verify = 543,
LmsSha256M24H5W4Verify = 545,
LmsSha256M24H5W8Verify = 547,
LmsSha256M24H10W1Verify = 549,
LmsSha256M24H10W2Verify = 551,
LmsSha256M24H10W4Verify = 553,
LmsSha256M24H10W8Verify = 555,
LmsSha256M24H15W1Verify = 557,
LmsSha256M24H15W2Verify = 559,
LmsSha256M24H15W4Verify = 561,
LmsSha256M24H15W8Verify = 563,
LmsSha256M24H20W1Verify = 565,
LmsSha256M24H20W2Verify = 567,
LmsSha256M24H20W4Verify = 569,
LmsSha256M24H20W8Verify = 571,
LmsSha256M24H25W1Verify = 573,
LmsSha256M24H25W2Verify = 575,
LmsSha256M24H25W4Verify = 577,
LmsSha256M24H25W8Verify = 579,
LmsShakeM32H5W1Verify = 581,
LmsShakeM32H5W2Verify = 583,
LmsShakeM32H5W4Verify = 585,
LmsShakeM32H5W8Verify = 587,
LmsShakeM32H10W1Verify = 589,
LmsShakeM32H10W2Verify = 591,
LmsShakeM32H10W4Verify = 593,
LmsShakeM32H10W8Verify = 595,
LmsShakeM32H15W1Verify = 597,
LmsShakeM32H15W2Verify = 599,
LmsShakeM32H15W4Verify = 601,
LmsShakeM32H15W8Verify = 603,
LmsShakeM32H20W1Verify = 605,
LmsShakeM32H20W2Verify = 607,
LmsShakeM32H20W4Verify = 609,
LmsShakeM32H20W8Verify = 611,
LmsShakeM32H25W1Verify = 613,
LmsShakeM32H25W2Verify = 615,
LmsShakeM32H25W4Verify = 617,
LmsShakeM32H25W8Verify = 619,
LmsShakeM24H5W1Verify = 621,
LmsShakeM24H5W2Verify = 623,
LmsShakeM24H5W4Verify = 625,
LmsShakeM24H5W8Verify = 627,
LmsShakeM24H10W1Verify = 629,
LmsShakeM24H10W2Verify = 631,
LmsShakeM24H10W4Verify = 633,
LmsShakeM24H10W8Verify = 635,
LmsShakeM24H15W1Verify = 637,
LmsShakeM24H15W2Verify = 639,
LmsShakeM24H15W4Verify = 641,
LmsShakeM24H15W8Verify = 643,
LmsShakeM24H20W1Verify = 645,
LmsShakeM24H20W2Verify = 647,
LmsShakeM24H20W4Verify = 649,
LmsShakeM24H20W8Verify = 651,
LmsShakeM24H25W1Verify = 653,
LmsShakeM24H25W2Verify = 655,
LmsShakeM24H25W4Verify = 657,
LmsShakeM24H25W8Verify = 659,
XmssVerify = 371,
Dh3072 = 380,
}
impl fmt::Display for Service {
#[allow(clippy::too_many_lines)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Sha1 => "SHA-1",
Self::Sha224 => "SHA-224",
Self::Sha256 => "SHA-256",
Self::Sha384 => "SHA-384",
Self::Sha512 => "SHA-512",
Self::Sha512_224 => "SHA-512/224",
Self::Sha512_256 => "SHA-512/256",
Self::Sha3_224 => "SHA3-224",
Self::Sha3_256 => "SHA3-256",
Self::Sha3_384 => "SHA3-384",
Self::Sha3_512 => "SHA3-512",
Self::Shake128 => "SHAKE128",
Self::Shake256 => "SHAKE256",
Self::CShake128 => "cSHAKE128",
Self::CShake256 => "cSHAKE256",
Self::Kmac128 => "KMAC128",
Self::Kmac256 => "KMAC256",
Self::KmacXof128 => "KMACXOF128",
Self::KmacXof256 => "KMACXOF256",
Self::TupleHash128 => "TupleHash128",
Self::TupleHash256 => "TupleHash256",
Self::TupleHashXof128 => "TupleHashXOF128",
Self::TupleHashXof256 => "TupleHashXOF256",
Self::ParallelHash128 => "ParallelHash128",
Self::ParallelHash256 => "ParallelHash256",
Self::ParallelHashXof128 => "ParallelHashXOF128",
Self::ParallelHashXof256 => "ParallelHashXOF256",
Self::HmacSha1 => "HMAC-SHA-1",
Self::HmacSha224 => "HMAC-SHA-224",
Self::HmacSha256 => "HMAC-SHA-256",
Self::HmacSha384 => "HMAC-SHA-384",
Self::HmacSha512 => "HMAC-SHA-512",
Self::HmacSha512_224 => "HMAC-SHA-512/224",
Self::HmacSha512_256 => "HMAC-SHA-512/256",
Self::HmacSha3_224 => "HMAC-SHA3-224",
Self::HmacSha3_256 => "HMAC-SHA3-256",
Self::HmacSha3_384 => "HMAC-SHA3-384",
Self::HmacSha3_512 => "HMAC-SHA3-512",
Self::CmacAes128 => "CMAC-AES-128",
Self::CmacAes192 => "CMAC-AES-192",
Self::CmacAes256 => "CMAC-AES-256",
Self::Aes128Ecb => "AES-128-ECB",
Self::Aes128Cbc => "AES-128-CBC",
Self::Aes128Ctr => "AES-128-CTR",
Self::Aes128Gcm => "AES-128-GCM",
Self::Aes128Ccm => "AES-128-CCM",
Self::Aes128Kw => "AES-128-KW",
Self::Aes128Kwp => "AES-128-KWP",
Self::Aes192Ecb => "AES-192-ECB",
Self::Aes192Cbc => "AES-192-CBC",
Self::Aes192Ctr => "AES-192-CTR",
Self::Aes192Gcm => "AES-192-GCM",
Self::Aes192Ccm => "AES-192-CCM",
Self::Aes192Kw => "AES-192-KW",
Self::Aes192Kwp => "AES-192-KWP",
Self::Aes256Ecb => "AES-256-ECB",
Self::Aes256Cbc => "AES-256-CBC",
Self::Aes256Ctr => "AES-256-CTR",
Self::Aes256Gcm => "AES-256-GCM",
Self::Aes256Ccm => "AES-256-CCM",
Self::Aes256Kw => "AES-256-KW",
Self::Aes256Kwp => "AES-256-KWP",
Self::Aes128 => "AES-128",
Self::Aes192 => "AES-192",
Self::Aes256 => "AES-256",
Self::CtrDrbgAes128 => "CTR_DRBG-AES-128",
Self::CtrDrbgAes192 => "CTR_DRBG-AES-192",
Self::CtrDrbgAes256 => "CTR_DRBG-AES-256",
Self::HashDrbgSha256 => "Hash_DRBG-SHA-256",
Self::HashDrbgSha384 => "Hash_DRBG-SHA-384",
Self::HashDrbgSha512 => "Hash_DRBG-SHA-512",
Self::HmacDrbgSha256 => "HMAC_DRBG-SHA-256",
Self::HmacDrbgSha384 => "HMAC_DRBG-SHA-384",
Self::HmacDrbgSha512 => "HMAC_DRBG-SHA-512",
Self::HkdfSha1 => "HKDF-SHA-1",
Self::HkdfSha256 => "HKDF-SHA-256",
Self::HkdfSha384 => "HKDF-SHA-384",
Self::HkdfSha512 => "HKDF-SHA-512",
Self::KbkdfHmacSha256 => "KBKDF-HMAC-SHA-256",
Self::KbkdfHmacSha384 => "KBKDF-HMAC-SHA-384",
Self::KbkdfHmacSha512 => "KBKDF-HMAC-SHA-512",
Self::KbkdfCmacAes128 => "KBKDF-CMAC-AES-128",
Self::KbkdfCmacAes192 => "KBKDF-CMAC-AES-192",
Self::KbkdfCmacAes256 => "KBKDF-CMAC-AES-256",
Self::Pbkdf2HmacSha1 => "PBKDF2-HMAC-SHA-1",
Self::Pbkdf2HmacSha224 => "PBKDF2-HMAC-SHA-224",
Self::Pbkdf2HmacSha256 => "PBKDF2-HMAC-SHA-256",
Self::Pbkdf2HmacSha384 => "PBKDF2-HMAC-SHA-384",
Self::Pbkdf2HmacSha512 => "PBKDF2-HMAC-SHA-512",
Self::RsaKeygen2048 => "RSA-2048 keygen",
Self::RsaPkcs1v15Sign2048 => "RSA-PKCS1v1.5-Sign-2048",
Self::RsaPssSign2048 => "RSA-PSS-Sign-2048",
Self::RsaOaep2048 => "RSA-OAEP-2048",
Self::RsaPkcs1v15Verify2048 => "RSA-PKCS1v1.5-Verify-2048",
Self::RsaPssVerify2048 => "RSA-PSS-Verify-2048",
Self::RsaKeygen3072 => "RSA-3072 keygen",
Self::RsaPkcs1v15Sign3072 => "RSA-PKCS1v1.5-Sign-3072",
Self::RsaPssSign3072 => "RSA-PSS-Sign-3072",
Self::RsaOaep3072 => "RSA-OAEP-3072",
Self::RsaPkcs1v15Verify3072 => "RSA-PKCS1v1.5-Verify-3072",
Self::RsaPssVerify3072 => "RSA-PSS-Verify-3072",
Self::RsaKeygen4096 => "RSA-4096 keygen",
Self::RsaPkcs1v15Sign4096 => "RSA-PKCS1v1.5-Sign-4096",
Self::RsaPssSign4096 => "RSA-PSS-Sign-4096",
Self::RsaOaep4096 => "RSA-OAEP-4096",
Self::RsaPkcs1v15Verify4096 => "RSA-PKCS1v1.5-Verify-4096",
Self::RsaPssVerify4096 => "RSA-PSS-Verify-4096",
Self::EcdsaP256Sign => "ECDSA-P256 sign",
Self::EcdsaP256Verify => "ECDSA-P256 verify",
Self::EcdsaP256Keygen => "ECDSA-P256 keygen",
Self::EcdsaP384Sign => "ECDSA-P384 sign",
Self::EcdsaP384Verify => "ECDSA-P384 verify",
Self::EcdsaP384Keygen => "ECDSA-P384 keygen",
Self::EcdhP256 => "ECDH-P256",
Self::EcdhP384 => "ECDH-P384",
Self::Ed25519Sign => "Ed25519 sign",
Self::Ed25519Verify => "Ed25519 verify",
Self::Ed25519Keygen => "Ed25519 keygen",
Self::Tls12Kdf => "TLS 1.2 KDF",
Self::Tls13Kdf => "TLS 1.3 KDF",
Self::MlKem1024Encaps => "ML-KEM-1024 encaps",
Self::MlKem1024Decaps => "ML-KEM-1024 decaps",
Self::MlKem1024Keygen => "ML-KEM-1024 keygen",
Self::MlKem512Encaps => "ML-KEM-512 encaps",
Self::MlKem512Decaps => "ML-KEM-512 decaps",
Self::MlKem512Keygen => "ML-KEM-512 keygen",
Self::MlKem768Encaps => "ML-KEM-768 encaps",
Self::MlKem768Decaps => "ML-KEM-768 decaps",
Self::MlKem768Keygen => "ML-KEM-768 keygen",
Self::MlDsa87Sign => "ML-DSA-87 sign",
Self::MlDsa87Verify => "ML-DSA-87 verify",
Self::MlDsa87Keygen => "ML-DSA-87 keygen",
Self::MlDsa44Sign => "ML-DSA-44 sign",
Self::MlDsa44Verify => "ML-DSA-44 verify",
Self::MlDsa44Keygen => "ML-DSA-44 keygen",
Self::MlDsa65Sign => "ML-DSA-65 sign",
Self::MlDsa65Verify => "ML-DSA-65 verify",
Self::MlDsa65Keygen => "ML-DSA-65 keygen",
Self::SlhDsaSha2256sKeygen => "SLH-DSA-SHA2-256s keygen",
Self::SlhDsaSha2256sSign => "SLH-DSA-SHA2-256s sign",
Self::SlhDsaSha2256sVerify => "SLH-DSA-SHA2-256s verify",
Self::SlhDsaSha2128sKeygen => "SLH-DSA-SHA2-128s keygen",
Self::SlhDsaSha2128sSign => "SLH-DSA-SHA2-128s sign",
Self::SlhDsaSha2128sVerify => "SLH-DSA-SHA2-128s verify",
Self::SlhDsaSha2128fKeygen => "SLH-DSA-SHA2-128f keygen",
Self::SlhDsaSha2128fSign => "SLH-DSA-SHA2-128f sign",
Self::SlhDsaSha2128fVerify => "SLH-DSA-SHA2-128f verify",
Self::SlhDsaSha2192sKeygen => "SLH-DSA-SHA2-192s keygen",
Self::SlhDsaSha2192sSign => "SLH-DSA-SHA2-192s sign",
Self::SlhDsaSha2192sVerify => "SLH-DSA-SHA2-192s verify",
Self::SlhDsaSha2192fKeygen => "SLH-DSA-SHA2-192f keygen",
Self::SlhDsaSha2192fSign => "SLH-DSA-SHA2-192f sign",
Self::SlhDsaSha2192fVerify => "SLH-DSA-SHA2-192f verify",
Self::SlhDsaSha2256fKeygen => "SLH-DSA-SHA2-256f keygen",
Self::SlhDsaSha2256fSign => "SLH-DSA-SHA2-256f sign",
Self::SlhDsaSha2256fVerify => "SLH-DSA-SHA2-256f verify",
Self::SlhDsaShake128sKeygen => "SLH-DSA-SHAKE-128s keygen",
Self::SlhDsaShake128sSign => "SLH-DSA-SHAKE-128s sign",
Self::SlhDsaShake128sVerify => "SLH-DSA-SHAKE-128s verify",
Self::SlhDsaShake128fKeygen => "SLH-DSA-SHAKE-128f keygen",
Self::SlhDsaShake128fSign => "SLH-DSA-SHAKE-128f sign",
Self::SlhDsaShake128fVerify => "SLH-DSA-SHAKE-128f verify",
Self::SlhDsaShake192sKeygen => "SLH-DSA-SHAKE-192s keygen",
Self::SlhDsaShake192sSign => "SLH-DSA-SHAKE-192s sign",
Self::SlhDsaShake192sVerify => "SLH-DSA-SHAKE-192s verify",
Self::SlhDsaShake192fKeygen => "SLH-DSA-SHAKE-192f keygen",
Self::SlhDsaShake192fSign => "SLH-DSA-SHAKE-192f sign",
Self::SlhDsaShake192fVerify => "SLH-DSA-SHAKE-192f verify",
Self::SlhDsaShake256sKeygen => "SLH-DSA-SHAKE-256s keygen",
Self::SlhDsaShake256sSign => "SLH-DSA-SHAKE-256s sign",
Self::SlhDsaShake256sVerify => "SLH-DSA-SHAKE-256s verify",
Self::SlhDsaShake256fKeygen => "SLH-DSA-SHAKE-256f keygen",
Self::SlhDsaShake256fSign => "SLH-DSA-SHAKE-256f sign",
Self::SlhDsaShake256fVerify => "SLH-DSA-SHAKE-256f verify",
Self::LmsSha256M32H5W1Verify => "LMS SHA-256 M=32 H=5 W=1 verify",
Self::LmsSha256M32H5W2Verify => "LMS SHA-256 M=32 H=5 W=2 verify",
Self::LmsSha256M32H5W4Verify => "LMS SHA-256 M=32 H=5 W=4 verify",
Self::LmsSha256M32H5W8Verify => "LMS SHA-256 M=32 H=5 W=8 verify",
Self::LmsSha256M32H10W1Verify => "LMS SHA-256 M=32 H=10 W=1 verify",
Self::LmsSha256M32H10W2Verify => "LMS SHA-256 M=32 H=10 W=2 verify",
Self::LmsSha256M32H10W4Verify => "LMS SHA-256 M=32 H=10 W=4 verify",
Self::LmsSha256M32H10W8Verify => "LMS SHA-256 M=32 H=10 W=8 verify",
Self::LmsSha256M32H15W1Verify => "LMS SHA-256 M=32 H=15 W=1 verify",
Self::LmsSha256M32H15W2Verify => "LMS SHA-256 M=32 H=15 W=2 verify",
Self::LmsSha256M32H15W4Verify => "LMS SHA-256 M=32 H=15 W=4 verify",
Self::LmsSha256M32H15W8Verify => "LMS SHA-256 M=32 H=15 W=8 verify",
Self::LmsSha256M32H20W1Verify => "LMS SHA-256 M=32 H=20 W=1 verify",
Self::LmsSha256M32H20W2Verify => "LMS SHA-256 M=32 H=20 W=2 verify",
Self::LmsSha256M32H20W4Verify => "LMS SHA-256 M=32 H=20 W=4 verify",
Self::LmsSha256M32H20W8Verify => "LMS SHA-256 M=32 H=20 W=8 verify",
Self::LmsSha256M32H25W1Verify => "LMS SHA-256 M=32 H=25 W=1 verify",
Self::LmsSha256M32H25W2Verify => "LMS SHA-256 M=32 H=25 W=2 verify",
Self::LmsSha256M32H25W4Verify => "LMS SHA-256 M=32 H=25 W=4 verify",
Self::LmsSha256M32H25W8Verify => "LMS SHA-256 M=32 H=25 W=8 verify",
Self::LmsSha256M24H5W1Verify => "LMS SHA-256 M=24 H=5 W=1 verify",
Self::LmsSha256M24H5W2Verify => "LMS SHA-256 M=24 H=5 W=2 verify",
Self::LmsSha256M24H5W4Verify => "LMS SHA-256 M=24 H=5 W=4 verify",
Self::LmsSha256M24H5W8Verify => "LMS SHA-256 M=24 H=5 W=8 verify",
Self::LmsSha256M24H10W1Verify => "LMS SHA-256 M=24 H=10 W=1 verify",
Self::LmsSha256M24H10W2Verify => "LMS SHA-256 M=24 H=10 W=2 verify",
Self::LmsSha256M24H10W4Verify => "LMS SHA-256 M=24 H=10 W=4 verify",
Self::LmsSha256M24H10W8Verify => "LMS SHA-256 M=24 H=10 W=8 verify",
Self::LmsSha256M24H15W1Verify => "LMS SHA-256 M=24 H=15 W=1 verify",
Self::LmsSha256M24H15W2Verify => "LMS SHA-256 M=24 H=15 W=2 verify",
Self::LmsSha256M24H15W4Verify => "LMS SHA-256 M=24 H=15 W=4 verify",
Self::LmsSha256M24H15W8Verify => "LMS SHA-256 M=24 H=15 W=8 verify",
Self::LmsSha256M24H20W1Verify => "LMS SHA-256 M=24 H=20 W=1 verify",
Self::LmsSha256M24H20W2Verify => "LMS SHA-256 M=24 H=20 W=2 verify",
Self::LmsSha256M24H20W4Verify => "LMS SHA-256 M=24 H=20 W=4 verify",
Self::LmsSha256M24H20W8Verify => "LMS SHA-256 M=24 H=20 W=8 verify",
Self::LmsSha256M24H25W1Verify => "LMS SHA-256 M=24 H=25 W=1 verify",
Self::LmsSha256M24H25W2Verify => "LMS SHA-256 M=24 H=25 W=2 verify",
Self::LmsSha256M24H25W4Verify => "LMS SHA-256 M=24 H=25 W=4 verify",
Self::LmsSha256M24H25W8Verify => "LMS SHA-256 M=24 H=25 W=8 verify",
Self::LmsShakeM32H5W1Verify => "LMS SHAKE M=32 H=5 W=1 verify",
Self::LmsShakeM32H5W2Verify => "LMS SHAKE M=32 H=5 W=2 verify",
Self::LmsShakeM32H5W4Verify => "LMS SHAKE M=32 H=5 W=4 verify",
Self::LmsShakeM32H5W8Verify => "LMS SHAKE M=32 H=5 W=8 verify",
Self::LmsShakeM32H10W1Verify => "LMS SHAKE M=32 H=10 W=1 verify",
Self::LmsShakeM32H10W2Verify => "LMS SHAKE M=32 H=10 W=2 verify",
Self::LmsShakeM32H10W4Verify => "LMS SHAKE M=32 H=10 W=4 verify",
Self::LmsShakeM32H10W8Verify => "LMS SHAKE M=32 H=10 W=8 verify",
Self::LmsShakeM32H15W1Verify => "LMS SHAKE M=32 H=15 W=1 verify",
Self::LmsShakeM32H15W2Verify => "LMS SHAKE M=32 H=15 W=2 verify",
Self::LmsShakeM32H15W4Verify => "LMS SHAKE M=32 H=15 W=4 verify",
Self::LmsShakeM32H15W8Verify => "LMS SHAKE M=32 H=15 W=8 verify",
Self::LmsShakeM32H20W1Verify => "LMS SHAKE M=32 H=20 W=1 verify",
Self::LmsShakeM32H20W2Verify => "LMS SHAKE M=32 H=20 W=2 verify",
Self::LmsShakeM32H20W4Verify => "LMS SHAKE M=32 H=20 W=4 verify",
Self::LmsShakeM32H20W8Verify => "LMS SHAKE M=32 H=20 W=8 verify",
Self::LmsShakeM32H25W1Verify => "LMS SHAKE M=32 H=25 W=1 verify",
Self::LmsShakeM32H25W2Verify => "LMS SHAKE M=32 H=25 W=2 verify",
Self::LmsShakeM32H25W4Verify => "LMS SHAKE M=32 H=25 W=4 verify",
Self::LmsShakeM32H25W8Verify => "LMS SHAKE M=32 H=25 W=8 verify",
Self::LmsShakeM24H5W1Verify => "LMS SHAKE M=24 H=5 W=1 verify",
Self::LmsShakeM24H5W2Verify => "LMS SHAKE M=24 H=5 W=2 verify",
Self::LmsShakeM24H5W4Verify => "LMS SHAKE M=24 H=5 W=4 verify",
Self::LmsShakeM24H5W8Verify => "LMS SHAKE M=24 H=5 W=8 verify",
Self::LmsShakeM24H10W1Verify => "LMS SHAKE M=24 H=10 W=1 verify",
Self::LmsShakeM24H10W2Verify => "LMS SHAKE M=24 H=10 W=2 verify",
Self::LmsShakeM24H10W4Verify => "LMS SHAKE M=24 H=10 W=4 verify",
Self::LmsShakeM24H10W8Verify => "LMS SHAKE M=24 H=10 W=8 verify",
Self::LmsShakeM24H15W1Verify => "LMS SHAKE M=24 H=15 W=1 verify",
Self::LmsShakeM24H15W2Verify => "LMS SHAKE M=24 H=15 W=2 verify",
Self::LmsShakeM24H15W4Verify => "LMS SHAKE M=24 H=15 W=4 verify",
Self::LmsShakeM24H15W8Verify => "LMS SHAKE M=24 H=15 W=8 verify",
Self::LmsShakeM24H20W1Verify => "LMS SHAKE M=24 H=20 W=1 verify",
Self::LmsShakeM24H20W2Verify => "LMS SHAKE M=24 H=20 W=2 verify",
Self::LmsShakeM24H20W4Verify => "LMS SHAKE M=24 H=20 W=4 verify",
Self::LmsShakeM24H20W8Verify => "LMS SHAKE M=24 H=20 W=8 verify",
Self::LmsShakeM24H25W1Verify => "LMS SHAKE M=24 H=25 W=1 verify",
Self::LmsShakeM24H25W2Verify => "LMS SHAKE M=24 H=25 W=2 verify",
Self::LmsShakeM24H25W4Verify => "LMS SHAKE M=24 H=25 W=4 verify",
Self::LmsShakeM24H25W8Verify => "LMS SHAKE M=24 H=25 W=8 verify",
Self::XmssVerify => "XMSS verify",
Self::Dh3072 => "DH-3072",
};
f.write_str(name)
}
}
pub fn require_allowed(service: Service) -> Result<(), Error> {
let profile = active_profile();
if is_allowed(profile, service) {
Ok(())
} else {
Err(Error::AlgorithmRestricted { service })
}
}
const fn is_allowed(profile: AlgorithmProfile, service: Service) -> bool {
match profile {
AlgorithmProfile::Unrestricted => true,
AlgorithmProfile::Cnsa2 => is_cnsa2_allowed(service),
AlgorithmProfile::Cnsa1 => is_cnsa1_allowed(service),
AlgorithmProfile::Migration => is_cnsa1_allowed(service) || is_cnsa2_allowed(service),
}
}
const fn is_lms_service(service: Service) -> bool {
lms_block_contains(service as u16)
}
const fn lms_block_contains(discriminant: u16) -> bool {
discriminant >= Service::LmsSha256M32H5W1Verify as u16
&& discriminant <= Service::LmsShakeM24H25W8Verify as u16
&& discriminant & 1 == 1
}
const LMS_SERVICES: [Service; 80] = [
Service::LmsSha256M32H5W1Verify,
Service::LmsSha256M32H5W2Verify,
Service::LmsSha256M32H5W4Verify,
Service::LmsSha256M32H5W8Verify,
Service::LmsSha256M32H10W1Verify,
Service::LmsSha256M32H10W2Verify,
Service::LmsSha256M32H10W4Verify,
Service::LmsSha256M32H10W8Verify,
Service::LmsSha256M32H15W1Verify,
Service::LmsSha256M32H15W2Verify,
Service::LmsSha256M32H15W4Verify,
Service::LmsSha256M32H15W8Verify,
Service::LmsSha256M32H20W1Verify,
Service::LmsSha256M32H20W2Verify,
Service::LmsSha256M32H20W4Verify,
Service::LmsSha256M32H20W8Verify,
Service::LmsSha256M32H25W1Verify,
Service::LmsSha256M32H25W2Verify,
Service::LmsSha256M32H25W4Verify,
Service::LmsSha256M32H25W8Verify,
Service::LmsSha256M24H5W1Verify,
Service::LmsSha256M24H5W2Verify,
Service::LmsSha256M24H5W4Verify,
Service::LmsSha256M24H5W8Verify,
Service::LmsSha256M24H10W1Verify,
Service::LmsSha256M24H10W2Verify,
Service::LmsSha256M24H10W4Verify,
Service::LmsSha256M24H10W8Verify,
Service::LmsSha256M24H15W1Verify,
Service::LmsSha256M24H15W2Verify,
Service::LmsSha256M24H15W4Verify,
Service::LmsSha256M24H15W8Verify,
Service::LmsSha256M24H20W1Verify,
Service::LmsSha256M24H20W2Verify,
Service::LmsSha256M24H20W4Verify,
Service::LmsSha256M24H20W8Verify,
Service::LmsSha256M24H25W1Verify,
Service::LmsSha256M24H25W2Verify,
Service::LmsSha256M24H25W4Verify,
Service::LmsSha256M24H25W8Verify,
Service::LmsShakeM32H5W1Verify,
Service::LmsShakeM32H5W2Verify,
Service::LmsShakeM32H5W4Verify,
Service::LmsShakeM32H5W8Verify,
Service::LmsShakeM32H10W1Verify,
Service::LmsShakeM32H10W2Verify,
Service::LmsShakeM32H10W4Verify,
Service::LmsShakeM32H10W8Verify,
Service::LmsShakeM32H15W1Verify,
Service::LmsShakeM32H15W2Verify,
Service::LmsShakeM32H15W4Verify,
Service::LmsShakeM32H15W8Verify,
Service::LmsShakeM32H20W1Verify,
Service::LmsShakeM32H20W2Verify,
Service::LmsShakeM32H20W4Verify,
Service::LmsShakeM32H20W8Verify,
Service::LmsShakeM32H25W1Verify,
Service::LmsShakeM32H25W2Verify,
Service::LmsShakeM32H25W4Verify,
Service::LmsShakeM32H25W8Verify,
Service::LmsShakeM24H5W1Verify,
Service::LmsShakeM24H5W2Verify,
Service::LmsShakeM24H5W4Verify,
Service::LmsShakeM24H5W8Verify,
Service::LmsShakeM24H10W1Verify,
Service::LmsShakeM24H10W2Verify,
Service::LmsShakeM24H10W4Verify,
Service::LmsShakeM24H10W8Verify,
Service::LmsShakeM24H15W1Verify,
Service::LmsShakeM24H15W2Verify,
Service::LmsShakeM24H15W4Verify,
Service::LmsShakeM24H15W8Verify,
Service::LmsShakeM24H20W1Verify,
Service::LmsShakeM24H20W2Verify,
Service::LmsShakeM24H20W4Verify,
Service::LmsShakeM24H20W8Verify,
Service::LmsShakeM24H25W1Verify,
Service::LmsShakeM24H25W2Verify,
Service::LmsShakeM24H25W4Verify,
Service::LmsShakeM24H25W8Verify,
];
#[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
const _: () = {
let mut i = 0;
let mut slot: u16 = 501;
while i < LMS_SERVICES.len() {
let d = LMS_SERVICES[i] as u16;
assert!(d == slot, "LMS service out of its slot");
assert!(lms_block_contains(d), "LMS service outside the LMS block");
slot += 2;
i += 1;
}
assert!(
LMS_SERVICES[79] as u16 == 659,
"LMS block does not end at 659"
);
};
const fn is_cnsa2_allowed(service: Service) -> bool {
matches!(
service,
Service::Aes256Ecb
| Service::Aes256Cbc
| Service::Aes256Ctr
| Service::Aes256Gcm
| Service::Aes256Ccm
| Service::Aes256Kw
| Service::Aes256Kwp
| Service::Aes256
| Service::Sha384
| Service::Sha512
| Service::HmacSha384
| Service::HmacSha512
| Service::CmacAes256
| Service::CtrDrbgAes256
| Service::HashDrbgSha384
| Service::HashDrbgSha512
| Service::HmacDrbgSha384
| Service::HmacDrbgSha512
| Service::HkdfSha384
| Service::HkdfSha512
| Service::KbkdfHmacSha384
| Service::KbkdfHmacSha512
| Service::KbkdfCmacAes256
| Service::MlKem1024Encaps
| Service::MlKem1024Decaps
| Service::MlKem1024Keygen
| Service::MlDsa87Sign
| Service::MlDsa87Verify
| Service::MlDsa87Keygen
| Service::XmssVerify
)
|| is_lms_service(service)
}
const fn is_cnsa1_allowed(service: Service) -> bool {
matches!(
service,
Service::Aes256
| Service::Aes256Ecb
| Service::Aes256Cbc
| Service::Aes256Ctr
| Service::Aes256Gcm
| Service::Aes256Ccm
| Service::Aes256Kw
| Service::Aes256Kwp
| Service::EcdhP384
| Service::EcdsaP384Keygen
| Service::EcdsaP384Sign
| Service::EcdsaP384Verify
| Service::Sha384
| Service::Dh3072
| Service::RsaKeygen3072
| Service::RsaKeygen4096
| Service::RsaOaep3072
| Service::RsaOaep4096
| Service::RsaPkcs1v15Sign3072
| Service::RsaPkcs1v15Sign4096
| Service::RsaPkcs1v15Verify3072
| Service::RsaPkcs1v15Verify4096
| Service::RsaPssSign3072
| Service::RsaPssSign4096
| Service::RsaPssVerify3072
| Service::RsaPssVerify4096
| Service::CtrDrbgAes256
| Service::HashDrbgSha384
| Service::HmacDrbgSha384
| Service::HmacSha384
| Service::CmacAes256
| Service::HkdfSha384
| Service::KbkdfHmacSha384
| Service::KbkdfCmacAes256
)
}
#[cfg(test)]
pub(crate) mod state_lock {
use super::{AlgorithmProfile, Ordering, PROFILE, STATE, State};
static STATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[must_use]
pub(crate) struct StateGuard(
#[allow(dead_code)]
std::sync::MutexGuard<'static, ()>,
);
impl StateGuard {
#[allow(clippy::unused_self)]
pub(crate) fn reset(&self) {
STATE.store(State::PowerOff as u64, Ordering::Release);
PROFILE.store(AlgorithmProfile::Unrestricted as u8, Ordering::Release);
}
}
pub(crate) fn reset_for_test() -> StateGuard {
let guard = StateGuard(
STATE_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
guard.reset();
guard
}
}
#[cfg(test)]
extern crate alloc;
#[cfg(test)]
extern crate std;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::state_lock::reset_for_test;
use super::{
AlgorithmProfile, Error, KatEntry, LMS_SERVICES, SelfTest, SelfTestFailure, Service, State,
active_profile, enter_error_state, initialize_with_profile, initialize_with_tests,
is_allowed, is_lms_service, is_operational, lms_block_contains, require_allowed,
require_operational, require_self_test_started, state,
};
use alloc::string::ToString;
use core::sync::atomic::Ordering;
const UNSIGNED_TEST_BINARY: &[KatEntry] = &[KatEntry {
name: "integrity not verifiable in an unsigned test binary",
run: || Ok(()),
}];
#[allow(clippy::unnecessary_wraps)]
fn always_pass() -> Result<(), SelfTestFailure> {
Ok(())
}
fn always_fail() -> Result<(), SelfTestFailure> {
Err(SelfTestFailure)
}
static UNREACHED_RUNS: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
#[allow(clippy::unnecessary_wraps)]
fn count_unreached() -> Result<(), SelfTestFailure> {
UNREACHED_RUNS.fetch_add(1, Ordering::AcqRel);
Ok(())
}
#[test]
fn initial_state_is_power_off() {
let _guard = reset_for_test();
assert_eq!(state(), State::PowerOff);
assert!(!is_operational());
}
#[test]
fn initialize_transitions_to_operational() {
let _guard = reset_for_test();
initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
assert_eq!(state(), State::Operational);
assert!(is_operational());
require_operational().unwrap();
}
#[test]
fn second_initialize_reports_already_initialized() {
let _guard = reset_for_test();
initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
match initialize_with_tests(UNSIGNED_TEST_BINARY, &[]) {
Err(Error::AlreadyInitialized) => {}
other => panic!("expected AlreadyInitialized, got {other:?}"),
}
}
#[test]
fn require_operational_rejects_power_off() {
let _guard = reset_for_test();
match require_operational() {
Err(Error::NotOperational {
current: State::PowerOff,
}) => {}
other => panic!("expected NotOperational{{PowerOff}}, got {other:?}"),
}
}
#[test]
fn enter_error_state_is_terminal_for_require_operational() {
let _guard = reset_for_test();
initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
assert!(is_operational());
enter_error_state("unit-test forced failure");
assert_eq!(state(), State::Error);
match require_operational() {
Err(Error::NotOperational {
current: State::Error,
}) => {}
other => panic!("expected NotOperational{{Error}}, got {other:?}"),
}
}
fn guard_admits_self_test() -> Result<(), SelfTestFailure> {
if state() != State::SelfTest {
return Err(SelfTestFailure);
}
require_self_test_started().map_err(|_| SelfTestFailure)
}
#[test]
fn require_self_test_started_rejects_power_off() {
let _guard = reset_for_test();
assert_eq!(
require_self_test_started(),
Err(Error::NotOperational {
current: State::PowerOff
})
);
}
#[test]
fn require_self_test_started_rejects_error_after_failed_init() {
let _guard = reset_for_test();
let tests = [KatEntry {
name: "dummy-fail",
run: always_fail,
}];
assert!(initialize_with_tests(UNSIGNED_TEST_BINARY, &tests).is_err());
assert_eq!(state(), State::Error);
assert_eq!(
require_self_test_started(),
Err(Error::NotOperational {
current: State::Error
})
);
}
#[test]
fn require_self_test_started_rejects_integrity_unverified() {
let _guard = reset_for_test();
assert_eq!(
initialize_with_tests(&[], &[]),
Err(Error::IntegrityNotAttested)
);
assert_eq!(state(), State::IntegrityUnverified);
assert_eq!(
require_self_test_started(),
Err(Error::NotOperational {
current: State::IntegrityUnverified
})
);
}
#[test]
fn initialize_after_integrity_unverified_is_already_failed() {
let _guard = reset_for_test();
assert_eq!(
initialize_with_tests(&[], &[]),
Err(Error::IntegrityNotAttested)
);
assert_eq!(state(), State::IntegrityUnverified);
let counting = [KatEntry {
name: "counts its runs",
run: count_unreached,
}];
UNREACHED_RUNS.store(0, Ordering::Release);
assert_eq!(
initialize_with_tests(UNSIGNED_TEST_BINARY, &counting),
Err(Error::AlreadyFailed)
);
assert_eq!(UNREACHED_RUNS.load(Ordering::Acquire), 0);
assert_eq!(state(), State::IntegrityUnverified);
}
#[test]
fn require_self_test_started_admits_self_test_and_operational() {
let _guard = reset_for_test();
let tests = [KatEntry {
name: "guard admits SelfTest",
run: guard_admits_self_test,
}];
initialize_with_tests(UNSIGNED_TEST_BINARY, &tests).unwrap();
assert_eq!(state(), State::Operational);
assert_eq!(require_self_test_started(), Ok(()));
}
#[test]
fn a_malformed_state_word_reads_as_error() {
let guard = reset_for_test();
let owner = 0x1234_u64 << super::STATE_BITS;
let malformed = [
State::SelfTest as u64,
State::Operational as u64 | owner,
State::PowerOff as u64 | owner,
State::IntegrityUnverified as u64 | owner,
5,
0xff,
0xff | owner,
];
assert_eq!(malformed.len(), 7);
for word in malformed {
guard.reset();
super::STATE.store(word, Ordering::Release);
assert_eq!(state(), State::Error, "word {word:#x}");
assert!(!is_operational(), "word {word:#x}");
assert_eq!(
require_operational(),
Err(Error::NotOperational {
current: State::Error
}),
"word {word:#x}"
);
assert_eq!(
require_self_test_started(),
Err(Error::NotOperational {
current: State::Error
}),
"word {word:#x}"
);
}
guard.reset();
assert_eq!(state(), State::PowerOff);
super::STATE.store(State::Operational as u64, Ordering::Release);
assert_eq!(state(), State::Operational);
}
static RELEASE_FAILING_WINNER: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
static FAILING_WINNER_PAUSED: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
fn wait_until(what: &str, done: impl Fn() -> bool) {
let start = std::time::Instant::now();
while !done() {
assert!(
start.elapsed() < std::time::Duration::from_secs(30),
"timed out waiting for {what}"
);
std::thread::yield_now();
}
}
fn paused_then_fails() -> Result<(), SelfTestFailure> {
FAILING_WINNER_PAUSED.store(true, Ordering::Release);
wait_until("the release of the failing winner", || {
RELEASE_FAILING_WINNER.load(Ordering::Acquire)
});
Err(SelfTestFailure)
}
#[test]
fn loser_waits_then_gets_already_failed() {
let _guard = reset_for_test();
FAILING_WINNER_PAUSED.store(false, Ordering::Release);
RELEASE_FAILING_WINNER.store(false, Ordering::Release);
let waiting_before = super::WAITING_LOSERS.load(Ordering::Acquire);
let winner = std::thread::spawn(|| {
let tests = [KatEntry {
name: "pauses, then fails",
run: paused_then_fails,
}];
initialize_with_tests(UNSIGNED_TEST_BINARY, &tests)
});
wait_until("the winner to pause", || {
FAILING_WINNER_PAUSED.load(Ordering::Acquire)
});
let loser = std::thread::spawn(|| initialize_with_tests(UNSIGNED_TEST_BINARY, &[]));
wait_until("the loser to enter its wait", || {
super::WAITING_LOSERS.load(Ordering::Acquire) > waiting_before
});
assert!(!loser.is_finished());
RELEASE_FAILING_WINNER.store(true, Ordering::Release);
assert_eq!(
winner.join().unwrap(),
Err(Error::SelfTestFailed {
test: "pauses, then fails"
})
);
assert_eq!(loser.join().unwrap(), Err(Error::AlreadyFailed));
assert_eq!(state(), State::Error);
}
#[test]
fn the_owner_id_filter_admits_only_recordable_ids() {
assert_eq!(super::owner_id(None), None);
assert_eq!(super::owner_id(Some(0)), None);
assert_eq!(super::owner_id(Some(1)), Some(1));
assert_eq!(super::owner_id(Some((1 << 56) - 1)), Some((1 << 56) - 1));
assert_eq!(super::owner_id(Some(1 << 56)), None);
assert_eq!(super::owner_id(Some(u64::MAX)), None);
}
#[test]
fn no_recordable_thread_id_latches_error_with_its_cause() {
let guard = reset_for_test();
let tests = [KatEntry {
name: "must not run",
run: always_fail,
}];
for raw in [None, Some(0), Some(1 << 56)] {
guard.reset();
assert_eq!(
super::initialize_as(
super::owner_id(raw),
UNSIGNED_TEST_BINARY,
&tests,
AlgorithmProfile::Unrestricted
),
Err(Error::SelfTestFailed {
test: super::THREAD_ID_CHECK
}),
"raw id {raw:?}"
);
assert_eq!(state(), State::Error);
assert_eq!(
initialize_with_tests(UNSIGNED_TEST_BINARY, &[]),
Err(Error::AlreadyFailed)
);
}
}
#[test]
fn state_display_is_stable() {
assert_eq!(State::PowerOff.to_string(), "PowerOff");
assert_eq!(State::SelfTest.to_string(), "SelfTest");
assert_eq!(State::Operational.to_string(), "Operational");
assert_eq!(State::Error.to_string(), "Error");
}
#[test]
fn registry_runs_passing_tests_and_reaches_operational() {
let _guard = reset_for_test();
let tests = [
KatEntry {
name: "dummy-pass-a",
run: always_pass,
},
KatEntry {
name: "dummy-pass-b",
run: always_pass,
},
];
initialize_with_tests(UNSIGNED_TEST_BINARY, &tests).unwrap();
assert_eq!(state(), State::Operational);
}
#[test]
fn registry_failing_test_latches_error_and_returns_name() {
let _guard = reset_for_test();
let tests = [
KatEntry {
name: "dummy-pass",
run: always_pass,
},
KatEntry {
name: "dummy-fail",
run: always_fail,
},
KatEntry {
name: "dummy-unreached",
run: count_unreached,
},
];
UNREACHED_RUNS.store(0, Ordering::Release);
match initialize_with_tests(UNSIGNED_TEST_BINARY, &tests) {
Err(Error::SelfTestFailed { test: "dummy-fail" }) => {}
other => panic!("expected SelfTestFailed{{dummy-fail}}, got {other:?}"),
}
assert_eq!(UNREACHED_RUNS.load(Ordering::Acquire), 0);
assert_eq!(state(), State::Error);
assert!(matches!(
require_operational(),
Err(Error::NotOperational {
current: State::Error
})
));
}
struct DummyTest;
impl SelfTest for DummyTest {
const NAME: &'static str = "dummy-trait-vector";
fn run() -> Result<(), SelfTestFailure> {
Ok(())
}
}
#[test]
fn dummy_self_test_trait_is_usable() {
assert_eq!(DummyTest::NAME, "dummy-trait-vector");
DummyTest::run().unwrap();
}
#[test]
fn default_profile_is_unrestricted() {
let _guard = reset_for_test();
assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
}
#[test]
fn initialize_with_profile_sets_cnsa2() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
assert!(is_operational());
}
#[test]
fn a_second_call_does_not_change_the_profile() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
match initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Unrestricted) {
Err(Error::AlreadyInitialized) => {}
other => panic!("expected AlreadyInitialized, got {other:?}"),
}
assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
match initialize_with_tests(UNSIGNED_TEST_BINARY, &[]) {
Err(Error::AlreadyInitialized) => {}
other => panic!("expected AlreadyInitialized, got {other:?}"),
}
assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
}
static RACER_CALLED: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
#[allow(clippy::unnecessary_wraps)]
fn wait_for_racer() -> Result<(), SelfTestFailure> {
while !RACER_CALLED.load(Ordering::Acquire) {
std::thread::yield_now();
}
std::thread::sleep(std::time::Duration::from_millis(50));
Ok(())
}
#[test]
fn a_racing_call_does_not_change_the_profile() {
let _guard = reset_for_test();
RACER_CALLED.store(false, Ordering::Release);
let tests = [KatEntry {
name: "waits for the racing call",
run: wait_for_racer,
}];
let winner = std::thread::spawn(move || {
initialize_with_profile(UNSIGNED_TEST_BINARY, &tests, AlgorithmProfile::Cnsa2)
});
while state() != State::SelfTest {
std::thread::yield_now();
}
RACER_CALLED.store(true, Ordering::Release);
let racer =
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Unrestricted);
let winner = winner.join().unwrap();
assert!(winner.is_ok(), "the first call failed: {winner:?}");
match racer {
Err(Error::AlreadyInitialized) => {}
other => panic!("expected AlreadyInitialized, got {other:?}"),
}
assert_eq!(active_profile(), AlgorithmProfile::Cnsa2);
}
#[test]
fn a_failed_initialisation_leaves_the_default_profile() {
let guard = reset_for_test();
let failing = [KatEntry {
name: "always fails",
run: always_fail,
}];
match initialize_with_profile(UNSIGNED_TEST_BINARY, &failing, AlgorithmProfile::Cnsa2) {
Err(Error::SelfTestFailed { .. }) => {}
other => panic!("expected SelfTestFailed, got {other:?}"),
}
assert_eq!(state(), State::Error);
assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
guard.reset();
match initialize_with_profile(&[], &[], AlgorithmProfile::Cnsa1) {
Err(Error::IntegrityNotAttested) => {}
other => panic!("expected IntegrityNotAttested, got {other:?}"),
}
assert_eq!(active_profile(), AlgorithmProfile::Unrestricted);
}
#[test]
fn initialize_with_profile_sets_cnsa1() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa1).unwrap();
assert_eq!(active_profile(), AlgorithmProfile::Cnsa1);
assert!(is_operational());
}
#[test]
fn unrestricted_allows_all_services() {
let spot = [
Service::Sha1,
Service::Sha256,
Service::Aes128Ecb,
Service::Aes256Gcm,
Service::CtrDrbgAes128,
Service::EcdsaP256Sign,
Service::Ed25519Sign,
Service::RsaPssSign2048,
Service::MlKem1024Encaps,
Service::LmsSha256M32H10W4Verify,
Service::Dh3072,
Service::SlhDsaSha2256sSign,
];
for svc in spot {
assert!(
is_allowed(AlgorithmProfile::Unrestricted, svc),
"{svc} should be allowed in Unrestricted"
);
}
}
#[test]
fn cnsa2_allows_aes256_and_pq() {
let allowed = [
Service::Aes256Gcm,
Service::Aes256Cbc,
Service::Sha384,
Service::Sha512,
Service::HmacSha384,
Service::HmacSha512,
Service::CtrDrbgAes256,
Service::HashDrbgSha384,
Service::HmacDrbgSha512,
Service::MlKem1024Encaps,
Service::MlKem1024Decaps,
Service::MlKem1024Keygen,
Service::MlDsa87Sign,
Service::MlDsa87Verify,
Service::MlDsa87Keygen,
Service::LmsSha256M32H10W4Verify,
Service::XmssVerify,
];
for svc in allowed {
assert!(
is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} should be allowed in CNSA 2.0"
);
}
}
#[test]
fn cnsa2_refuses_the_keccak_family() {
let refused = [
Service::Shake256,
Service::CShake256,
Service::Kmac256,
Service::KmacXof256,
Service::TupleHash256,
Service::TupleHashXof256,
Service::ParallelHash256,
Service::ParallelHashXof256,
Service::Sha3_384,
Service::Sha3_512,
Service::HmacSha3_384,
Service::HmacSha3_512,
];
for svc in refused {
assert!(
!is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} is not in CNSA 2.0 and must be refused"
);
}
assert!(is_allowed(AlgorithmProfile::Cnsa2, Service::Sha384));
assert!(is_allowed(
AlgorithmProfile::Cnsa2,
Service::MlKem1024Encaps
));
}
#[test]
fn cnsa2_blocks_classical_and_small_keys() {
let blocked = [
Service::Sha1,
Service::Sha224,
Service::Sha256,
Service::Sha512_224,
Service::Sha512_256,
Service::Sha3_224,
Service::Sha3_256,
Service::Shake128,
Service::Kmac128,
Service::Aes128Ecb,
Service::Aes128Gcm,
Service::Aes192Cbc,
Service::CtrDrbgAes128,
Service::CtrDrbgAes192,
Service::HashDrbgSha256,
Service::HmacDrbgSha256,
Service::HmacSha1,
Service::HmacSha256,
Service::CmacAes128,
Service::CmacAes192,
Service::EcdsaP256Sign,
Service::EcdsaP256Verify,
Service::EcdhP256,
Service::Ed25519Sign,
Service::Ed25519Verify,
Service::RsaPssSign2048,
Service::RsaOaep2048,
Service::SlhDsaSha2128sSign,
Service::MlDsa44Sign,
Service::MlDsa44Verify,
Service::MlDsa44Keygen,
Service::MlDsa65Sign,
Service::MlDsa65Verify,
Service::MlDsa65Keygen,
];
for svc in blocked {
assert!(
!is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} should be BLOCKED in CNSA 2.0"
);
}
}
const ALL_SERVICES: [Service; 256] = [
Service::Sha1,
Service::Sha224,
Service::Sha256,
Service::Sha384,
Service::Sha512,
Service::Sha512_224,
Service::Sha512_256,
Service::Sha3_224,
Service::Sha3_256,
Service::Sha3_384,
Service::Sha3_512,
Service::Shake128,
Service::Shake256,
Service::CShake128,
Service::CShake256,
Service::Kmac128,
Service::Kmac256,
Service::KmacXof128,
Service::KmacXof256,
Service::TupleHash128,
Service::TupleHash256,
Service::TupleHashXof128,
Service::TupleHashXof256,
Service::ParallelHash128,
Service::ParallelHash256,
Service::ParallelHashXof128,
Service::ParallelHashXof256,
Service::HmacSha1,
Service::HmacSha224,
Service::HmacSha256,
Service::HmacSha384,
Service::HmacSha512,
Service::HmacSha512_224,
Service::HmacSha512_256,
Service::HmacSha3_224,
Service::HmacSha3_256,
Service::HmacSha3_384,
Service::HmacSha3_512,
Service::CmacAes128,
Service::CmacAes192,
Service::CmacAes256,
Service::Aes128Ecb,
Service::Aes128Cbc,
Service::Aes128Ctr,
Service::Aes128Gcm,
Service::Aes128Ccm,
Service::Aes128Kw,
Service::Aes128Kwp,
Service::Aes192Ecb,
Service::Aes192Cbc,
Service::Aes192Ctr,
Service::Aes192Gcm,
Service::Aes192Ccm,
Service::Aes192Kw,
Service::Aes192Kwp,
Service::Aes256Ecb,
Service::Aes256Cbc,
Service::Aes256Ctr,
Service::Aes256Gcm,
Service::Aes256Ccm,
Service::Aes256Kw,
Service::Aes256Kwp,
Service::Aes128,
Service::Aes192,
Service::Aes256,
Service::CtrDrbgAes128,
Service::CtrDrbgAes192,
Service::CtrDrbgAes256,
Service::HashDrbgSha256,
Service::HashDrbgSha384,
Service::HashDrbgSha512,
Service::HmacDrbgSha256,
Service::HmacDrbgSha384,
Service::HmacDrbgSha512,
Service::HkdfSha1,
Service::HkdfSha256,
Service::HkdfSha384,
Service::HkdfSha512,
Service::KbkdfHmacSha256,
Service::KbkdfHmacSha384,
Service::KbkdfHmacSha512,
Service::KbkdfCmacAes128,
Service::KbkdfCmacAes192,
Service::KbkdfCmacAes256,
Service::Pbkdf2HmacSha1,
Service::Pbkdf2HmacSha224,
Service::Pbkdf2HmacSha256,
Service::Pbkdf2HmacSha384,
Service::Pbkdf2HmacSha512,
Service::RsaKeygen2048,
Service::RsaPkcs1v15Sign2048,
Service::RsaPssSign2048,
Service::RsaOaep2048,
Service::RsaPkcs1v15Verify2048,
Service::RsaPssVerify2048,
Service::RsaKeygen3072,
Service::RsaPkcs1v15Sign3072,
Service::RsaPssSign3072,
Service::RsaOaep3072,
Service::RsaPkcs1v15Verify3072,
Service::RsaPssVerify3072,
Service::RsaKeygen4096,
Service::RsaPkcs1v15Sign4096,
Service::RsaPssSign4096,
Service::RsaOaep4096,
Service::RsaPkcs1v15Verify4096,
Service::RsaPssVerify4096,
Service::EcdsaP256Sign,
Service::EcdsaP256Verify,
Service::EcdsaP256Keygen,
Service::EcdsaP384Sign,
Service::EcdsaP384Verify,
Service::EcdsaP384Keygen,
Service::EcdhP256,
Service::EcdhP384,
Service::Ed25519Sign,
Service::Ed25519Verify,
Service::Ed25519Keygen,
Service::Tls12Kdf,
Service::Tls13Kdf,
Service::MlKem1024Encaps,
Service::MlKem1024Decaps,
Service::MlKem1024Keygen,
Service::MlKem512Encaps,
Service::MlKem512Decaps,
Service::MlKem512Keygen,
Service::MlKem768Encaps,
Service::MlKem768Decaps,
Service::MlKem768Keygen,
Service::MlDsa87Sign,
Service::MlDsa87Verify,
Service::MlDsa87Keygen,
Service::MlDsa44Sign,
Service::MlDsa44Verify,
Service::MlDsa44Keygen,
Service::MlDsa65Sign,
Service::MlDsa65Verify,
Service::MlDsa65Keygen,
Service::SlhDsaSha2256sKeygen,
Service::SlhDsaSha2256sSign,
Service::SlhDsaSha2256sVerify,
Service::SlhDsaSha2128sKeygen,
Service::SlhDsaSha2128sSign,
Service::SlhDsaSha2128sVerify,
Service::SlhDsaSha2128fKeygen,
Service::SlhDsaSha2128fSign,
Service::SlhDsaSha2128fVerify,
Service::SlhDsaSha2192sKeygen,
Service::SlhDsaSha2192sSign,
Service::SlhDsaSha2192sVerify,
Service::SlhDsaSha2192fKeygen,
Service::SlhDsaSha2192fSign,
Service::SlhDsaSha2192fVerify,
Service::SlhDsaSha2256fKeygen,
Service::SlhDsaSha2256fSign,
Service::SlhDsaSha2256fVerify,
Service::SlhDsaShake128sKeygen,
Service::SlhDsaShake128sSign,
Service::SlhDsaShake128sVerify,
Service::SlhDsaShake128fKeygen,
Service::SlhDsaShake128fSign,
Service::SlhDsaShake128fVerify,
Service::SlhDsaShake192sKeygen,
Service::SlhDsaShake192sSign,
Service::SlhDsaShake192sVerify,
Service::SlhDsaShake192fKeygen,
Service::SlhDsaShake192fSign,
Service::SlhDsaShake192fVerify,
Service::SlhDsaShake256sKeygen,
Service::SlhDsaShake256sSign,
Service::SlhDsaShake256sVerify,
Service::SlhDsaShake256fKeygen,
Service::SlhDsaShake256fSign,
Service::SlhDsaShake256fVerify,
Service::LmsSha256M32H5W1Verify,
Service::LmsSha256M32H5W2Verify,
Service::LmsSha256M32H5W4Verify,
Service::LmsSha256M32H5W8Verify,
Service::LmsSha256M32H10W1Verify,
Service::LmsSha256M32H10W2Verify,
Service::LmsSha256M32H10W4Verify,
Service::LmsSha256M32H10W8Verify,
Service::LmsSha256M32H15W1Verify,
Service::LmsSha256M32H15W2Verify,
Service::LmsSha256M32H15W4Verify,
Service::LmsSha256M32H15W8Verify,
Service::LmsSha256M32H20W1Verify,
Service::LmsSha256M32H20W2Verify,
Service::LmsSha256M32H20W4Verify,
Service::LmsSha256M32H20W8Verify,
Service::LmsSha256M32H25W1Verify,
Service::LmsSha256M32H25W2Verify,
Service::LmsSha256M32H25W4Verify,
Service::LmsSha256M32H25W8Verify,
Service::LmsSha256M24H5W1Verify,
Service::LmsSha256M24H5W2Verify,
Service::LmsSha256M24H5W4Verify,
Service::LmsSha256M24H5W8Verify,
Service::LmsSha256M24H10W1Verify,
Service::LmsSha256M24H10W2Verify,
Service::LmsSha256M24H10W4Verify,
Service::LmsSha256M24H10W8Verify,
Service::LmsSha256M24H15W1Verify,
Service::LmsSha256M24H15W2Verify,
Service::LmsSha256M24H15W4Verify,
Service::LmsSha256M24H15W8Verify,
Service::LmsSha256M24H20W1Verify,
Service::LmsSha256M24H20W2Verify,
Service::LmsSha256M24H20W4Verify,
Service::LmsSha256M24H20W8Verify,
Service::LmsSha256M24H25W1Verify,
Service::LmsSha256M24H25W2Verify,
Service::LmsSha256M24H25W4Verify,
Service::LmsSha256M24H25W8Verify,
Service::LmsShakeM32H5W1Verify,
Service::LmsShakeM32H5W2Verify,
Service::LmsShakeM32H5W4Verify,
Service::LmsShakeM32H5W8Verify,
Service::LmsShakeM32H10W1Verify,
Service::LmsShakeM32H10W2Verify,
Service::LmsShakeM32H10W4Verify,
Service::LmsShakeM32H10W8Verify,
Service::LmsShakeM32H15W1Verify,
Service::LmsShakeM32H15W2Verify,
Service::LmsShakeM32H15W4Verify,
Service::LmsShakeM32H15W8Verify,
Service::LmsShakeM32H20W1Verify,
Service::LmsShakeM32H20W2Verify,
Service::LmsShakeM32H20W4Verify,
Service::LmsShakeM32H20W8Verify,
Service::LmsShakeM32H25W1Verify,
Service::LmsShakeM32H25W2Verify,
Service::LmsShakeM32H25W4Verify,
Service::LmsShakeM32H25W8Verify,
Service::LmsShakeM24H5W1Verify,
Service::LmsShakeM24H5W2Verify,
Service::LmsShakeM24H5W4Verify,
Service::LmsShakeM24H5W8Verify,
Service::LmsShakeM24H10W1Verify,
Service::LmsShakeM24H10W2Verify,
Service::LmsShakeM24H10W4Verify,
Service::LmsShakeM24H10W8Verify,
Service::LmsShakeM24H15W1Verify,
Service::LmsShakeM24H15W2Verify,
Service::LmsShakeM24H15W4Verify,
Service::LmsShakeM24H15W8Verify,
Service::LmsShakeM24H20W1Verify,
Service::LmsShakeM24H20W2Verify,
Service::LmsShakeM24H20W4Verify,
Service::LmsShakeM24H20W8Verify,
Service::LmsShakeM24H25W1Verify,
Service::LmsShakeM24H25W2Verify,
Service::LmsShakeM24H25W4Verify,
Service::LmsShakeM24H25W8Verify,
Service::XmssVerify,
Service::Dh3072,
];
const EXPECTED_CNSA1: [Service; 34] = [
Service::Sha384,
Service::HmacSha384,
Service::CmacAes256,
Service::Aes256Ecb,
Service::Aes256Cbc,
Service::Aes256Ctr,
Service::Aes256Gcm,
Service::Aes256Ccm,
Service::Aes256Kw,
Service::Aes256Kwp,
Service::Aes256,
Service::CtrDrbgAes256,
Service::HashDrbgSha384,
Service::HmacDrbgSha384,
Service::HkdfSha384,
Service::KbkdfHmacSha384,
Service::KbkdfCmacAes256,
Service::RsaKeygen3072,
Service::RsaPkcs1v15Sign3072,
Service::RsaPssSign3072,
Service::RsaOaep3072,
Service::RsaPkcs1v15Verify3072,
Service::RsaPssVerify3072,
Service::RsaKeygen4096,
Service::RsaPkcs1v15Sign4096,
Service::RsaPssSign4096,
Service::RsaOaep4096,
Service::RsaPkcs1v15Verify4096,
Service::RsaPssVerify4096,
Service::EcdsaP384Sign,
Service::EcdsaP384Verify,
Service::EcdsaP384Keygen,
Service::EcdhP384,
Service::Dh3072,
];
const EXPECTED_CNSA2_NON_LMS: [Service; 30] = [
Service::Sha384,
Service::Sha512,
Service::HmacSha384,
Service::HmacSha512,
Service::CmacAes256,
Service::Aes256Ecb,
Service::Aes256Cbc,
Service::Aes256Ctr,
Service::Aes256Gcm,
Service::Aes256Ccm,
Service::Aes256Kw,
Service::Aes256Kwp,
Service::Aes256,
Service::CtrDrbgAes256,
Service::HashDrbgSha384,
Service::HashDrbgSha512,
Service::HmacDrbgSha384,
Service::HmacDrbgSha512,
Service::HkdfSha384,
Service::HkdfSha512,
Service::KbkdfHmacSha384,
Service::KbkdfHmacSha512,
Service::KbkdfCmacAes256,
Service::MlKem1024Encaps,
Service::MlKem1024Decaps,
Service::MlKem1024Keygen,
Service::MlDsa87Sign,
Service::MlDsa87Verify,
Service::MlDsa87Keygen,
Service::XmssVerify,
];
fn assert_exact_membership(profile: AlgorithmProfile, expected: &[Service], allow_lms: bool) {
for svc in ALL_SERVICES {
let want = expected.iter().any(|e| *e as u16 == svc as u16)
|| (allow_lms && is_lms_service(svc));
assert_eq!(
is_allowed(profile, svc),
want,
"{svc} under {profile}: gate says {}, suite says {want}",
is_allowed(profile, svc)
);
}
}
#[test]
fn every_profile_round_trips_through_the_active_profile() {
for profile in [
AlgorithmProfile::Unrestricted,
AlgorithmProfile::Cnsa2,
AlgorithmProfile::Cnsa1,
AlgorithmProfile::Migration,
] {
assert_eq!(
AlgorithmProfile::from_u8(profile as u8),
profile,
"{profile} did not survive the round trip through its discriminant"
);
}
for raw in [4u8, 9, 200, 255] {
assert_ne!(
AlgorithmProfile::from_u8(raw),
AlgorithmProfile::Unrestricted,
"unknown discriminant {raw} must not open the module"
);
}
}
#[test]
fn migration_gates_as_itself_through_require_allowed() {
let guard = reset_for_test();
guard.reset();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Migration).unwrap();
assert_eq!(active_profile(), AlgorithmProfile::Migration);
assert!(
require_allowed(Service::EcdsaP384Sign).is_ok(),
"ECDSA P-384 is in CNSA 1.0 and must pass under Migration"
);
assert!(
require_allowed(Service::Dh3072).is_ok(),
"DH-3072 is in CNSA 1.0 and must pass under Migration"
);
assert!(
require_allowed(Service::MlKem1024Encaps).is_ok(),
"ML-KEM-1024 is in CNSA 2.0 and must pass under Migration"
);
assert!(require_allowed(Service::Sha256).is_err());
}
#[test]
fn profile_membership_is_exact() {
assert_exact_membership(AlgorithmProfile::Cnsa1, &EXPECTED_CNSA1, false);
assert_exact_membership(AlgorithmProfile::Cnsa2, &EXPECTED_CNSA2_NON_LMS, true);
for svc in ALL_SERVICES {
let want = is_allowed(AlgorithmProfile::Cnsa1, svc)
|| is_allowed(AlgorithmProfile::Cnsa2, svc);
assert_eq!(
is_allowed(AlgorithmProfile::Migration, svc),
want,
"{svc}: Migration must permit exactly the union"
);
}
for svc in ALL_SERVICES {
assert!(is_allowed(AlgorithmProfile::Unrestricted, svc));
}
}
#[test]
fn migration_is_the_union_of_both_suites() {
for svc in [
Service::EcdsaP384Sign,
Service::EcdhP384,
Service::RsaPssSign3072,
Service::Dh3072,
] {
assert!(
is_allowed(AlgorithmProfile::Migration, svc),
"{svc} (CNSA 1.0) must pass"
);
assert!(
!is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} is not CNSA 2.0"
);
}
for svc in [
Service::MlKem1024Encaps,
Service::MlDsa87Sign,
Service::XmssVerify,
Service::Sha512,
Service::LmsSha256M32H10W4Verify,
] {
assert!(
is_allowed(AlgorithmProfile::Migration, svc),
"{svc} (CNSA 2.0) must pass"
);
assert!(
!is_allowed(AlgorithmProfile::Cnsa1, svc),
"{svc} is not CNSA 1.0"
);
}
for svc in [Service::Aes256Gcm, Service::Sha384, Service::HmacSha384] {
assert!(is_allowed(AlgorithmProfile::Cnsa1, svc));
assert!(is_allowed(AlgorithmProfile::Cnsa2, svc));
assert!(is_allowed(AlgorithmProfile::Migration, svc));
}
for svc in [
Service::Sha1,
Service::Sha256,
Service::Aes128Gcm,
Service::RsaPssSign2048,
Service::EcdsaP256Sign,
Service::Ed25519Sign,
Service::Sha3_384,
Service::Shake256,
Service::Kmac256,
] {
assert!(
!is_allowed(AlgorithmProfile::Migration, svc),
"{svc} is in neither suite and must be refused"
);
assert!(is_allowed(AlgorithmProfile::Unrestricted, svc));
}
}
#[test]
fn cnsa1_allows_the_2016_suite() {
let allowed = [
Service::Aes256Gcm,
Service::Sha384,
Service::EcdsaP384Sign,
Service::EcdsaP384Verify,
Service::EcdsaP384Keygen,
Service::EcdhP384,
Service::RsaPssSign3072,
Service::RsaOaep3072,
Service::RsaPssSign4096,
Service::Dh3072,
Service::HmacSha384,
Service::CtrDrbgAes256,
Service::HashDrbgSha384,
Service::KbkdfHmacSha384,
];
for svc in allowed {
assert!(
is_allowed(AlgorithmProfile::Cnsa1, svc),
"{svc} should be allowed in CNSA 1.0"
);
}
}
#[test]
fn cnsa1_blocks_small_keys_and_ed25519() {
let blocked = [
Service::Sha1,
Service::Sha224,
Service::Aes128Ecb,
Service::Aes192Gcm,
Service::CtrDrbgAes128,
Service::EcdsaP256Sign,
Service::EcdhP256,
Service::Ed25519Sign,
Service::Ed25519Verify,
Service::RsaPssSign2048,
Service::RsaOaep2048,
Service::Sha256,
Service::HmacSha256,
Service::HashDrbgSha256,
Service::HkdfSha256,
Service::Sha512,
Service::HmacSha512,
Service::Sha3_256,
Service::Shake256,
Service::MlKem1024Encaps,
Service::MlDsa87Sign,
Service::XmssVerify,
Service::LmsSha256M32H10W4Verify,
];
for svc in blocked {
assert!(
!is_allowed(AlgorithmProfile::Cnsa1, svc),
"{svc} should be BLOCKED in CNSA 1.0"
);
}
}
#[test]
fn lms_gating_is_exhaustive_across_all_80_variants() {
let all_80 = LMS_SERVICES;
assert_eq!(all_80.len(), 80, "LMS enumeration drift");
for svc in all_80 {
assert!(
is_allowed(AlgorithmProfile::Unrestricted, svc),
"{svc} must be allowed in Unrestricted"
);
assert!(
is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} must be allowed in CNSA 2.0"
);
assert!(
!is_allowed(AlgorithmProfile::Cnsa1, svc),
"{svc} is not in CNSA 1.0 and must be refused"
);
assert!(
is_allowed(AlgorithmProfile::Migration, svc),
"{svc} must be allowed in Migration"
);
}
}
#[test]
fn lms_discriminant_slots_are_complete_and_pure() {
let mut discriminants = [0u16; 80];
for (slot, svc) in discriminants.iter_mut().zip(LMS_SERVICES) {
*slot = svc as u16;
}
discriminants.sort_unstable();
let mut compared = 0usize;
for pair in discriminants.windows(2) {
let [lo, hi] = pair else {
unreachable!("windows(2) yields two-element slices")
};
assert_eq!(*hi, lo + 2, "LMS slot after {lo} is missing or duplicated");
compared += 1;
}
assert_eq!(compared, 79, "slot sweep did not run in full");
let (Some(&first), Some(&last)) = (discriminants.first(), discriminants.last()) else {
unreachable!("the array holds 80 elements")
};
assert_eq!(first, 501, "LMS block does not start at 501");
assert_eq!(last, 659, "LMS block does not end at 659");
for svc in LMS_SERVICES {
assert!(is_lms_service(svc), "{svc} must be inside the LMS block");
}
let lms_count = ALL_SERVICES
.iter()
.filter(|svc| is_lms_service(**svc))
.count();
assert_eq!(lms_count, 80, "a non-LMS variant sits on an LMS slot");
for svc in [
Service::SlhDsaShake256fVerify,
Service::XmssVerify,
Service::Dh3072,
] {
assert!(!is_lms_service(svc), "{svc} must be outside the LMS block");
}
}
#[test]
fn slh_dsa_gating_is_exhaustive_across_all_36_variants() {
let all_36 = [
Service::SlhDsaSha2256sKeygen,
Service::SlhDsaSha2256sSign,
Service::SlhDsaSha2256sVerify,
Service::SlhDsaSha2128sKeygen,
Service::SlhDsaSha2128sSign,
Service::SlhDsaSha2128sVerify,
Service::SlhDsaSha2128fKeygen,
Service::SlhDsaSha2128fSign,
Service::SlhDsaSha2128fVerify,
Service::SlhDsaSha2192sKeygen,
Service::SlhDsaSha2192sSign,
Service::SlhDsaSha2192sVerify,
Service::SlhDsaSha2192fKeygen,
Service::SlhDsaSha2192fSign,
Service::SlhDsaSha2192fVerify,
Service::SlhDsaSha2256fKeygen,
Service::SlhDsaSha2256fSign,
Service::SlhDsaSha2256fVerify,
Service::SlhDsaShake128sKeygen,
Service::SlhDsaShake128sSign,
Service::SlhDsaShake128sVerify,
Service::SlhDsaShake128fKeygen,
Service::SlhDsaShake128fSign,
Service::SlhDsaShake128fVerify,
Service::SlhDsaShake192sKeygen,
Service::SlhDsaShake192sSign,
Service::SlhDsaShake192sVerify,
Service::SlhDsaShake192fKeygen,
Service::SlhDsaShake192fSign,
Service::SlhDsaShake192fVerify,
Service::SlhDsaShake256sKeygen,
Service::SlhDsaShake256sSign,
Service::SlhDsaShake256sVerify,
Service::SlhDsaShake256fKeygen,
Service::SlhDsaShake256fSign,
Service::SlhDsaShake256fVerify,
];
assert_eq!(all_36.len(), 36, "SLH-DSA enumeration drift");
for svc in all_36 {
assert!(
is_allowed(AlgorithmProfile::Unrestricted, svc),
"{svc} must be allowed in Unrestricted"
);
assert!(
!is_allowed(AlgorithmProfile::Cnsa1, svc),
"{svc} must be BLOCKED in CNSA 1.0"
);
assert!(
!is_allowed(AlgorithmProfile::Cnsa2, svc),
"{svc} must be BLOCKED in CNSA 2.0"
);
}
}
#[test]
fn require_allowed_returns_restricted_error_under_cnsa2() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
match require_allowed(Service::Aes128Ecb) {
Err(Error::AlgorithmRestricted {
service: Service::Aes128Ecb,
}) => {}
other => panic!("expected AlgorithmRestricted(Aes128Ecb), got {other:?}"),
}
}
#[test]
fn require_allowed_passes_permitted_services() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
require_allowed(Service::Aes256Gcm).unwrap();
require_allowed(Service::Sha384).unwrap();
require_allowed(Service::MlKem1024Encaps).unwrap();
}
#[test]
fn require_allowed_passes_everything_in_unrestricted() {
let _guard = reset_for_test();
initialize_with_tests(UNSIGNED_TEST_BINARY, &[]).unwrap();
require_allowed(Service::Sha1).unwrap();
require_allowed(Service::Aes128Ecb).unwrap();
require_allowed(Service::Ed25519Sign).unwrap();
require_allowed(Service::MlKem1024Encaps).unwrap();
}
#[test]
fn require_allowed_refuses_every_slh_dsa_service_under_cnsa2() {
let guard = reset_for_test();
for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Cnsa1] {
guard.reset();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], profile).unwrap();
for svc in [
Service::SlhDsaSha2256sKeygen,
Service::SlhDsaSha2256sSign,
Service::SlhDsaSha2256sVerify,
Service::SlhDsaShake128fVerify,
] {
match require_allowed(svc) {
Err(Error::AlgorithmRestricted { service }) => assert_eq!(service, svc),
other => {
panic!("expected AlgorithmRestricted({svc}) under {profile}, got {other:?}")
}
}
}
}
}
#[test]
fn require_allowed_admits_lms_m24_under_cnsa2_and_migration() {
let guard = reset_for_test();
for profile in [AlgorithmProfile::Cnsa2, AlgorithmProfile::Migration] {
guard.reset();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], profile).unwrap();
for svc in [
Service::LmsSha256M24H5W1Verify,
Service::LmsSha256M24H25W8Verify,
Service::LmsShakeM24H10W4Verify,
Service::LmsShakeM24H25W8Verify,
] {
require_allowed(svc)
.unwrap_or_else(|e| panic!("{svc} must be allowed under {profile}: {e:?}"));
}
}
}
#[test]
fn lms_block_boundaries_are_exact() {
assert!(!lms_block_contains(499), "499 is below the LMS block");
assert!(!lms_block_contains(500), "500 is even");
assert!(lms_block_contains(501), "501 is the first LMS discriminant");
assert!(!lms_block_contains(502), "502 is even");
assert!(!lms_block_contains(658), "658 is even");
assert!(lms_block_contains(659), "659 is the last LMS discriminant");
assert!(!lms_block_contains(660), "660 is above the LMS block");
assert!(!lms_block_contains(661), "661 is above the LMS block");
assert_eq!(Service::LmsSha256M32H5W1Verify as u16, 501);
assert_eq!(Service::LmsShakeM24H25W8Verify as u16, 659);
}
#[test]
fn require_allowed_distinguishes_cnsa1_from_cnsa2() {
let guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa1).unwrap();
require_allowed(Service::EcdsaP384Sign)
.unwrap_or_else(|e| panic!("P-384 ECDSA must be permitted under CNSA 1.0: {e:?}"));
guard.reset();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
match require_allowed(Service::EcdsaP384Sign) {
Err(Error::AlgorithmRestricted {
service: Service::EcdsaP384Sign,
}) => {}
other => panic!("expected P-384 ECDSA refused under CNSA 2.0, got {other:?}"),
}
}
#[test]
fn drbg_minimum_length_errors_display_their_cause() {
let entropy = Error::EntropyInputTooShort.to_string();
let nonce = Error::NonceTooShort.to_string();
assert!(
entropy.contains("entropy input is shorter than the minimum"),
"{entropy}"
);
assert!(nonce.contains("nonce is shorter than half"), "{nonce}");
assert_ne!(entropy, Error::InvalidInput.to_string());
}
#[test]
fn algorithm_profile_display() {
assert_eq!(AlgorithmProfile::Unrestricted.to_string(), "Unrestricted");
assert_eq!(AlgorithmProfile::Cnsa2.to_string(), "CNSA 2.0");
assert_eq!(AlgorithmProfile::Cnsa1.to_string(), "CNSA 1.0");
}
#[test]
fn algorithm_restricted_error_display() {
let _guard = reset_for_test();
initialize_with_profile(UNSIGNED_TEST_BINARY, &[], AlgorithmProfile::Cnsa2).unwrap();
let err = Error::AlgorithmRestricted {
service: Service::Aes128Ecb,
};
let msg = err.to_string();
assert!(msg.contains("AES-128-ECB"), "got: {msg}");
assert!(msg.contains("CNSA 2.0"), "got: {msg}");
}
#[test]
fn not_implemented_error_display() {
let err = Error::NotImplemented;
assert_eq!(err.to_string(), "algorithm not yet implemented");
}
#[test]
fn service_display_is_stable() {
assert_eq!(Service::Sha256.to_string(), "SHA-256");
assert_eq!(Service::Aes256Gcm.to_string(), "AES-256-GCM");
assert_eq!(Service::MlKem1024Encaps.to_string(), "ML-KEM-1024 encaps");
assert_eq!(Service::Ed25519Sign.to_string(), "Ed25519 sign");
}
}