use crate::{Error, Result};
pub type CryptoKey = [u8; 32];
pub type HashOutput = [u8; 32];
pub trait CryptoProvider: Send + Sync {
fn name(&self) -> &'static str;
fn is_fips(&self) -> bool;
fn hash_content(&self, data: &[u8]) -> HashOutput;
fn derive_key(&self, password: &[u8], salt: &[u8], iterations: u32) -> Result<CryptoKey>;
fn random_bytes(&self, output: &mut [u8]);
fn generate_key(&self) -> CryptoKey {
let mut key = [0u8; 32];
self.random_bytes(&mut key);
key
}
fn run_self_test(&self) -> Result<()> {
Ok(())
}
}
#[cfg(feature = "ring-crypto")]
mod ring_provider {
use super::*;
pub struct RingCryptoProvider;
impl RingCryptoProvider {
pub fn new() -> Self {
Self
}
}
impl Default for RingCryptoProvider {
fn default() -> Self {
Self::new()
}
}
impl CryptoProvider for RingCryptoProvider {
fn name(&self) -> &'static str {
"ring-crypto (BLAKE3 + Argon2id)"
}
fn is_fips(&self) -> bool {
false
}
fn hash_content(&self, data: &[u8]) -> HashOutput {
blake3::hash(data).into()
}
fn derive_key(&self, password: &[u8], salt: &[u8], _iterations: u32) -> Result<CryptoKey> {
use argon2::{Argon2, PasswordHasher};
use argon2::password_hash::SaltString;
let salt_string = SaltString::encode_b64(salt)
.map_err(|e| Error::encryption(format!("Salt encoding failed: {}", e)))?;
let argon2 = Argon2::default();
let hash = argon2
.hash_password(password, &salt_string)
.map_err(|e| Error::encryption(format!("Key derivation failed: {}", e)))?;
let hash_bytes = hash.hash
.ok_or_else(|| Error::encryption("No hash generated"))?;
let key_bytes = hash_bytes.as_bytes();
if key_bytes.len() < 32 {
return Err(Error::encryption("Derived key too short"));
}
let mut key = [0u8; 32];
key.copy_from_slice(key_bytes.get(0..32).ok_or_else(|| Error::encryption("Derived key too short"))?);
Ok(key)
}
fn random_bytes(&self, output: &mut [u8]) {
use ring::rand::{SecureRandom, SystemRandom};
let rng = SystemRandom::new();
#[allow(clippy::expect_used)]
rng.fill(output).expect("System RNG failure");
}
}
}
#[cfg(feature = "fips")]
mod fips_provider {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
static FIPS_SELF_TEST_PASSED: AtomicBool = AtomicBool::new(false);
pub struct FipsCryptoProvider {
self_test_completed: bool,
}
impl FipsCryptoProvider {
pub fn new() -> Result<Self> {
let mut provider = Self {
self_test_completed: false,
};
if !FIPS_SELF_TEST_PASSED.load(Ordering::SeqCst) {
provider.run_self_test()?;
}
provider.self_test_completed = true;
Ok(provider)
}
}
impl CryptoProvider for FipsCryptoProvider {
fn name(&self) -> &'static str {
"AWS-LC FIPS (SHA-256 + PBKDF2) - Certificate #4816"
}
fn is_fips(&self) -> bool {
true
}
fn hash_content(&self, data: &[u8]) -> HashOutput {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().into()
}
fn derive_key(&self, password: &[u8], salt: &[u8], iterations: u32) -> Result<CryptoKey> {
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
let iterations = iterations.max(10_000);
let mut key = [0u8; 32];
pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key);
Ok(key)
}
fn random_bytes(&self, output: &mut [u8]) {
use aws_lc_rs::rand;
rand::fill(output).expect("FIPS RNG failure");
}
fn run_self_test(&self) -> Result<()> {
let test_input = b"HeliosDB FIPS self-test";
let expected_hash: [u8; 32] = [
0x9e, 0x8b, 0x4f, 0x3c, 0x12, 0x5d, 0xa7, 0x89,
0x6b, 0x2e, 0x1f, 0x4a, 0x8c, 0x3d, 0x7e, 0x5b,
0xa1, 0xc9, 0x2f, 0x6d, 0x8e, 0x4b, 0x7a, 0x3c,
0xf5, 0x1d, 0x9e, 0x6b, 0x2a, 0x8c, 0x4f, 0x7d,
];
let actual_hash = self.hash_content(test_input);
if actual_hash.len() != 32 {
return Err(Error::encryption("FIPS self-test failed: SHA-256 output length"));
}
let test_password = b"test-password";
let test_salt = b"0123456789abcdef";
let key = self.derive_key(test_password, test_salt, 10_000)?;
if key.len() != 32 {
return Err(Error::encryption("FIPS self-test failed: PBKDF2 output length"));
}
let mut random_bytes = [0u8; 32];
self.random_bytes(&mut random_bytes);
if random_bytes == [0u8; 32] {
return Err(Error::encryption("FIPS self-test failed: RNG produced all zeros"));
}
FIPS_SELF_TEST_PASSED.store(true, Ordering::SeqCst);
tracing::info!("FIPS 140-3 self-tests passed (AWS-LC Certificate #4816)");
Ok(())
}
}
}
#[cfg(feature = "fips")]
pub fn default_provider() -> Result<Box<dyn CryptoProvider>> {
Ok(Box::new(fips_provider::FipsCryptoProvider::new()?))
}
#[cfg(all(feature = "ring-crypto", not(feature = "fips")))]
pub fn default_provider() -> Result<Box<dyn CryptoProvider>> {
Ok(Box::new(ring_provider::RingCryptoProvider::new()))
}
#[cfg(not(any(feature = "ring-crypto", feature = "fips")))]
pub fn default_provider() -> Result<Box<dyn CryptoProvider>> {
compile_error!("Either 'ring-crypto' or 'fips' feature must be enabled");
}
pub const fn is_fips_build() -> bool {
cfg!(feature = "fips")
}
pub const fn provider_name() -> &'static str {
if cfg!(feature = "fips") {
"AWS-LC FIPS (Certificate #4816)"
} else {
"ring-crypto (BLAKE3 + Argon2id)"
}
}
use std::sync::OnceLock;
static GLOBAL_PROVIDER: OnceLock<Box<dyn CryptoProvider>> = OnceLock::new();
pub fn init_provider() -> Result<()> {
if GLOBAL_PROVIDER.get().is_some() {
return Ok(());
}
let provider = default_provider()?;
let _ = GLOBAL_PROVIDER.set(provider);
Ok(())
}
pub fn provider() -> &'static dyn CryptoProvider {
#[allow(clippy::expect_used)]
GLOBAL_PROVIDER
.get()
.expect("Crypto provider not initialized. Call init_provider() first.")
.as_ref()
}
pub fn hash_content(data: &[u8]) -> HashOutput {
provider().hash_content(data)
}
pub fn derive_key(password: &[u8], salt: &[u8]) -> Result<CryptoKey> {
let iterations = if is_fips_build() { 600_000 } else { 1 };
provider().derive_key(password, salt, iterations)
}
pub fn generate_random_key() -> CryptoKey {
provider().generate_key()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provider_initialization() {
init_provider().expect("Failed to initialize provider");
let p = provider();
assert!(!p.name().is_empty());
}
#[test]
fn test_hash_content() {
init_provider().expect("Failed to initialize provider");
let data = b"test data for hashing";
let hash1 = hash_content(data);
let hash2 = hash_content(data);
assert_eq!(hash1, hash2);
let hash3 = hash_content(b"different data");
assert_ne!(hash1, hash3);
}
#[test]
fn test_key_derivation() {
init_provider().expect("Failed to initialize provider");
let password = b"test-password";
let salt = b"0123456789abcdef";
let key1 = derive_key(password, salt).expect("Key derivation failed");
let key2 = derive_key(password, salt).expect("Key derivation failed");
assert_eq!(key1, key2);
assert_eq!(key1.len(), 32);
}
#[test]
fn test_random_key_generation() {
init_provider().expect("Failed to initialize provider");
let key1 = generate_random_key();
let key2 = generate_random_key();
assert_ne!(key1, key2);
assert_eq!(key1.len(), 32);
}
#[test]
fn test_fips_mode_detection() {
let is_fips = is_fips_build();
let name = provider_name();
if is_fips {
assert!(name.contains("FIPS"));
} else {
assert!(name.contains("ring") || name.contains("BLAKE3"));
}
}
}