Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions src/fips/kats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2026 Simo Sorce
// See LICENSE.txt file for terms

use super::set_fips_error_state;
use crate::attribute::Attribute;
use crate::error::Result;
use crate::hmac::test_get_hmac;
use crate::mechanism::Verify;
use crate::native::hmac::HMACOperation;
use crate::native::tlskdf::TLSPRF;
use crate::object::Object;
use crate::pkcs11::*;
use std::sync::LazyLock;

/// Holds the result of the FIPS self-test
pub struct FIPSSelftest {
pub result: CK_RV,
}

impl FIPSSelftest {
fn fail() -> FIPSSelftest {
set_fips_error_state();
FIPSSelftest {
result: CKR_FIPS_SELF_TEST_FAILED,
}
}
fn pass() -> FIPSSelftest {
FIPSSelftest { result: CKR_OK }
}
}

/// Lazy code to run FIPS Known Answer Tests (KATs) on first use
///
/// Uses a test vector from OpenSSL.
///
/// If the calculated output does not match the expected output, it sets the
/// FIPS error state and stores `CKR_FIPS_SELF_TEST_FAILED` in
/// [FIPSSelfTest.result].
pub static HMAC_SELFTEST: LazyLock<FIPSSelftest> = LazyLock::new(|| {
/* Test vector taken from OpenSSL selftest */
let plaintext: [u8; 16] = [
0xDD, 0x0C, 0x30, 0x33, 0x35, 0xF9, 0xE4, 0x2E, 0xC2, 0xEF, 0xCC, 0xBF,
0x07, 0x95, 0xEE, 0xA2,
];
let secret: Vec<u8> = vec![
0xF4, 0x55, 0x66, 0x50, 0xAC, 0x31, 0xD3, 0x54, 0x61, 0x61, 0x0B, 0xAC,
0x4E, 0xD8, 0x1B, 0x1A, 0x18, 0x1B, 0x2D, 0x8A, 0x43, 0xEA, 0x28, 0x54,
0xCB, 0xAE, 0x22, 0xCA, 0x74, 0x56, 0x08, 0x13,
];
let expect: [u8; 32] = [
0xF5, 0xF5, 0xE5, 0xF2, 0x66, 0x49, 0xE2, 0x40, 0xFC, 0x9E, 0x85, 0x7F,
0x2B, 0x9A, 0xBE, 0x28, 0x20, 0x12, 0x00, 0x92, 0x82, 0x21, 0x3E, 0x51,
0x44, 0x5D, 0xE3, 0x31, 0x04, 0x01, 0x72, 0x6B,
];

let mut hmac = match HMACOperation::internal(CKM_SHA256_HMAC, secret, 32) {
Ok(h) => h,
Err(_) => return FIPSSelftest::fail(),
};
if Verify::verify(&mut hmac, &plaintext, &expect).is_err() {
return FIPSSelftest::fail();
}
FIPSSelftest::pass()
});

fn secret_key_object(secret: Vec<u8>) -> Result<Object> {
let mut key = Object::new();
key.set_attr(Attribute::from_ulong(CKA_CLASS, CKO_SECRET_KEY))?;
key.set_attr(Attribute::from_ulong(CKA_KEY_TYPE, CKK_GENERIC_SECRET))?;
key.set_attr(Attribute::from_ulong(
CKA_VALUE_LEN,
secret.len() as CK_ULONG,
))?;
key.set_attr(Attribute::from_bytes(CKA_VALUE, secret))?;
key.set_attr(Attribute::from_bool(CKA_DERIVE, true))?;
Ok(key)
}

