use super::error::AuditError;
pub const MASTER_KEY_ENV: &str = "JAMMI_AUDIT_MASTER_KEY";
pub trait SigningKeyStore: Send + Sync + 'static {
fn master_key(&self) -> Result<[u8; 32], AuditError>;
}
pub struct EnvSigningKeyStore;
impl SigningKeyStore for EnvSigningKeyStore {
fn master_key(&self) -> Result<[u8; 32], AuditError> {
let hex_str = std::env::var(MASTER_KEY_ENV)
.map_err(|_| AuditError::MasterKey(format!("{MASTER_KEY_ENV} is not set")))?;
let bytes = hex::decode(hex_str.trim())
.map_err(|e| AuditError::MasterKey(format!("not valid hex: {e}")))?;
let arr: [u8; 32] = bytes.as_slice().try_into().map_err(|_| {
AuditError::MasterKey(format!(
"expected 32 bytes (64 hex chars), got {} bytes",
bytes.len()
))
})?;
Ok(arr)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
const TEST_KEY: &str = "0000000000000000000000000000000000000000000000000000000000000001";
#[test]
fn valid_key_decodes_to_32_bytes() {
let _g = ENV_LOCK.lock().unwrap();
std::env::set_var(MASTER_KEY_ENV, TEST_KEY);
let key = EnvSigningKeyStore.master_key().unwrap();
let mut expected = [0u8; 32];
expected[31] = 1;
assert_eq!(key, expected);
}
#[test]
fn missing_master_key_is_error() {
let _g = ENV_LOCK.lock().unwrap();
std::env::remove_var(MASTER_KEY_ENV);
assert!(matches!(
EnvSigningKeyStore.master_key(),
Err(AuditError::MasterKey(_))
));
}
#[test]
fn bad_length_master_key_is_error() {
let _g = ENV_LOCK.lock().unwrap();
std::env::set_var(MASTER_KEY_ENV, "abcd");
assert!(matches!(
EnvSigningKeyStore.master_key(),
Err(AuditError::MasterKey(_))
));
std::env::remove_var(MASTER_KEY_ENV);
}
}