use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[repr(u32)]
pub enum FipsErrorCode {
SelfTestFailed = 0x0001,
IntegrityCheckFailed = 0x0002,
ConditionalTestFailed = 0x0003,
KatFailed = 0x0004,
ContinuousRngTestFailed = 0x0005,
InvalidKeyLength = 0x0100,
InvalidNonce = 0x0101,
DecryptionFailed = 0x0102,
SignatureInvalid = 0x0103,
InvalidParameter = 0x0104,
UnsupportedAlgorithm = 0x0105,
KeyGenerationFailed = 0x0106,
EncapsulationFailed = 0x0107,
DecapsulationFailed = 0x0108,
SigningFailed = 0x0109,
InvalidCiphertext = 0x010A,
InvalidPublicKey = 0x010B,
InvalidSecretKey = 0x010C,
EncryptionFailed = 0x010D,
HashFailed = 0x010E,
MacFailed = 0x010F,
KeyDerivationFailed = 0x0110,
RngFailure = 0x0200,
ZeroizationFailed = 0x0201,
ResourceExhausted = 0x0202,
InternalError = 0x0203,
IoError = 0x0204,
SerializationFailed = 0x0205,
DeserializationFailed = 0x0206,
BufferTooSmall = 0x0207,
Timeout = 0x0208,
ModuleNotInitialized = 0x0300,
OperationNotPermitted = 0x0301,
ModuleInErrorState = 0x0302,
FeatureNotAvailable = 0x0303,
KeyValidationFailed = 0x0304,
WeakKeyDetected = 0x0305,
}
impl FipsErrorCode {
#[must_use]
pub const fn code(&self) -> u32 {
*self as u32
}
#[must_use]
pub const fn message(&self) -> &'static str {
match self {
Self::SelfTestFailed => "Power-up self-test failed",
Self::IntegrityCheckFailed => "Integrity check failed",
Self::ConditionalTestFailed => "Conditional self-test failed",
Self::KatFailed => "Known answer test failed",
Self::ContinuousRngTestFailed => "Continuous RNG test failed",
Self::InvalidKeyLength => "Invalid key length",
Self::InvalidNonce => "Invalid nonce or IV",
Self::DecryptionFailed => "Decryption authentication failed",
Self::SignatureInvalid => "Signature verification failed",
Self::InvalidParameter => "Invalid parameter",
Self::UnsupportedAlgorithm => "Unsupported algorithm",
Self::KeyGenerationFailed => "Key generation failed",
Self::EncapsulationFailed => "Encapsulation failed",
Self::DecapsulationFailed => "Decapsulation failed",
Self::SigningFailed => "Signing failed",
Self::InvalidCiphertext => "Invalid ciphertext",
Self::InvalidPublicKey => "Invalid public key",
Self::InvalidSecretKey => "Invalid secret key",
Self::EncryptionFailed => "Encryption failed",
Self::HashFailed => "Hash operation failed",
Self::MacFailed => "MAC operation failed",
Self::KeyDerivationFailed => "Key derivation failed",
Self::RngFailure => "Random number generation failed",
Self::ZeroizationFailed => "Key zeroization failed",
Self::ResourceExhausted => "Resource exhausted",
Self::InternalError => "Internal error",
Self::IoError => "I/O error",
Self::SerializationFailed => "Serialization failed",
Self::DeserializationFailed => "Deserialization failed",
Self::BufferTooSmall => "Buffer too small",
Self::Timeout => "Operation timeout",
Self::ModuleNotInitialized => "Module not initialized",
Self::OperationNotPermitted => "Operation not permitted",
Self::ModuleInErrorState => "Module in error state",
Self::FeatureNotAvailable => "Feature not available",
Self::KeyValidationFailed => "Key validation failed",
Self::WeakKeyDetected => "Weak key detected",
}
}
#[must_use]
pub const fn is_critical(&self) -> bool {
matches!(
self,
Self::SelfTestFailed
| Self::IntegrityCheckFailed
| Self::ConditionalTestFailed
| Self::KatFailed
| Self::ContinuousRngTestFailed
| Self::ModuleInErrorState
)
}
#[must_use]
pub const fn is_self_test_error(&self) -> bool {
let code = self.code();
code >= 0x0001 && code <= 0x00FF
}
#[must_use]
pub const fn is_algorithm_error(&self) -> bool {
let code = self.code();
code >= 0x0100 && code <= 0x01FF
}
#[must_use]
pub const fn is_operational_error(&self) -> bool {
let code = self.code();
code >= 0x0200 && code <= 0x02FF
}
#[must_use]
pub const fn is_status_code(&self) -> bool {
let code = self.code();
code >= 0x0300 && code <= 0x03FF
}
#[must_use]
pub const fn category(&self) -> &'static str {
if self.is_self_test_error() {
"SELF_TEST"
} else if self.is_algorithm_error() {
"ALGORITHM"
} else if self.is_operational_error() {
"OPERATIONAL"
} else {
"STATUS"
}
}
}
impl fmt::Display for FipsErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FIPS-{:04X}: {}", self.code(), self.message())
}
}
pub trait FipsError {
fn fips_code(&self) -> FipsErrorCode;
fn fips_message(&self) -> String {
self.fips_code().to_string()
}
fn is_fips_critical(&self) -> bool {
self.fips_code().is_critical()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FipsCompliantError {
code: FipsErrorCode,
context: Option<String>,
}
impl FipsCompliantError {
#[must_use]
pub const fn new(code: FipsErrorCode) -> Self {
Self { code, context: None }
}
#[must_use]
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
#[must_use]
pub const fn code(&self) -> FipsErrorCode {
self.code
}
#[must_use]
pub fn context(&self) -> Option<&str> {
self.context.as_deref()
}
#[must_use]
pub const fn is_critical(&self) -> bool {
self.code.is_critical()
}
}
impl fmt::Display for FipsCompliantError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.context {
Some(ctx) => write!(f, "{} ({})", self.code, ctx),
None => write!(f, "{}", self.code),
}
}
}
impl std::error::Error for FipsCompliantError {}
impl FipsError for FipsCompliantError {
fn fips_code(&self) -> FipsErrorCode {
self.code
}
}
impl FipsError for crate::primitives::error::PrimitivesError {
fn fips_code(&self) -> FipsErrorCode {
match self {
Self::FeatureNotAvailable(_) => FipsErrorCode::FeatureNotAvailable,
Self::InvalidInput(_) => FipsErrorCode::InvalidParameter,
Self::EncryptionFailed(_) => FipsErrorCode::EncryptionFailed,
Self::DecryptionFailed(_) => FipsErrorCode::DecryptionFailed,
Self::SerializationError(_) => FipsErrorCode::SerializationFailed,
Self::DeserializationError(_) => FipsErrorCode::DeserializationFailed,
Self::Other(_) => FipsErrorCode::InternalError,
Self::MlKem(e) => e.fips_code(),
Self::ResourceExceeded(_) => FipsErrorCode::ResourceExhausted,
Self::KeyValidationFailed => FipsErrorCode::KeyValidationFailed,
Self::WeakKey => FipsErrorCode::WeakKeyDetected,
Self::InvalidKeyFormat => FipsErrorCode::InvalidParameter,
}
}
}
impl FipsError for crate::primitives::kem::ml_kem::MlKemError {
fn fips_code(&self) -> FipsErrorCode {
match self {
Self::KeyGenerationError(_) => FipsErrorCode::KeyGenerationFailed,
Self::EncapsulationError(_) => FipsErrorCode::EncapsulationFailed,
Self::DecapsulationError(_) => FipsErrorCode::DecapsulationFailed,
Self::InvalidKeyLength { .. } => FipsErrorCode::InvalidKeyLength,
Self::InvalidCiphertextLength { .. } => FipsErrorCode::InvalidCiphertext,
Self::UnsupportedSecurityLevel(_) => FipsErrorCode::UnsupportedAlgorithm,
Self::CryptoError(_) => FipsErrorCode::InternalError,
}
}
}
impl From<FipsErrorCode> for FipsCompliantError {
fn from(code: FipsErrorCode) -> Self {
Self::new(code)
}
}
impl From<&crate::primitives::error::PrimitivesError> for FipsErrorCode {
fn from(error: &crate::primitives::error::PrimitivesError) -> Self {
error.fips_code()
}
}
impl From<&crate::primitives::kem::ml_kem::MlKemError> for FipsErrorCode {
fn from(error: &crate::primitives::kem::ml_kem::MlKemError) -> Self {
error.fips_code()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_code_values_match_fips_spec_fails() {
assert_eq!(FipsErrorCode::SelfTestFailed.code(), 0x0001);
assert_eq!(FipsErrorCode::IntegrityCheckFailed.code(), 0x0002);
assert_eq!(FipsErrorCode::ConditionalTestFailed.code(), 0x0003);
assert_eq!(FipsErrorCode::KatFailed.code(), 0x0004);
assert_eq!(FipsErrorCode::ContinuousRngTestFailed.code(), 0x0005);
assert_eq!(FipsErrorCode::InvalidKeyLength.code(), 0x0100);
assert_eq!(FipsErrorCode::InvalidNonce.code(), 0x0101);
assert_eq!(FipsErrorCode::DecryptionFailed.code(), 0x0102);
assert_eq!(FipsErrorCode::SignatureInvalid.code(), 0x0103);
assert_eq!(FipsErrorCode::RngFailure.code(), 0x0200);
assert_eq!(FipsErrorCode::ZeroizationFailed.code(), 0x0201);
assert_eq!(FipsErrorCode::ResourceExhausted.code(), 0x0202);
assert_eq!(FipsErrorCode::ModuleNotInitialized.code(), 0x0300);
assert_eq!(FipsErrorCode::OperationNotPermitted.code(), 0x0301);
assert_eq!(FipsErrorCode::ModuleInErrorState.code(), 0x0302);
}
#[test]
fn test_critical_errors_identified_correctly_fails() {
assert!(FipsErrorCode::SelfTestFailed.is_critical());
assert!(FipsErrorCode::IntegrityCheckFailed.is_critical());
assert!(FipsErrorCode::ConditionalTestFailed.is_critical());
assert!(FipsErrorCode::KatFailed.is_critical());
assert!(FipsErrorCode::ContinuousRngTestFailed.is_critical());
assert!(FipsErrorCode::ModuleInErrorState.is_critical());
assert!(!FipsErrorCode::InvalidKeyLength.is_critical());
assert!(!FipsErrorCode::DecryptionFailed.is_critical());
assert!(!FipsErrorCode::RngFailure.is_critical());
assert!(!FipsErrorCode::ModuleNotInitialized.is_critical());
}
#[test]
fn test_error_categories_classified_correctly_fails() {
assert!(FipsErrorCode::SelfTestFailed.is_self_test_error());
assert!(FipsErrorCode::IntegrityCheckFailed.is_self_test_error());
assert!(!FipsErrorCode::InvalidKeyLength.is_self_test_error());
assert!(FipsErrorCode::InvalidKeyLength.is_algorithm_error());
assert!(FipsErrorCode::DecryptionFailed.is_algorithm_error());
assert!(FipsErrorCode::SignatureInvalid.is_algorithm_error());
assert!(!FipsErrorCode::RngFailure.is_algorithm_error());
assert!(FipsErrorCode::RngFailure.is_operational_error());
assert!(FipsErrorCode::ResourceExhausted.is_operational_error());
assert!(!FipsErrorCode::InvalidKeyLength.is_operational_error());
assert!(FipsErrorCode::ModuleNotInitialized.is_status_code());
assert!(FipsErrorCode::FeatureNotAvailable.is_status_code());
assert!(!FipsErrorCode::InvalidKeyLength.is_status_code());
}
#[test]
fn test_category_strings_match_expected_succeeds() {
assert_eq!(FipsErrorCode::SelfTestFailed.category(), "SELF_TEST");
assert_eq!(FipsErrorCode::InvalidKeyLength.category(), "ALGORITHM");
assert_eq!(FipsErrorCode::RngFailure.category(), "OPERATIONAL");
assert_eq!(FipsErrorCode::ModuleNotInitialized.category(), "STATUS");
}
#[test]
fn test_display_format_matches_expected() {
let code = FipsErrorCode::InvalidKeyLength;
let display = format!("{}", code);
assert_eq!(display, "FIPS-0100: Invalid key length");
let code = FipsErrorCode::SelfTestFailed;
let display = format!("{}", code);
assert_eq!(display, "FIPS-0001: Power-up self-test failed");
}
#[test]
fn test_fips_compliant_error_construction_succeeds() {
let error = FipsCompliantError::new(FipsErrorCode::InvalidKeyLength);
assert_eq!(error.code(), FipsErrorCode::InvalidKeyLength);
assert!(error.context().is_none());
assert!(!error.is_critical());
let error_with_context = FipsCompliantError::new(FipsErrorCode::InvalidKeyLength)
.with_context("Expected 32 bytes");
assert_eq!(error_with_context.context(), Some("Expected 32 bytes"));
let display = format!("{}", error_with_context);
assert!(display.contains("FIPS-0100"));
assert!(display.contains("Expected 32 bytes"));
}
#[test]
fn test_fips_error_trait_maps_codes_correctly_fails() {
let error = crate::primitives::error::PrimitivesError::InvalidInput("test".to_string());
assert_eq!(error.fips_code(), FipsErrorCode::InvalidParameter);
assert!(!error.is_fips_critical());
let ml_kem_error =
crate::primitives::kem::ml_kem::MlKemError::KeyGenerationError("test".to_string());
assert_eq!(ml_kem_error.fips_code(), FipsErrorCode::KeyGenerationFailed);
let ml_kem_unsupported =
crate::primitives::kem::ml_kem::MlKemError::UnsupportedSecurityLevel(
"test".to_string(),
);
assert_eq!(ml_kem_unsupported.fips_code(), FipsErrorCode::UnsupportedAlgorithm);
let ml_kem_crypto =
crate::primitives::kem::ml_kem::MlKemError::CryptoError("test".to_string());
assert_eq!(ml_kem_crypto.fips_code(), FipsErrorCode::InternalError);
}
#[test]
fn test_error_from_conversions_map_correctly_fails() {
let error =
crate::primitives::error::PrimitivesError::DecryptionFailed("auth failed".to_string());
let fips_code: FipsErrorCode = (&error).into();
assert_eq!(fips_code, FipsErrorCode::DecryptionFailed);
}
#[test]
fn test_messages_no_sensitive_data_present_succeeds() {
let sensitive_patterns = ["key=", "password", "secret", "private", "0x"];
for code in [
FipsErrorCode::SelfTestFailed,
FipsErrorCode::InvalidKeyLength,
FipsErrorCode::DecryptionFailed,
FipsErrorCode::RngFailure,
FipsErrorCode::ModuleNotInitialized,
] {
let message = code.message().to_lowercase();
for pattern in &sensitive_patterns {
assert!(
!message.contains(pattern),
"Message for {:?} contains sensitive pattern '{}': {}",
code,
pattern,
message
);
}
}
}
#[test]
fn test_all_messages_non_empty_for_all_codes_succeeds() {
let all_codes = [
FipsErrorCode::SelfTestFailed,
FipsErrorCode::IntegrityCheckFailed,
FipsErrorCode::ConditionalTestFailed,
FipsErrorCode::KatFailed,
FipsErrorCode::ContinuousRngTestFailed,
FipsErrorCode::InvalidKeyLength,
FipsErrorCode::InvalidNonce,
FipsErrorCode::DecryptionFailed,
FipsErrorCode::SignatureInvalid,
FipsErrorCode::InvalidParameter,
FipsErrorCode::UnsupportedAlgorithm,
FipsErrorCode::KeyGenerationFailed,
FipsErrorCode::EncapsulationFailed,
FipsErrorCode::DecapsulationFailed,
FipsErrorCode::SigningFailed,
FipsErrorCode::InvalidCiphertext,
FipsErrorCode::InvalidPublicKey,
FipsErrorCode::InvalidSecretKey,
FipsErrorCode::EncryptionFailed,
FipsErrorCode::HashFailed,
FipsErrorCode::MacFailed,
FipsErrorCode::KeyDerivationFailed,
FipsErrorCode::RngFailure,
FipsErrorCode::ZeroizationFailed,
FipsErrorCode::ResourceExhausted,
FipsErrorCode::InternalError,
FipsErrorCode::IoError,
FipsErrorCode::SerializationFailed,
FipsErrorCode::DeserializationFailed,
FipsErrorCode::BufferTooSmall,
FipsErrorCode::Timeout,
FipsErrorCode::ModuleNotInitialized,
FipsErrorCode::OperationNotPermitted,
FipsErrorCode::ModuleInErrorState,
FipsErrorCode::FeatureNotAvailable,
FipsErrorCode::KeyValidationFailed,
FipsErrorCode::WeakKeyDetected,
];
for code in &all_codes {
let msg = code.message();
assert!(!msg.is_empty(), "Message for {:?} is empty", code);
let display = format!("{}", code);
assert!(
display.starts_with("FIPS-"),
"Display for {:?} doesn't start with FIPS-",
code
);
}
}
#[test]
fn test_fips_error_trait_all_error_variants_map_correctly_fails() {
let cases: Vec<(crate::primitives::error::PrimitivesError, FipsErrorCode)> = vec![
(
crate::primitives::error::PrimitivesError::FeatureNotAvailable("test".into()),
FipsErrorCode::FeatureNotAvailable,
),
(
crate::primitives::error::PrimitivesError::InvalidInput("test".into()),
FipsErrorCode::InvalidParameter,
),
(
crate::primitives::error::PrimitivesError::EncryptionFailed("test".into()),
FipsErrorCode::EncryptionFailed,
),
(
crate::primitives::error::PrimitivesError::DecryptionFailed("test".into()),
FipsErrorCode::DecryptionFailed,
),
(
crate::primitives::error::PrimitivesError::SerializationError("test".into()),
FipsErrorCode::SerializationFailed,
),
(
crate::primitives::error::PrimitivesError::DeserializationError("test".into()),
FipsErrorCode::DeserializationFailed,
),
(
crate::primitives::error::PrimitivesError::Other("test".into()),
FipsErrorCode::InternalError,
),
(
crate::primitives::error::PrimitivesError::ResourceExceeded("test".into()),
FipsErrorCode::ResourceExhausted,
),
(
crate::primitives::error::PrimitivesError::KeyValidationFailed,
FipsErrorCode::KeyValidationFailed,
),
(crate::primitives::error::PrimitivesError::WeakKey, FipsErrorCode::WeakKeyDetected),
(
crate::primitives::error::PrimitivesError::InvalidKeyFormat,
FipsErrorCode::InvalidParameter,
),
];
for (error, expected_code) in &cases {
assert_eq!(error.fips_code(), *expected_code, "Wrong FIPS code for {:?}", error);
}
}
#[test]
fn test_fips_error_trait_all_ml_kem_variants_map_correctly_fails() {
use crate::primitives::kem::ml_kem::MlKemError;
let cases: Vec<(MlKemError, FipsErrorCode)> = vec![
(MlKemError::KeyGenerationError("test".into()), FipsErrorCode::KeyGenerationFailed),
(MlKemError::EncapsulationError("test".into()), FipsErrorCode::EncapsulationFailed),
(MlKemError::DecapsulationError("test".into()), FipsErrorCode::DecapsulationFailed),
(
MlKemError::InvalidKeyLength {
variant: "ML-KEM-768".into(),
size: 32,
actual: 16,
key_type: "public".into(),
},
FipsErrorCode::InvalidKeyLength,
),
(
MlKemError::InvalidCiphertextLength {
variant: "ML-KEM-768".into(),
expected: 1088,
actual: 100,
},
FipsErrorCode::InvalidCiphertext,
),
(
MlKemError::UnsupportedSecurityLevel("test".into()),
FipsErrorCode::UnsupportedAlgorithm,
),
(MlKemError::CryptoError("test".into()), FipsErrorCode::InternalError),
];
for (error, expected_code) in &cases {
assert_eq!(error.fips_code(), *expected_code, "Wrong FIPS code for {:?}", error);
let converted: FipsErrorCode = error.into();
assert_eq!(converted, *expected_code);
}
}
#[test]
fn test_fips_error_trait_default_methods_return_correctly_fails() {
let error = FipsCompliantError::new(FipsErrorCode::SelfTestFailed);
assert!(error.is_fips_critical());
let msg = error.fips_message();
assert!(msg.contains("FIPS-0001"));
let error = FipsCompliantError::new(FipsErrorCode::InvalidKeyLength);
assert!(!error.is_fips_critical());
}
#[test]
fn test_fips_compliant_error_display_without_context_succeeds() {
let error = FipsCompliantError::new(FipsErrorCode::RngFailure);
let display = format!("{}", error);
assert_eq!(display, "FIPS-0200: Random number generation failed");
}
#[test]
fn test_fips_compliant_error_from_code_succeeds() {
let error: FipsCompliantError = FipsErrorCode::Timeout.into();
assert_eq!(error.code(), FipsErrorCode::Timeout);
assert!(error.context().is_none());
}
#[test]
fn test_fips_compliant_error_is_std_error_compatible_fails() {
let error = FipsCompliantError::new(FipsErrorCode::BufferTooSmall);
let _: &dyn std::error::Error = &error;
}
#[test]
fn test_fips_error_ml_kem_from_error_maps_correctly_fails() {
use crate::primitives::kem::ml_kem::MlKemError;
let ml_kem_err = MlKemError::KeyGenerationError("test".into());
let error = crate::primitives::error::PrimitivesError::MlKem(ml_kem_err);
assert_eq!(error.fips_code(), FipsErrorCode::KeyGenerationFailed);
}
#[test]
fn test_error_code_uniqueness_verified_fails() {
let codes = [
FipsErrorCode::SelfTestFailed,
FipsErrorCode::IntegrityCheckFailed,
FipsErrorCode::ConditionalTestFailed,
FipsErrorCode::KatFailed,
FipsErrorCode::ContinuousRngTestFailed,
FipsErrorCode::InvalidKeyLength,
FipsErrorCode::InvalidNonce,
FipsErrorCode::DecryptionFailed,
FipsErrorCode::SignatureInvalid,
FipsErrorCode::InvalidParameter,
FipsErrorCode::UnsupportedAlgorithm,
FipsErrorCode::KeyGenerationFailed,
FipsErrorCode::EncapsulationFailed,
FipsErrorCode::DecapsulationFailed,
FipsErrorCode::SigningFailed,
FipsErrorCode::InvalidCiphertext,
FipsErrorCode::InvalidPublicKey,
FipsErrorCode::InvalidSecretKey,
FipsErrorCode::EncryptionFailed,
FipsErrorCode::HashFailed,
FipsErrorCode::MacFailed,
FipsErrorCode::KeyDerivationFailed,
FipsErrorCode::RngFailure,
FipsErrorCode::ZeroizationFailed,
FipsErrorCode::ResourceExhausted,
FipsErrorCode::InternalError,
FipsErrorCode::IoError,
FipsErrorCode::SerializationFailed,
FipsErrorCode::DeserializationFailed,
FipsErrorCode::BufferTooSmall,
FipsErrorCode::Timeout,
FipsErrorCode::ModuleNotInitialized,
FipsErrorCode::OperationNotPermitted,
FipsErrorCode::ModuleInErrorState,
FipsErrorCode::FeatureNotAvailable,
FipsErrorCode::KeyValidationFailed,
FipsErrorCode::WeakKeyDetected,
];
let mut seen = std::collections::HashSet::new();
for code in codes {
assert!(seen.insert(code.code()), "Duplicate error code: {:04X}", code.code());
}
}
}