use wolf_crypto_sys::{wc_PBKDF2};
use crate::{can_cast_i32, const_can_cast_i32, Fips, Unspecified};
use crate::kdf::{Salt, Iters};
#[cfg(feature = "allow-non-fips")]
use crate::kdf::salt::NonEmpty as MinSize;
#[cfg(not(feature = "allow-non-fips"))]
use crate::kdf::salt::Min16 as MinSize;
use crate::mac::hmac::algo::Hash;
use core::fmt;
pub const FIPS_MIN_KEY: usize = 14;
unsafe fn pbkdf2_unchecked<H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters,
out: &mut [u8]
) {
debug_assert!(
can_cast_i32(out.len())
&& can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size()
);
#[cfg(not(feature = "allow-non-fips"))] {
debug_assert!(out.len() >= FIPS_MIN_KEY);
}
let _res = wc_PBKDF2(
out.as_mut_ptr(),
password.as_ptr(),
password.len() as i32,
salt.ptr(),
salt.i_size(),
iters.get() as i32,
out.len() as i32,
H::type_id()
);
debug_assert_eq!(_res, 0);
}
#[cfg(not(feature = "allow-non-fips"))]
#[inline]
#[must_use]
const fn check_key_len(len: usize) -> bool {
can_cast_i32(len) && len >= FIPS_MIN_KEY
}
#[cfg(feature = "allow-non-fips")]
#[inline]
#[must_use]
const fn check_key_len(len: usize) -> bool {
can_cast_i32(len)
}
#[cfg(not(feature = "allow-non-fips"))]
#[inline]
#[must_use]
const fn const_check_key_len<const L: usize>() -> bool {
const_can_cast_i32::<L>() && L >= FIPS_MIN_KEY
}
#[cfg(feature = "allow-non-fips")]
#[inline]
#[must_use]
const fn const_check_key_len<const L: usize>() -> bool {
const_can_cast_i32::<L>()
}
pub fn pbkdf2_into<H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters,
out_key: &mut [u8]
) -> Result<(), Unspecified> {
if can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size()
&& check_key_len(out_key.len()) {
unsafe { pbkdf2_unchecked::<H>(password, salt, iters, out_key) };
Ok(())
} else {
Err(Unspecified)
}
}
pub fn pbkdf2<const KL: usize, H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters
) -> Result<[u8; KL], Unspecified> {
if const_check_key_len::<KL>()
&& can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size() {
let mut out = [0u8; KL];
unsafe { pbkdf2_unchecked::<H>(password, salt, iters, out.as_mut_slice()) };
Ok(out)
} else {
Err(Unspecified)
}
}
use core::marker::PhantomData;
use crate::kdf::salt::Min16;
use crate::sealed::FipsSealed;
#[derive(Copy, Clone)]
pub struct FipsPbkdf2<H: Hash + Fips> {
_hash: PhantomData<H>
}
impl<H: Hash + Fips> fmt::Debug for FipsPbkdf2<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FipsPbkdf2<")
.and_then(|_| H::write_alg_name(f))
.and_then(|_| f.write_str(">"))
}
}
impl<H: Hash + Fips> FipsPbkdf2<H> {
#[inline]
pub fn array<const KL: usize>(
password: &[u8],
salt: impl Salt<Min16>,
iters: Iters
) -> Result<[u8; KL], Unspecified> {
#[cfg(feature = "allow-non-fips")] {
if KL < FIPS_MIN_KEY { return Err(Unspecified) }
}
pbkdf2::<KL, H>(password, salt, iters)
}
#[inline]
pub fn into(
password: &[u8],
salt: impl Salt<Min16>,
iters: Iters,
out_key: &mut [u8]
) -> Result<(), Unspecified> {
#[cfg(feature = "allow-non-fips")] {
if out_key.len() < FIPS_MIN_KEY { return Err(Unspecified) }
}
pbkdf2_into::<H>(password, salt, iters, out_key)
}
#[cfg(test)]
const fn new() -> Self {
Self { _hash: PhantomData }
}
}
impl<H: Hash + Fips> FipsSealed for FipsPbkdf2<H> {}
impl<H: Hash + Fips> Fips for FipsPbkdf2<H> {}
non_fips! {
use wolf_crypto_sys::wc_PBKDF1;
unsafe fn pbkdf1_unchecked<H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters,
out: &mut [u8]
) {
debug_assert!(
can_cast_i32(out.len())
&& can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size()
);
let _res = wc_PBKDF1(
out.as_mut_ptr(),
password.as_ptr(),
password.len() as i32,
salt.ptr(),
salt.i_size(),
iters.get() as i32,
out.len() as i32,
H::type_id()
);
debug_assert_eq!(_res, 0);
}
pub fn pbkdf1_into<H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters,
out_key: &mut [u8]
) -> Result<(), Unspecified> {
if can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size()
&& check_key_len(out_key.len()) {
unsafe { pbkdf1_unchecked::<H>(password, salt, iters, out_key) };
Ok(())
} else {
Err(Unspecified)
}
}
pub fn pbkdf1<const KL: usize, H: Hash>(
password: &[u8],
salt: impl Salt<MinSize>,
iters: Iters
) -> Result<[u8; KL], Unspecified> {
if const_check_key_len::<KL>()
&& can_cast_i32(password.len())
&& salt.i_is_valid_size()
&& iters.is_valid_size() {
let mut out = [0u8; KL];
unsafe { pbkdf1_unchecked::<H>(password, salt, iters, out.as_mut_slice()) };
Ok(out)
} else {
Err(Unspecified)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kdf::{FipsSaltSlice, Sha256};
macro_rules! bogus_slice {
($sz:expr) => {{
unsafe { core::slice::from_raw_parts(b"bogus".as_ptr(), $sz) }
}};
(mut $sz:expr) => {{
unsafe { core::slice::from_raw_parts_mut(b"bogus".as_ptr().cast_mut(), $sz) }
}};
}
#[test]
fn catch_pwd_overflow() {
let pass = bogus_slice!(i32::MAX as usize + 1);
assert!(pbkdf2::<32, Sha256>(pass, [0u8; 16], Iters::new(100).unwrap()).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1::<32, Sha256>(pass, [0u8; 16], Iters::new(100).unwrap()).is_err());
}
let mut out = [0; 69];
assert!(pbkdf2_into::<Sha256>(pass, [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1_into::<Sha256>(pass, [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
}
}
#[test]
fn catch_salt_overflow() {
let salt = FipsSaltSlice::new(bogus_slice!(i32::MAX as usize + 1)).unwrap();
let pass = b"my password";
assert!(pbkdf2::<32, Sha256>(pass, salt.clone(), Iters::new(100).unwrap()).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1::<32, Sha256>(pass, salt.clone(), Iters::new(100).unwrap()).is_err());
}
let mut out = [0; 69];
assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), &mut out).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), &mut out).is_err());
}
}
#[test]
fn catch_iters_overflow() {
let salt = [0u8; 16];
let pass = b"my password";
let iters = Iters::new(i32::MAX as u32 + 1).unwrap();
assert!(pbkdf2::<32, Sha256>(pass, salt.clone(), iters).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1::<32, Sha256>(pass, salt.clone(), iters).is_err());
}
let mut out = [0; 69];
assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), iters, &mut out).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), iters, &mut out).is_err());
}
}
#[test]
fn catch_desired_key_overflow() {
let desired = bogus_slice!(mut i32::MAX as usize + 1);
let salt = [0u8; 16];
let pass = b"my password";
assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), desired).is_err());
#[cfg(feature = "allow-non-fips")] {
assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), desired).is_err());
}
}
#[test]
#[cfg_attr(feature = "allow-non-fips", ignore)]
fn catch_fips_min_key() {
let mut out = [0u8; 13];
assert!(pbkdf2::<13, Sha256>(b"hello world", [0u8; 16], Iters::new(100).unwrap()).is_err());
assert!(pbkdf2_into::<Sha256>(b"hello world", [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
}
#[test]
fn fmt_fips_pbkdf2() {
assert_eq!(format!("{:?}", FipsPbkdf2::<Sha256>::new()), "FipsPbkdf2<Sha256>");
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
use crate::aes::test_utils::BoundList;
use super::*;
use crate::kdf::{Sha256, Sha384, Sha512};
use crate::kdf::DynSaltSlice as SaltSlice;
use pbkdf2::{pbkdf2_hmac};
macro_rules! against_rc_into {
(
name: $name:ident,
cases: $cases:literal,
max_iters: $max_iters:literal,
algo: $algo:ident
) => {proptest! {
#![proptest_config(ProptestConfig::with_cases($cases))]
#[test]
fn $name(
pwd in any::<BoundList<512>>(),
salt in any::<BoundList<512>>(),
iters in 1..$max_iters,
key_len in 1..1024usize
) {
#[cfg(feature = "allow-non-fips")] {
prop_assume!(!salt.as_slice().is_empty());
}
#[cfg(not(feature = "allow-non-fips"))] {
prop_assume!(salt.len() >= 16);
prop_assume!(key_len >= 14);
}
let mut key_buf = BoundList::<1024>::new_zeroes(key_len);
let mut rc_key_buf = key_buf.create_self();
pbkdf2_into::<$algo>(
pwd.as_slice(),
SaltSlice::new(salt.as_slice()).unwrap(),
Iters::new(iters).unwrap(),
key_buf.as_mut_slice()
).unwrap();
pbkdf2_hmac::<sha2::$algo>(
pwd.as_slice(),
salt.as_slice(),
iters,
rc_key_buf.as_mut_slice()
);
prop_assert_eq!(key_buf.as_slice(), rc_key_buf.as_slice());
}
}};
}
against_rc_into! {
name: rust_crypto_equivalence_sha256,
cases: 5000,
max_iters: 100u32,
algo: Sha256
}
against_rc_into! {
name: rust_crypto_equivalence_sha384,
cases: 5000,
max_iters: 100u32,
algo: Sha384
}
against_rc_into! {
name: rust_crypto_equivalence_sha512,
cases: 5000,
max_iters: 50u32,
algo: Sha512
}
}