/// Static Lazy variable to run FIPS Known Answer Tests (KATs) for the TLS PRF
/// on first use
///
/// Uses a test vector from OpenSSL.
///
/// If the calculated output does not match the expected output, it sets the
/// FIPS error state and stores `CKR_FIPS_SELF_TEST_FAILED` in
/// [FIPSSelfTest.result].
pub static TLS_PRF_SELFTEST: LazyLock<FIPSSelftest> = LazyLock::new(|| {
/* Test vector taken from OpenSSL selftest */
let prf: CK_MECHANISM_TYPE = CKM_SHA256_HMAC;
let secret: Vec<u8> = vec![
0x20, 0x2C, 0x88, 0xC0, 0x0F, 0x84, 0xA1, 0x7A, 0x20, 0x02, 0x70, 0x79,
0x60, 0x47, 0x87, 0x46, 0x11, 0x76, 0x45, 0x55, 0x39, 0xE7, 0x05, 0xBE,
0x73, 0x08, 0x90, 0x60, 0x2C, 0x28, 0x9A, 0x50, 0x01, 0xE3, 0x4E, 0xEB,
0x3A, 0x04, 0x3E, 0x5D, 0x52, 0xA6, 0x5E, 0x66, 0x12, 0x51, 0x88, 0xBF,
];
let seed: Vec<u8> = vec![
b'k', b'e', b'y', b' ', b'e', b'x', b'p', b'a', b'n', b's', b'i', b'o',
b'n', 0xAE, 0x6C, 0x80, 0x6F, 0x8A, 0xD4, 0xD8, 0x07, 0x84, 0x54, 0x9D,
0xFF, 0x28, 0xA4, 0xB5, 0x8F, 0xD8, 0x37, 0x68, 0x1A, 0x51, 0xD9, 0x28,
0xC3, 0xE3, 0x0E, 0xE5, 0xFF, 0x14, 0xF3, 0x98, 0x68, 0x62, 0xE1, 0xFD,
0x91, 0xF2, 0x3F, 0x55, 0x8A, 0x60, 0x5F, 0x28, 0x47, 0x8C, 0x58, 0xCF,
0x72, 0x63, 0x7B, 0x89, 0x78, 0x4D, 0x95, 0x9D, 0xF7, 0xE9, 0x46, 0xD3,
0xF0, 0x7B, 0xD1, 0xB6, 0x16,
];

let expect: Vec<u8> = vec![
0xD0, 0x61, 0x39, 0x88, 0x9F, 0xFF, 0xAC, 0x1E, 0x3A, 0x71, 0x86, 0x5F,
0x50, 0x4A, 0xA5, 0xD0, 0xD2, 0xA2, 0xE8, 0x95, 0x06, 0xC6, 0xF2, 0x27,
0x9B, 0x67, 0x0C, 0x3E, 0x1B, 0x74, 0xF5, 0x31, 0x01, 0x6A, 0x25, 0x30,
0xC5, 0x1A, 0x3A, 0x0F, 0x7E, 0x1D, 0x65, 0x90, 0xD0, 0xF0, 0x56, 0x6B,
0x2F, 0x38, 0x7F, 0x8D, 0x11, 0xFD, 0x4F, 0x73, 0x1C, 0xDD, 0x57, 0x2D,
0x2E, 0xAE, 0x92, 0x7F, 0x6F, 0x2F, 0x81, 0x41, 0x0B, 0x25, 0xE6, 0x96,
0x0B, 0xE6, 0x89, 0x85, 0xAD, 0xD6, 0xC3, 0x84, 0x45, 0xAD, 0x9F, 0x8C,
0x64, 0xBF, 0x80, 0x68, 0xBF, 0x9A, 0x66, 0x79, 0x48, 0x5D, 0x96, 0x6F,
0x1A, 0xD6, 0xF6, 0x8B, 0x43, 0x49, 0x5B, 0x10, 0xA6, 0x83, 0x75, 0x5E,
0xA2, 0xB8, 0x58, 0xD7, 0x0C, 0xCA, 0xC7, 0xEC, 0x8B, 0x05, 0x3C, 0x6B,
0xD4, 0x1C, 0xA2, 0x99, 0xD4, 0xE5, 0x19, 0x28,
];

/* mock key */
let key = match secret_key_object(secret) {
Ok(s) => s,
Err(_) => return FIPSSelftest::fail(),
};

let mech = test_get_hmac(prf);

let mut tlsprf = match TLSPRF::init(&key, &mech, prf) {
Ok(a) => a,
Err(_) => return FIPSSelftest::fail(),
};
let out = match tlsprf.finish(&seed, expect.len()) {
Ok(a) => a,
Err(_) => return FIPSSelftest::fail(),
};
if out == expect {
FIPSSelftest::pass()
} else {
FIPSSelftest::fail()
}
});
1 change: 1 addition & 0 deletions src/fips/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::token::Token;
use ossl::fips;

