use crate::config::AlgorithmConfig;
use crate::error::{HsmError, HsmResult};
#[allow(unused_imports)]
use crate::pkcs11_abi::types::CK_MECHANISM_TYPE;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalStatus {
Approved,
NonApproved,
Unknown,
}
impl ApprovalStatus {
pub fn is_approved(self) -> bool {
matches!(self, Self::Approved)
}
}
pub fn approval_status(mechanism: CK_MECHANISM_TYPE) -> ApprovalStatus {
use crate::pkcs11_abi::constants::*;
match mechanism {
CKM_SHA256 | CKM_SHA384 | CKM_SHA512 => ApprovalStatus::Approved,
CKM_SHA3_256 | CKM_SHA3_384 | CKM_SHA3_512 => ApprovalStatus::Approved,
CKM_SHA_1 => ApprovalStatus::NonApproved,
CKM_RSA_PKCS => ApprovalStatus::Approved,
CKM_SHA256_RSA_PKCS | CKM_SHA384_RSA_PKCS | CKM_SHA512_RSA_PKCS => ApprovalStatus::Approved,
CKM_RSA_PKCS_PSS
| CKM_SHA256_RSA_PKCS_PSS
| CKM_SHA384_RSA_PKCS_PSS
| CKM_SHA512_RSA_PKCS_PSS => ApprovalStatus::Approved,
CKM_RSA_PKCS_OAEP => ApprovalStatus::Approved,
CKM_RSA_PKCS_KEY_PAIR_GEN => ApprovalStatus::Approved,
CKM_ECDSA | CKM_ECDSA_SHA256 | CKM_ECDSA_SHA384 | CKM_ECDSA_SHA512 => {
ApprovalStatus::Approved
}
CKM_EC_KEY_PAIR_GEN => ApprovalStatus::Approved,
CKM_EC_EDWARDS_KEY_PAIR_GEN | CKM_EDDSA => ApprovalStatus::NonApproved,
CKM_AES_GCM => ApprovalStatus::Approved,
CKM_AES_CBC | CKM_AES_CBC_PAD => ApprovalStatus::Approved,
CKM_AES_CTR => ApprovalStatus::Approved,
CKM_AES_KEY_GEN => ApprovalStatus::Approved,
CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_KWP => ApprovalStatus::Approved,
CKM_ECDH1_DERIVE => ApprovalStatus::Approved,
CKM_ML_KEM_KEY_PAIR_GEN | CKM_ML_KEM_512 | CKM_ML_KEM_768 | CKM_ML_KEM_1024 => {
ApprovalStatus::Approved
}
CKM_ML_DSA_KEY_PAIR_GEN | CKM_ML_DSA | CKM_ML_DSA_44 | CKM_ML_DSA_65 | CKM_ML_DSA_87 => {
ApprovalStatus::Approved
}
CKM_SLH_DSA_KEY_PAIR_GEN | CKM_SLH_DSA | CKM_SLH_DSA_SHA2_128S | CKM_SLH_DSA_SHA2_256S => {
ApprovalStatus::Approved
}
CKM_HYBRID_ML_DSA_ECDSA => ApprovalStatus::NonApproved,
CKM_HYBRID_X25519_ML_KEM_768 | CKM_HYBRID_X25519_ML_KEM_1024 => ApprovalStatus::NonApproved,
_ => ApprovalStatus::Unknown,
}
}
pub fn enforce(
mechanism: CK_MECHANISM_TYPE,
config: &AlgorithmConfig,
key_size_bits: Option<u32>,
) -> HsmResult<()> {
if !config.fips_approved_only {
return Ok(());
}
let status = approval_status(mechanism);
match status {
ApprovalStatus::Approved => {
if mechanism == crate::pkcs11_abi::constants::CKM_RSA_PKCS {
tracing::warn!(
"CKM_RSA_PKCS (PKCS#1 v1.5) is approved but deprecated per SP 800-131A Rev.2; \
consider migrating to CKM_RSA_PKCS_OAEP or CKM_RSA_PKCS_PSS"
);
}
if let Some(bits) = key_size_bits {
let is_rsa = matches!(
mechanism,
crate::pkcs11_abi::constants::CKM_RSA_PKCS
| crate::pkcs11_abi::constants::CKM_RSA_PKCS_PSS
| crate::pkcs11_abi::constants::CKM_RSA_PKCS_OAEP
| crate::pkcs11_abi::constants::CKM_RSA_PKCS_KEY_PAIR_GEN
| crate::pkcs11_abi::constants::CKM_SHA256_RSA_PKCS
| crate::pkcs11_abi::constants::CKM_SHA384_RSA_PKCS
| crate::pkcs11_abi::constants::CKM_SHA512_RSA_PKCS
| crate::pkcs11_abi::constants::CKM_SHA256_RSA_PKCS_PSS
| crate::pkcs11_abi::constants::CKM_SHA384_RSA_PKCS_PSS
| crate::pkcs11_abi::constants::CKM_SHA512_RSA_PKCS_PSS
);
if is_rsa && bits < 2048 {
tracing::warn!(
"RSA key size {} bits is below FIPS minimum (2048); rejected",
bits
);
return Err(HsmError::KeySizeRange);
}
}
Ok(())
}
ApprovalStatus::NonApproved => {
tracing::warn!(
"Mechanism 0x{:08X} is non-approved; rejected in FIPS approved-only mode",
mechanism
);
Err(HsmError::MechanismInvalid)
}
ApprovalStatus::Unknown => {
tracing::warn!(
"Unknown mechanism 0x{:08X}; rejected in FIPS approved-only mode",
mechanism
);
Err(HsmError::MechanismInvalid)
}
}
}
pub fn is_fips_approved(mechanism: CK_MECHANISM_TYPE) -> bool {
approval_status(mechanism).is_approved()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pkcs11_abi::constants::*;
#[test]
fn test_approved_mechanisms() {
assert!(approval_status(CKM_SHA256).is_approved());
assert!(approval_status(CKM_RSA_PKCS).is_approved());
assert!(approval_status(CKM_AES_GCM).is_approved());
assert!(approval_status(CKM_ECDSA).is_approved());
assert!(approval_status(CKM_ML_DSA_65).is_approved());
assert!(approval_status(CKM_ML_KEM_768).is_approved());
}
#[test]
fn test_non_approved_mechanisms() {
assert!(!approval_status(CKM_EDDSA).is_approved());
assert!(!approval_status(CKM_SHA_1).is_approved());
assert!(!approval_status(CKM_HYBRID_ML_DSA_ECDSA).is_approved());
}
#[test]
fn test_enforce_fips_mode() {
let mut config = AlgorithmConfig::default();
config.fips_approved_only = true;
assert!(enforce(CKM_SHA256, &config, None).is_ok());
assert!(enforce(CKM_EDDSA, &config, None).is_err());
assert!(enforce(CKM_SHA_1, &config, None).is_err());
}
#[test]
fn test_enforce_non_fips_mode() {
let mut config = AlgorithmConfig::default();
config.fips_approved_only = false;
assert!(enforce(CKM_SHA256, &config, None).is_ok());
assert!(enforce(CKM_EDDSA, &config, None).is_ok());
assert!(enforce(CKM_SHA_1, &config, None).is_ok());
}
#[test]
fn test_enforce_rsa_key_size_fips() {
let mut config = AlgorithmConfig::default();
config.fips_approved_only = true;
assert!(enforce(CKM_RSA_PKCS, &config, Some(2048)).is_ok());
assert!(enforce(CKM_RSA_PKCS, &config, Some(4096)).is_ok());
assert!(enforce(CKM_RSA_PKCS, &config, Some(1024)).is_err());
assert!(enforce(CKM_RSA_PKCS_PSS, &config, Some(1024)).is_err());
assert!(enforce(CKM_AES_GCM, &config, Some(128)).is_ok());
}
#[test]
fn test_enforce_rsa_key_size_non_fips() {
let mut config = AlgorithmConfig::default();
config.fips_approved_only = false;
assert!(enforce(CKM_RSA_PKCS, &config, Some(1024)).is_ok());
}
}