use chrono::prelude::*;
use rand::{rngs::OsRng, Rng};
use secstr::SecBox;
use serde::{Deserialize, Serialize};
use tiny_keccak::{Hasher, Kmac};
use crate::support::user_config::b64;
const MASTER_SIZE: usize = 32;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum Algorithm {
Argon2i_V13_M4096_T10_L1_Kmac256,
}
impl Default for Algorithm {
fn default() -> Self {
Algorithm::Argon2i_V13_M4096_T10_L1_Kmac256
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MasterKeyConfig {
#[serde(with = "b64")]
password_hash: Vec<u8>,
#[serde(with = "b64")]
salt: Vec<u8>,
algorithm: Algorithm,
#[serde(with = "b64")]
master_key_xor: Vec<u8>,
#[serde(default)]
pub last_changed: Option<DateTime<FixedOffset>>,
}
pub struct MasterKey {
master_key: SecBox<[u8; MASTER_SIZE]>,
}
impl MasterKey {
pub fn new() -> Self {
let mut key = SecBox::new(Box::new([0u8; MASTER_SIZE]));
for i in 0..MASTER_SIZE {
key.unsecure_mut()[i] = OsRng.gen();
}
MasterKey { master_key: key }
}
pub fn pem_passphrase(&self, key_name: &str) -> String {
let mut k = Kmac::v256(self.master_key.unsecure(), b"pem");
k.update(key_name.as_bytes());
let mut hash = [0u8; 32];
k.finalize(&mut hash);
base64::encode(&hash)
}
pub fn make_config(
&self,
password: &[u8],
) -> Result<MasterKeyConfig, argon2::Error> {
let salt: [u8; 32] = OsRng.gen();
let (password_hash, derived_key) =
hash_password(password, &salt, Algorithm::default())?;
let mut master_key_xor = vec![0u8; MASTER_SIZE];
for i in 0..MASTER_SIZE {
master_key_xor[i] = self.master_key.unsecure()[i] ^ derived_key[i];
}
Ok(MasterKeyConfig {
password_hash: password_hash[..].to_owned(),
salt: salt[..].to_owned(),
algorithm: Algorithm::default(),
master_key_xor,
last_changed: None,
})
}
pub fn from_config(
conf: &MasterKeyConfig,
password: &[u8],
) -> Option<Self> {
let (password_hash, derived_key) =
hash_password(password, &conf.salt, conf.algorithm).ok()?;
if password_hash.len() != conf.password_hash.len()
|| !openssl::memcmp::eq(&password_hash, &conf.password_hash)
|| MASTER_SIZE != conf.master_key_xor.len()
{
return None;
}
let mut key = SecBox::new(Box::new([0u8; MASTER_SIZE]));
for i in 0..MASTER_SIZE {
key.unsecure_mut()[i] = derived_key[i] ^ conf.master_key_xor[i];
}
Some(MasterKey { master_key: key })
}
}
fn hash_password(
password: &[u8],
salt: &[u8],
algorithm: Algorithm,
) -> Result<([u8; 32], [u8; 32]), argon2::Error> {
let raw_hash = match algorithm {
Algorithm::Argon2i_V13_M4096_T10_L1_Kmac256 => argon2::hash_raw(
password,
salt,
&argon2::Config {
hash_length: 32,
lanes: 1,
mem_cost: 4096,
thread_mode: argon2::ThreadMode::Sequential,
time_cost: 10,
variant: argon2::Variant::Argon2i,
version: argon2::Version::Version13,
..argon2::Config::default()
},
)?,
};
let mut password_hash = [0u8; 32];
{
let mut k = Kmac::v256(salt, b"check");
k.update(&raw_hash);
k.finalize(&mut password_hash);
}
let mut derived_key = [0u8; 32];
{
let mut k = Kmac::v256(salt, b"master");
k.update(&raw_hash);
k.finalize(&mut derived_key);
}
Ok((password_hash, derived_key))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn rederive_master_key() {
let orig = MasterKey::new();
let config = orig.make_config(b"hunter2").unwrap();
let derived = MasterKey::from_config(&config, b"hunter2").unwrap();
assert_eq!(orig.master_key, derived.master_key);
}
#[test]
fn derive_fails_for_bad_password() {
let config = MasterKey::new().make_config(b"hunter2").unwrap();
assert!(MasterKey::from_config(&config, b"hunter3").is_none());
}
#[test]
fn config_generation_makes_distinct_hashes() {
let key = MasterKey::new();
let config1 = key.make_config(b"hunter2").unwrap();
let config2 = key.make_config(b"hunter2").unwrap();
assert_ne!(config1.password_hash, config2.password_hash);
}
}