use phf::phf_set;
use std::collections::BTreeMap;
use crate::policy::CryptoPolicy;
use crate::policy::default::CryptoPolicyDefault;
pub struct CryptoPolicyNIST {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum AlgKind {
Cipher,
Hash,
Pbkdf,
}
impl AlgKind {
fn approved_set(self) -> &'static phf::Set<&'static str> {
match self {
Self::Cipher => &CryptoPolicyNIST::NIST_APPROVED_CIPHERS,
Self::Hash => &CryptoPolicyNIST::NIST_APPROVED_HASHES,
Self::Pbkdf => &CryptoPolicyNIST::NIST_APPROVED_PBKDFS,
}
}
fn label(self) -> &'static str {
match self {
Self::Cipher => "Cipher",
Self::Hash => "Hash",
Self::Pbkdf => "PBKDF",
}
}
fn is_approved(self, alg: &str) -> Result<(), String> {
if self.approved_set().contains(alg) {
Ok(())
} else {
Err(format!(
"{} algorithm is not permitted by policy: {}",
self.label(),
alg
))
}
}
}
impl CryptoPolicyNIST {
const DEFAULT_PBKDF_ALG: &'static str = "pbkdf2-sha512";
const DEFAULT_PBKDF_SALT_LEN: usize = 32;
const DEFAULT_PBKDF_MSEC: u32 = CryptoPolicyDefault::DEFAULT_PBKDF_MSEC;
const DEFAULT_CIPHER_ALG: &'static str = "aes-256-gcm";
const NIST_PBKDF_MIN_SALT_LEN: usize = 16;
const NIST_APPROVED_PBKDFS: phf::Set<&'static str> = phf_set! {
"pbkdf2-sha256",
"pbkdf2-sha512",
};
const NIST_APPROVED_CIPHERS: phf::Set<&'static str> = phf_set! {
"aes-256-gcm",
};
const NIST_APPROVED_HASHES: phf::Set<&'static str> = phf_set! {
"sha3-256",
"sha3-512",
};
}
impl CryptoPolicy for CryptoPolicyNIST {
fn check_hash(&self, alg: &str) -> Result<(), String> {
AlgKind::Hash.is_approved(alg)
}
fn check_pbkdf(
&self,
alg: &str,
key_len: usize,
_password: &str,
salt: &[u8],
params: &BTreeMap<String, usize>,
) -> Result<(), String> {
AlgKind::Pbkdf.is_approved(alg)?;
if salt.len() < Self::NIST_PBKDF_MIN_SALT_LEN {
return Err("Salt length violates policy".to_string());
}
if key_len < 14 {
return Err("Key length violates policy".to_string());
}
if let Some(iters) = params.get("i") {
if *iters < 1000 {
return Err("Iteration count violates policy".to_string());
}
}
Ok(())
}
fn check_cipher_alg(&self, alg: &str) -> Result<(), String> {
self.check_cipher_alg_impl(alg.trim_end_matches("-det"))
}
fn check_cipher_alg_impl(&self, alg: &str) -> Result<(), String> {
AlgKind::Cipher.is_approved(alg)
}
fn check_cipher(&self, alg: &str, _key: &[u8], iv: &[u8], _ad: &[u8]) -> Result<(), String> {
let base = alg.trim_end_matches("-det");
AlgKind::Cipher.is_approved(base)?;
if base == "aes-256-gcm" && iv.len() != 96 / 8 {
return Err(
"IV length does not match NIST recommendations for this cipher.".to_string(),
);
}
Ok(())
}
fn default_pbkdf_alg(&self) -> String {
Self::DEFAULT_PBKDF_ALG.to_string()
}
fn default_pbkdf_salt_length(&self) -> usize {
Self::DEFAULT_PBKDF_SALT_LEN
}
fn default_pbkdf_millis(&self) -> u32 {
Self::DEFAULT_PBKDF_MSEC
}
fn default_cipher_alg(&self) -> String {
Self::DEFAULT_CIPHER_ALG.to_string()
}
}