pub(crate) mod indicators;
pub(crate) mod kats;

/// Sets the FIPS module into the error state
pub fn set_fips_error_state() {
Expand Down
1 change: 0 additions & 1 deletion src/mechanism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,6 @@ pub trait Digest: MechOperation {
}
}

/* not used in FIPS builds */
pub trait Mac: MechOperation {
/// One-step MAC function
///
Expand Down
138 changes: 31 additions & 107 deletions src/native/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ use crate::pkcs11::*;
use constant_time_eq::constant_time_eq;

#[cfg(feature = "fips")]
use crate::fips::set_fips_error_state;
#[cfg(feature = "fips")]
use std::sync::LazyLock;
use crate::fips::kats;

/// Maximum size for internal buffers
///
Expand Down Expand Up @@ -51,84 +49,6 @@ fn hmac_mech_to_hash_mech(
})
}

/// Holds the result of the FIPS self-test
#[cfg(feature = "fips")]
struct FIPSSelftest {
result: CK_RV,
}

#[cfg(feature = "fips")]
impl FIPSSelftest {
fn fail() -> FIPSSelftest {
set_fips_error_state();
FIPSSelftest {
result: CKR_FIPS_SELF_TEST_FAILED,
}
}
fn pass() -> FIPSSelftest {
FIPSSelftest { result: CKR_OK }
}
}

/// Lazy code to run FIPS Known Answer Tests (KATs) on initialization
///
/// Uses a test vector from OpenSSL.
///
/// If the calculated output does not match the expected output, it sets the
/// FIPS error state and stores `CKR_FIPS_SELF_TEST_FAILED` in
/// [FIPSSelfTest.result].
#[cfg(feature = "fips")]
static HMAC_SELFTEST: LazyLock<FIPSSelftest> = LazyLock::new(|| {
/* Test vector taken from OpenSSL selftest */
let mech: CK_MECHANISM_TYPE = CKM_SHA256_HMAC;
let plaintext: [u8; 16] = [
0xDD, 0x0C, 0x30, 0x33, 0x35, 0xF9, 0xE4, 0x2E, 0xC2, 0xEF, 0xCC, 0xBF,
0x07, 0x95, 0xEE, 0xA2,
];
let secret: Vec<u8> = vec![
0xF4, 0x55, 0x66, 0x50, 0xAC, 0x31, 0xD3, 0x54, 0x61, 0x61, 0x0B, 0xAC,
0x4E, 0xD8, 0x1B, 0x1A, 0x18, 0x1B, 0x2D, 0x8A, 0x43, 0xEA, 0x28, 0x54,
0xCB, 0xAE, 0x22, 0xCA, 0x74, 0x56, 0x08, 0x13,
];
let expect: [u8; 32] = [
0xF5, 0xF5, 0xE5, 0xF2, 0x66, 0x49, 0xE2, 0x40, 0xFC, 0x9E, 0x85, 0x7F,
0x2B, 0x9A, 0xBE, 0x28, 0x20, 0x12, 0x00, 0x92, 0x82, 0x21, 0x3E, 0x51,
0x44, 0x5D, 0xE3, 0x31, 0x04, 0x01, 0x72, 0x6B,
];

let hash = match hmac_mech_to_hash_mech(mech) {
Ok(h) => h,
Err(_) => return FIPSSelftest::fail(),
};
let hashlen = hash::hash_size(hash);
let blocklen = hash::block_size(hash);
let op = match hash::internal_hash_op(hash) {
Ok(h) => h,
Err(_) => return FIPSSelftest::fail(),
};
let mut hmac = HMACOperation {
mech: CKM_SHA256_HMAC,
key: secret,
hashlen: hashlen,
blocklen: blocklen,
outputlen: 32,
state: [0u8; MAX_BSZ],
ipad: IPAD_INIT,
opad: OPAD_INIT,
inner: op,
finalized: false,
in_use: false,
signature: None,
};
if hmac.init().is_err() {
return FIPSSelftest::fail();
}
if Verify::verify(&mut hmac, &plaintext, &expect).is_err() {
return FIPSSelftest::fail();
}
FIPSSelftest::pass()
});

