use data_encoding::HEXLOWER;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::encoding::decode;
use crate::error::KeyError;
use crate::types::{Encoding, MANIFEST_KEY};
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct MandateKey([u8; 64]);
impl MandateKey {
pub fn generate() -> Self {
let mut bytes = [0u8; 64];
getrandom::getrandom(&mut bytes).expect("platform CSPRNG unavailable");
MandateKey(bytes)
}
pub fn from_bytes(bytes: [u8; 64]) -> Result<Self, KeyError> {
if bytes == MANIFEST_KEY {
return Err(KeyError::IsManifestKey);
}
if bytes == [0u8; 64] {
return Err(KeyError::AllZero);
}
Ok(MandateKey(bytes))
}
pub fn from_hex(hex: &str) -> Result<Self, KeyError> {
let decoded =
zeroize::Zeroizing::new(decode(hex, Encoding::Hex).ok_or(KeyError::BadHexEncoding)?);
let bytes: [u8; 64] = decoded
.as_slice()
.try_into()
.map_err(|_| KeyError::BadHexLength { got: decoded.len() })?;
Self::from_bytes(bytes)
}
pub(crate) fn bytes(&self) -> &[u8; 64] {
&self.0
}
}
pub fn generate_key() -> String {
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
getrandom::getrandom(&mut bytes[..]).expect("platform CSPRNG unavailable");
HEXLOWER.encode(&bytes[..])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_hex_accepts_canonical_lowercase() {
assert!(MandateKey::from_hex(&"2a".repeat(64)).is_ok());
}
#[test]
fn from_hex_rejects_uppercase_and_bad_length() {
assert_eq!(
MandateKey::from_hex(&"2A".repeat(64)).err(),
Some(KeyError::BadHexEncoding)
);
assert_eq!(
MandateKey::from_hex("2a2a2a").err(),
Some(KeyError::BadHexLength { got: 3 })
);
assert_eq!(
MandateKey::from_hex("2a2").err(),
Some(KeyError::BadHexEncoding)
);
}
#[test]
fn from_hex_rejects_manifest_and_zero() {
assert_eq!(
MandateKey::from_hex(&HEXLOWER.encode(&MANIFEST_KEY)).err(),
Some(KeyError::IsManifestKey)
);
assert_eq!(
MandateKey::from_hex(&"00".repeat(64)).err(),
Some(KeyError::AllZero)
);
}
#[test]
fn generate_key_is_canonical_hex_that_round_trips() {
let hex = generate_key();
assert_eq!(hex.len(), 128);
assert!(hex
.chars()
.all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c)));
assert!(MandateKey::from_hex(&hex).is_ok());
}
}