use std::fmt;
use std::io;
#[derive(Debug)]
pub enum FipsError {
KatFailed(&'static str),
PairwiseTestFailed(&'static str),
RngRepetitionDetected,
IntegrityCheckFailed,
}
impl fmt::Display for FipsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FipsError::KatFailed(alg) => write!(f, "KAT failed for {}", alg),
FipsError::PairwiseTestFailed(alg) => write!(f, "Pairwise test failed for {}", alg),
FipsError::RngRepetitionDetected => write!(f, "RNG repetition detected"),
FipsError::IntegrityCheckFailed => write!(f, "Module integrity check failed"),
}
}
}
impl std::error::Error for FipsError {}
impl From<FipsError> for io::Error {
fn from(e: FipsError) -> Self {
io::Error::new(io::ErrorKind::Other, e)
}
}
pub fn run_power_up_tests() -> Result<(), FipsError> {
super::verify_module_integrity()?;
test_sha512_kat()?;
test_hmac_sha512_kat()?;
test_hkdf_sha512_kat()?;
test_aes_gcm_siv_kat()?;
test_ml_kem_1024_kat()?;
test_ml_dsa_87_kat()?;
Ok(())
}
fn test_sha512_kat() -> Result<(), FipsError> {
use sha2::{Digest, Sha512};
let input = b"abc";
let expected = hex::decode(
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
)
.unwrap();
let mut hasher = Sha512::new();
hasher.update(input);
let result = hasher.finalize();
if result.as_slice() != expected.as_slice() {
return Err(FipsError::KatFailed("SHA-512"));
}
Ok(())
}
fn test_hmac_sha512_kat() -> Result<(), FipsError> {
use hmac::{Hmac, Mac};
use sha2::Sha512;
let key = b"key";
let message = b"The quick brown fox jumps over the lazy dog";
let expected = hex::decode(
"b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb\
82f948a549f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a",
)
.unwrap();
let mut mac = Hmac::<Sha512>::new_from_slice(key).unwrap();
mac.update(message);
let result = mac.finalize();
if result.into_bytes().as_slice() != expected.as_slice() {
return Err(FipsError::KatFailed("HMAC-SHA512"));
}
Ok(())
}
fn test_hkdf_sha512_kat() -> Result<(), FipsError> {
use hkdf::Hkdf;
use sha2::Sha512;
let ikm = b"input key material";
let salt = b"salt";
let info = b"info";
let hkdf = Hkdf::<Sha512>::new(Some(salt), ikm);
let mut okm = [0u8; 42];
hkdf.expand(info, &mut okm).unwrap();
if okm.iter().all(|&b| b == 0) {
return Err(FipsError::KatFailed("HKDF-SHA512"));
}
Ok(())
}
fn test_aes_gcm_siv_kat() -> Result<(), FipsError> {
use aes_gcm_siv::{
aead::{Aead, KeyInit, Payload},
Aes256GcmSiv, Nonce,
};
let key =
hex::decode("0100000000000000000000000000000000000000000000000000000000000000").unwrap();
let nonce_bytes = hex::decode("030000000000000000000000").unwrap();
let nonce = Nonce::from_slice(&nonce_bytes);
let plaintext = b"";
let cipher = Aes256GcmSiv::new_from_slice(&key).unwrap();
let ciphertext = cipher
.encrypt(
nonce,
Payload {
msg: plaintext,
aad: b"",
},
)
.map_err(|_| FipsError::KatFailed("AES-256-GCM-SIV encrypt"))?;
let expected = hex::decode("07f5f4169bbf55a8400cd47ea6fd400f").unwrap();
if ciphertext != expected {
return Err(FipsError::KatFailed("AES-256-GCM-SIV"));
}
let decrypted = cipher
.decrypt(
nonce,
Payload {
msg: &ciphertext,
aad: b"",
},
)
.map_err(|_| FipsError::KatFailed("AES-256-GCM-SIV decrypt"))?;
if decrypted != plaintext {
return Err(FipsError::KatFailed("AES-256-GCM-SIV round-trip"));
}
Ok(())
}
fn test_ml_kem_1024_kat() -> Result<(), FipsError> {
use oqs::kem::Kem;
let kem = Kem::new(oqs::kem::Algorithm::MlKem1024)
.map_err(|_| FipsError::KatFailed("ML-KEM-1024 init"))?;
let (pk, sk) = kem
.keypair()
.map_err(|_| FipsError::KatFailed("ML-KEM-1024 keygen"))?;
let (ct, ss1) = kem
.encapsulate(&pk)
.map_err(|_| FipsError::KatFailed("ML-KEM-1024 encapsulate"))?;
let ss2 = kem
.decapsulate(&sk, &ct)
.map_err(|_| FipsError::KatFailed("ML-KEM-1024 decapsulate"))?;
if ss1.as_ref() != ss2.as_ref() {
return Err(FipsError::PairwiseTestFailed("ML-KEM-1024"));
}
Ok(())
}
fn test_ml_dsa_87_kat() -> Result<(), FipsError> {
use oqs::sig::Sig;
let sig_alg = Sig::new(oqs::sig::Algorithm::MlDsa87)
.map_err(|_| FipsError::KatFailed("ML-DSA-87 init"))?;
let (pk, sk) = sig_alg
.keypair()
.map_err(|_| FipsError::KatFailed("ML-DSA-87 keygen"))?;
let message = b"FIPS 140-3 self-test message";
let signature = sig_alg
.sign(message, &sk)
.map_err(|_| FipsError::KatFailed("ML-DSA-87 sign"))?;
sig_alg
.verify(message, &signature, &pk)
.map_err(|_| FipsError::PairwiseTestFailed("ML-DSA-87"))?;
Ok(())
}
pub fn test_mlkem_pairwise(pk: &[u8], sk: &[u8]) -> Result<(), FipsError> {
use oqs::kem::Kem;
let kem = Kem::new(oqs::kem::Algorithm::MlKem1024)
.map_err(|_| FipsError::PairwiseTestFailed("ML-KEM-1024 init"))?;
let pk_obj = kem
.public_key_from_bytes(pk)
.ok_or(FipsError::PairwiseTestFailed(
"ML-KEM-1024 invalid public key",
))?;
let sk_obj = kem
.secret_key_from_bytes(sk)
.ok_or(FipsError::PairwiseTestFailed(
"ML-KEM-1024 invalid secret key",
))?;
let (ct, ss1) = kem
.encapsulate(pk_obj)
.map_err(|_| FipsError::PairwiseTestFailed("ML-KEM-1024 encapsulate"))?;
let ss2 = kem
.decapsulate(sk_obj, &ct)
.map_err(|_| FipsError::PairwiseTestFailed("ML-KEM-1024 decapsulate"))?;
if ss1.as_ref() != ss2.as_ref() {
return Err(FipsError::PairwiseTestFailed("ML-KEM-1024"));
}
Ok(())
}
pub fn test_mldsa_pairwise(pk: &[u8], sk: &[u8]) -> Result<(), FipsError> {
use oqs::sig::Sig;
let sig_alg = Sig::new(oqs::sig::Algorithm::MlDsa87)
.map_err(|_| FipsError::PairwiseTestFailed("ML-DSA-87 init"))?;
let pk_obj = sig_alg
.public_key_from_bytes(pk)
.ok_or(FipsError::PairwiseTestFailed(
"ML-DSA-87 invalid public key",
))?;
let sk_obj = sig_alg
.secret_key_from_bytes(sk)
.ok_or(FipsError::PairwiseTestFailed(
"ML-DSA-87 invalid secret key",
))?;
let test_message = b"Pairwise consistency test";
let signature = sig_alg
.sign(test_message, sk_obj)
.map_err(|_| FipsError::PairwiseTestFailed("ML-DSA-87 sign"))?;
sig_alg
.verify(test_message, &signature, pk_obj)
.map_err(|_| FipsError::PairwiseTestFailed("ML-DSA-87 verify"))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_power_up_tests() {
assert!(run_power_up_tests().is_ok());
}
#[test]
fn test_sha512() {
assert!(test_sha512_kat().is_ok());
}
#[test]
fn test_hmac_sha512() {
assert!(test_hmac_sha512_kat().is_ok());
}
#[test]
fn test_hkdf_sha512() {
assert!(test_hkdf_sha512_kat().is_ok());
}
#[test]
fn test_aes_gcm_siv() {
assert!(test_aes_gcm_siv_kat().is_ok());
}
#[test]
fn test_ml_kem_1024() {
assert!(test_ml_kem_1024_kat().is_ok());
}
#[test]
fn test_ml_dsa_87() {
assert!(test_ml_dsa_87_kat().is_ok());
}
#[test]
fn test_module_integrity() {
use crate::fips::{module_integrity_info, verify_module_integrity};
assert!(verify_module_integrity().is_ok());
let info = module_integrity_info();
assert_eq!(info.hash.len(), 64); assert!(info.hash.chars().all(|c| c.is_ascii_hexdigit()));
assert!(!info.build_timestamp.is_empty());
println!("{}", info);
}
}