#[derive(asn1::Asn1Read, asn1::Asn1Write)]
struct HMACSaveState<'a> {
dgst_state: &'a [u8],
Expand Down Expand Up @@ -177,56 +97,60 @@ impl Drop for HMACOperation {
}

impl HMACOperation {
/// Instantiates a new HMAC operation
pub fn new(
pub fn internal(
mech: CK_MECHANISM_TYPE,
mut key: HmacKey,
key: Vec<u8>,
outputlen: usize,
signature: Option<&[u8]>,
) -> Result<HMACOperation> {
#[cfg(feature = "fips")]
if (*HMAC_SELFTEST).result != CKR_OK {
return Err((*HMAC_SELFTEST).result)?;
}
let hash = hmac_mech_to_hash_mech(mech)?;
let hashlen = hash::hash_size(hash);
let blocklen = hash::block_size(hash);
let op = hash::internal_hash_op(hash)?;
let mut hmac = HMACOperation {
mech: mech,
key: key.take(),
hashlen: hashlen,
blocklen: blocklen,
key: key,
hashlen: hash::hash_size(hash),
blocklen: hash::block_size(hash),
outputlen: outputlen,
state: [0u8; MAX_BSZ],
ipad: IPAD_INIT,
opad: OPAD_INIT,
inner: op,
inner: hash::internal_hash_op(hash)?,
finalized: false,
in_use: false,
signature: match signature {
Some(s) => {
if s.len() != outputlen {
return Err(CKR_SIGNATURE_LEN_RANGE)?;
}
Some(s.to_vec())
}
None => None,
},
signature: None,
};
hmac.init()?;
Ok(hmac)
}

/// Instantiates a new HMAC operation
pub fn new(
mech: CK_MECHANISM_TYPE,
mut key: HmacKey,
outputlen: usize,
signature: Option<&[u8]>,
) -> Result<HMACOperation> {
#[cfg(feature = "fips")]
if (*kats::HMAC_SELFTEST).result != CKR_OK {
return Err((*kats::HMAC_SELFTEST).result)?;
}
let mut hmac = HMACOperation::internal(mech, key.take(), outputlen)?;
if let Some(s) = signature {
if s.len() != outputlen {
return Err(CKR_SIGNATURE_LEN_RANGE)?;
}
hmac.signature = Some(s.to_vec())
};
Ok(hmac)
}

pub fn restore(
mech: CK_MECHANISM_TYPE,
key: HmacKey,
signature: Option<&[u8]>,
state: &[u8],
) -> Result<HMACOperation> {
#[cfg(feature = "fips")]
if (*HMAC_SELFTEST).result != CKR_OK {
return Err((*HMAC_SELFTEST).result)?;
if (*kats::HMAC_SELFTEST).result != CKR_OK {
return Err((*kats::HMAC_SELFTEST).result)?;
}
let save_state: HMACSaveState =
asn1::parse_single(state).map_err(|_| CKR_SAVED_STATE_INVALID)?;
Expand Down
Loading
Loading