use aes_gcm::aead::{Aead, KeyInit, Payload};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use argon2::{Algorithm, Argon2, Params, Version};
use zeroize::Zeroizing;
use crate::error::{MnemoError, Result};
pub const KEY_LEN: usize = 32;
pub const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
pub const SALT_LEN: usize = 16;
#[derive(Clone, Copy, Debug)]
pub struct KdfParams {
pub m_cost: u32,
pub t_cost: u32,
pub p_cost: u32,
}
impl KdfParams {
pub fn secure() -> Self {
Self { m_cost: 19_456, t_cost: 2, p_cost: 1 }
}
#[doc(hidden)]
pub fn fast() -> Self {
Self { m_cost: 512, t_cost: 1, p_cost: 1 }
}
}
impl Default for KdfParams {
fn default() -> Self {
Self::secure()
}
}
pub fn derive_kek(
passphrase: &[u8],
salt: &[u8],
params: KdfParams,
) -> Result<Zeroizing<[u8; KEY_LEN]>> {
let p = Params::new(params.m_cost, params.t_cost, params.p_cost, Some(KEY_LEN))
.map_err(|e| MnemoError::Kdf(e.to_string()))?;
let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
let mut key = Zeroizing::new([0u8; KEY_LEN]);
argon
.hash_password_into(passphrase, salt, &mut key[..])
.map_err(|e| MnemoError::Kdf(e.to_string()))?;
Ok(key)
}
fn cipher(key: &[u8; KEY_LEN]) -> Aes256Gcm {
Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key))
}
pub fn aead_encrypt(
key: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>> {
cipher(key)
.encrypt(Nonce::from_slice(nonce), Payload { msg: plaintext, aad })
.map_err(|e| MnemoError::Crypto(e.to_string()))
}
pub fn aead_decrypt(
key: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
ciphertext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>> {
cipher(key)
.decrypt(Nonce::from_slice(nonce), Payload { msg: ciphertext, aad })
.map_err(|_| MnemoError::Crypto("authentication failed".into()))
}
pub fn random_dek() -> Zeroizing<[u8; KEY_LEN]> {
use rand::RngCore;
let mut dek = Zeroizing::new([0u8; KEY_LEN]);
rand::thread_rng().fill_bytes(&mut dek[..]);
dek
}
pub fn random_salt() -> [u8; SALT_LEN] {
use rand::RngCore;
let mut salt = [0u8; SALT_LEN];
rand::thread_rng().fill_bytes(&mut salt);
salt
}
pub fn random_nonce() -> [u8; NONCE_LEN] {
use rand::RngCore;
let mut n = [0u8; NONCE_LEN];
rand::thread_rng().fill_bytes(&mut n);
n
}
pub fn page_nonce(page_no: u64, write_counter: u64) -> [u8; NONCE_LEN] {
let mut n = [0u8; NONCE_LEN];
n[0..4].copy_from_slice(&(page_no as u32).to_le_bytes());
n[4..12].copy_from_slice(&write_counter.to_le_bytes());
n
}
pub fn wrap_dek(
kek: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
dek: &[u8; KEY_LEN],
) -> Result<Vec<u8>> {
aead_encrypt(kek, nonce, dek, &[])
}
pub fn unwrap_dek(
kek: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
wrapped: &[u8],
) -> Result<Zeroizing<[u8; KEY_LEN]>> {
let plain = cipher(kek)
.decrypt(
Nonce::from_slice(nonce),
Payload { msg: wrapped, aad: &[] },
)
.map_err(|_| MnemoError::WrongPassphrase)?;
if plain.len() != KEY_LEN {
return Err(MnemoError::WrongPassphrase);
}
let mut dek = Zeroizing::new([0u8; KEY_LEN]);
dek.copy_from_slice(&plain);
Ok(dek)
}