use crate::algorithms::argon2id;
use crate::algorithms::bcrypt::BcryptParams;
use crate::algorithms::pbkdf2::Pbkdf2Params;
use crate::algorithms::scrypt::ScryptParams;
use crate::backend::Backend;
use crate::error::Error;
use argon2::Params as Argon2Params;
#[cfg(feature = "pepper")]
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum PrimaryAlgorithm {
Argon2id,
Bcrypt,
Scrypt,
Pbkdf2,
}
#[derive(Clone, Debug)]
pub struct Policy {
pub(crate) primary: PrimaryAlgorithm,
pub(crate) backend: Backend,
pub(crate) argon2: Argon2Params,
pub(crate) bcrypt: BcryptParams,
pub(crate) scrypt: ScryptParams,
pub(crate) pbkdf2: Pbkdf2Params,
#[cfg(feature = "pepper")]
pub(crate) pepper: Option<Arc<dyn hsh_kms::Pepper>>,
}
impl Policy {
#[must_use]
pub fn owasp_minimum_2025() -> Self {
Self {
primary: PrimaryAlgorithm::Argon2id,
backend: Backend::Native,
argon2: argon2id::owasp_minimum_2025(),
bcrypt: BcryptParams::new(10),
scrypt: ScryptParams::default(),
pbkdf2: Pbkdf2Params::owasp_minimum_2025(),
#[cfg(feature = "pepper")]
pepper: None,
}
}
#[must_use]
pub fn rfc9106_first_recommended() -> Self {
Self {
primary: PrimaryAlgorithm::Argon2id,
backend: Backend::Native,
argon2: argon2id::rfc9106_first_recommended(),
bcrypt: BcryptParams::new(10),
scrypt: ScryptParams::default(),
pbkdf2: Pbkdf2Params::owasp_minimum_2025(),
#[cfg(feature = "pepper")]
pepper: None,
}
}
#[must_use]
pub fn fips_140_pbkdf2() -> Self {
Self {
primary: PrimaryAlgorithm::Pbkdf2,
backend: Backend::Fips140Required,
argon2: argon2id::owasp_minimum_2025(),
bcrypt: BcryptParams::new(10),
scrypt: ScryptParams::default(),
pbkdf2: Pbkdf2Params::owasp_minimum_2025(),
#[cfg(feature = "pepper")]
pepper: None,
}
}
#[must_use]
pub const fn primary(&self) -> PrimaryAlgorithm {
self.primary
}
#[must_use]
pub const fn backend(&self) -> Backend {
self.backend
}
#[must_use]
pub const fn argon2_params(&self) -> &Argon2Params {
&self.argon2
}
#[must_use]
pub const fn bcrypt_params(&self) -> BcryptParams {
self.bcrypt
}
#[must_use]
pub const fn scrypt_params(&self) -> ScryptParams {
self.scrypt
}
#[must_use]
pub const fn pbkdf2_params(&self) -> Pbkdf2Params {
self.pbkdf2
}
#[must_use]
pub fn has_pepper(&self) -> bool {
#[cfg(feature = "pepper")]
{
self.pepper.is_some()
}
#[cfg(not(feature = "pepper"))]
{
false
}
}
#[cfg(feature = "pepper")]
#[must_use]
pub fn with_pepper(
mut self,
provider: impl hsh_kms::Pepper + 'static,
) -> Self {
self.pepper = Some(Arc::new(provider));
self
}
#[cfg(feature = "pepper")]
#[must_use]
pub fn with_pepper_arc(
mut self,
provider: Arc<dyn hsh_kms::Pepper>,
) -> Self {
self.pepper = Some(provider);
self
}
#[must_use]
pub fn to_builder(&self) -> PolicyBuilder {
PolicyBuilder::from_preset(self)
}
pub(crate) fn argon2_satisfies(
&self,
stored: &Argon2Params,
) -> bool {
stored.m_cost() >= self.argon2.m_cost()
&& stored.t_cost() >= self.argon2.t_cost()
&& stored.p_cost() >= self.argon2.p_cost()
&& stored.output_len() == self.argon2.output_len()
}
pub(crate) fn bcrypt_satisfies(&self, stored_cost: u32) -> bool {
stored_cost >= self.bcrypt.cost
}
pub(crate) fn scrypt_satisfies(
&self,
stored: &ScryptParams,
) -> bool {
stored.log_n >= self.scrypt.log_n
&& stored.r >= self.scrypt.r
&& stored.p >= self.scrypt.p
&& stored.dk_len == self.scrypt.dk_len
}
pub(crate) fn pbkdf2_satisfies(
&self,
stored_iters: u32,
stored_dk_len: usize,
) -> bool {
stored_iters >= self.pbkdf2.iterations
&& stored_dk_len == self.pbkdf2.dk_len
}
}
impl Default for Policy {
fn default() -> Self {
Self::owasp_minimum_2025()
}
}
#[derive(Clone, Debug, Default)]
pub struct PolicyBuilder {
primary: Option<PrimaryAlgorithm>,
backend: Option<Backend>,
argon2: Option<Argon2Params>,
bcrypt: Option<BcryptParams>,
scrypt: Option<ScryptParams>,
pbkdf2: Option<Pbkdf2Params>,
#[cfg(feature = "pepper")]
pepper: Option<Arc<dyn hsh_kms::Pepper>>,
}
impl PolicyBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_preset(policy: &Policy) -> Self {
Self {
primary: Some(policy.primary),
backend: Some(policy.backend),
argon2: Some(policy.argon2.clone()),
bcrypt: Some(policy.bcrypt),
scrypt: Some(policy.scrypt),
pbkdf2: Some(policy.pbkdf2),
#[cfg(feature = "pepper")]
pepper: policy.pepper.clone(),
}
}
#[must_use]
pub fn primary(mut self, primary: PrimaryAlgorithm) -> Self {
self.primary = Some(primary);
self
}
#[must_use]
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = Some(backend);
self
}
#[must_use]
pub fn argon2(mut self, params: Argon2Params) -> Self {
self.argon2 = Some(params);
self
}
#[must_use]
pub fn bcrypt(mut self, params: BcryptParams) -> Self {
self.bcrypt = Some(params);
self
}
#[must_use]
pub fn scrypt(mut self, params: ScryptParams) -> Self {
self.scrypt = Some(params);
self
}
#[must_use]
pub fn pbkdf2(mut self, params: Pbkdf2Params) -> Self {
self.pbkdf2 = Some(params);
self
}
#[cfg(feature = "pepper")]
#[must_use]
pub fn pepper(
mut self,
provider: impl hsh_kms::Pepper + 'static,
) -> Self {
self.pepper = Some(Arc::new(provider));
self
}
#[cfg(feature = "pepper")]
#[must_use]
pub fn pepper_arc(
mut self,
provider: Arc<dyn hsh_kms::Pepper>,
) -> Self {
self.pepper = Some(provider);
self
}
#[cfg(feature = "pepper")]
#[must_use]
pub fn no_pepper(mut self) -> Self {
self.pepper = None;
self
}
pub fn build(self) -> Result<Policy, Error> {
Ok(Policy {
primary: self.primary.ok_or(Error::InvalidPolicy(
"primary algorithm required".into(),
))?,
backend: self.backend.unwrap_or_default(),
argon2: self
.argon2
.unwrap_or_else(argon2id::owasp_minimum_2025),
bcrypt: self
.bcrypt
.unwrap_or_else(|| BcryptParams::new(10)),
scrypt: self.scrypt.unwrap_or_default(),
pbkdf2: self.pbkdf2.unwrap_or_default(),
#[cfg(feature = "pepper")]
pepper: self.pepper,
})
}
}