use crate::{
checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
KdfAlg, Result,
};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "encryption")]
use {
crate::Cipher,
bcrypt_pbkdf::bcrypt_pbkdf,
rand_core::{CryptoRng, RngCore},
zeroize::Zeroizing,
};
#[cfg(feature = "encryption")]
const DEFAULT_BCRYPT_ROUNDS: u32 = 16;
#[cfg(feature = "encryption")]
const DEFAULT_SALT_SIZE: usize = 16;
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Kdf {
None,
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
Bcrypt {
salt: Vec<u8>,
rounds: u32,
},
}
impl Kdf {
#[cfg(feature = "encryption")]
#[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
pub fn new(algorithm: KdfAlg, mut rng: impl CryptoRng + RngCore) -> Result<Self> {
let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
rng.fill_bytes(&mut salt);
match algorithm {
KdfAlg::None => {
Err(Error::Algorithm)
}
KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
salt,
rounds: DEFAULT_BCRYPT_ROUNDS,
}),
}
}
pub fn algorithm(&self) -> KdfAlg {
match self {
Self::None => KdfAlg::None,
#[cfg(feature = "alloc")]
Self::Bcrypt { .. } => KdfAlg::Bcrypt,
}
}
#[cfg(feature = "encryption")]
#[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
match self {
Kdf::None => Err(Error::Decrypted),
Kdf::Bcrypt { salt, rounds } => {
bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
Ok(())
}
}
}
#[cfg(feature = "encryption")]
#[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
pub fn derive_key_and_iv(
&self,
cipher: Cipher,
password: impl AsRef<[u8]>,
) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
let okm_size = key_size.checked_add(iv_size).ok_or(Error::Length)?;
let mut okm = Zeroizing::new(vec![0u8; okm_size]);
self.derive(password, &mut okm)?;
let iv = okm.split_off(key_size);
Ok((okm, iv))
}
pub fn is_none(&self) -> bool {
self == &Self::None
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn is_bcrypt(&self) -> bool {
matches!(self, Self::Bcrypt { .. })
}
}
impl Default for Kdf {
fn default() -> Self {
Self::None
}
}
impl Decode for Kdf {
fn decode(reader: &mut impl Reader) -> Result<Self> {
match KdfAlg::decode(reader)? {
KdfAlg::None => {
if usize::decode(reader)? == 0 {
Ok(Self::None)
} else {
Err(Error::Algorithm)
}
}
KdfAlg::Bcrypt => {
#[cfg(not(feature = "alloc"))]
return Err(Error::Algorithm);
#[cfg(feature = "alloc")]
reader.read_nested(|reader| {
Ok(Self::Bcrypt {
salt: Vec::decode(reader)?,
rounds: u32::decode(reader)?,
})
})
}
}
}
}
impl Encode for Kdf {
fn encoded_len(&self) -> Result<usize> {
let kdfopts_len = match self {
Self::None => 0,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, .. } => [8, salt.len()].checked_sum()?,
};
[
self.algorithm().encoded_len()?,
4, kdfopts_len,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.algorithm().encode(writer)?;
match self {
Self::None => 0usize.encode(writer),
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, rounds } => {
[8, salt.len()].checked_sum()?.encode(writer)?;
salt.encode(writer)?;
rounds.encode(writer)
}
}
}
}