use std::time::Duration;
use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
use crate::crypto::KEY_LEN;
use crate::error::{Error, Result};
use crate::secret::SecretString;
pub enum Credential {
Password(SecretString),
Key([u8; KEY_LEN]),
}
impl Credential {
pub fn password(pwd: impl Into<String>) -> Self {
Credential::Password(SecretString::new(pwd.into()))
}
pub fn key(key: [u8; KEY_LEN]) -> Self {
Credential::Key(key)
}
}
impl Drop for Credential {
fn drop(&mut self) {
if let Credential::Key(key) = self {
key.zeroize();
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub(crate) enum KdfRepr {
Scrypt {
log_n: u8,
r: u32,
p: u32,
},
Argon2id {
m_cost: u32,
t_cost: u32,
p_cost: u32,
},
}
#[derive(Clone)]
pub struct KdfParams(pub(crate) KdfRepr);
impl KdfParams {
pub fn scrypt(log_n: u8, r: u32, p: u32) -> Self {
KdfParams(KdfRepr::Scrypt { log_n, r, p })
}
pub fn interactive() -> Self {
KdfParams::scrypt(15, 8, 1)
}
pub fn sensitive() -> Self {
KdfParams::scrypt(17, 8, 1)
}
#[cfg(feature = "argon2")]
pub fn argon2id(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
KdfParams(KdfRepr::Argon2id {
m_cost,
t_cost,
p_cost,
})
}
}
impl Default for KdfParams {
fn default() -> Self {
KdfParams::interactive()
}
}
#[derive(Clone, Copy, Default)]
pub enum AutoSave {
#[default]
Manual,
OnEveryWrite,
Periodic(Duration),
OnDrop,
}
#[derive(Clone, Copy, Default)]
pub enum LockMode {
#[default]
None,
Shared,
Exclusive,
}
#[derive(Clone, Default)]
pub struct Config {
pub kdf: KdfParams,
pub autosave: AutoSave,
pub lock_mode: LockMode,
pub read_only: bool,
}
pub(crate) fn credential_key(
cred: &Credential,
kdf: &KdfRepr,
salt: &[u8],
) -> Result<[u8; KEY_LEN]> {
match cred {
Credential::Password(pwd) => derive_pwd(pwd.as_bytes(), kdf, salt),
Credential::Key(key) => Ok(*key),
}
}
pub(crate) fn derive_pwd(pwd: &[u8], kdf: &KdfRepr, salt: &[u8]) -> Result<[u8; KEY_LEN]> {
let mut key = [0u8; KEY_LEN];
match kdf {
KdfRepr::Scrypt { log_n, r, p } => {
let params = scrypt::Params::new(*log_n, *r, *p, KEY_LEN)
.map_err(|_| Error::CorruptStore("invalid scrypt parameters".to_string()))?;
scrypt::scrypt(pwd, salt, ¶ms, &mut key).map_err(|_| Error::Crypto)?;
}
KdfRepr::Argon2id {
m_cost,
t_cost,
p_cost,
} => {
#[cfg(feature = "argon2")]
{
use argon2::{Algorithm, Argon2, Params as AParams, Version};
let params = AParams::new(*m_cost, *t_cost, *p_cost, Some(KEY_LEN))
.map_err(|_| Error::CorruptStore("invalid argon2 parameters".to_string()))?;
let a = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
a.hash_password_into(pwd, salt, &mut key)
.map_err(|_| Error::Crypto)?;
}
#[cfg(not(feature = "argon2"))]
{
let _ = (m_cost, t_cost, p_cost);
return Err(Error::CorruptStore(
"store uses argon2 but the 'argon2' feature is disabled".to_string(),
));
}
}
}
Ok(key)
}