use chacha20poly1305::{
aead,
aead::{Aead, NewAead},
};
use generic_array::GenericArray;
use secstr::{SecStr, SecUtf8};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::pinentry::Pinentry;
pub type KdfParams = scrypt::Params;
lazy_static! {
pub static ref KDF_PARAMS_PROD: KdfParams = scrypt::Params::new(15, 8, 1).unwrap();
pub static ref KDF_PARAMS_TEST: KdfParams = scrypt::Params::new(4, 8, 1).unwrap();
}
type Nonce = GenericArray<u8, <chacha20poly1305::ChaCha20Poly1305 as aead::AeadCore>::NonceSize>;
const SALT_SIZE: usize = 24;
type Salt = [u8; SALT_SIZE];
pub trait Crypto: Sized {
type SecretBox;
type Error;
fn seal<K: AsRef<[u8]>>(&self, secret: K) -> Result<Self::SecretBox, Self::Error>;
fn unseal(&self, secret_box: Self::SecretBox) -> Result<SecStr, Self::Error>;
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SecretBox {
nonce: Nonce,
salt: Salt,
sealed: Vec<u8>,
}
#[derive(Debug, Error)]
pub enum SecretBoxError<PinentryError: std::error::Error + 'static> {
#[error("Unable to decrypt secret box using the derived key")]
InvalidKey,
#[error("Error returned from underlying crypto")]
CryptoError,
#[error("Error getting passphrase")]
Pinentry(#[from] PinentryError),
}
#[derive(Clone)]
pub struct Pwhash<P> {
pinentry: P,
params: KdfParams,
}
impl<P> Pwhash<P> {
pub fn new(pinentry: P, params: KdfParams) -> Self {
Self { pinentry, params }
}
}
impl<P> Crypto for Pwhash<P>
where
P: Pinentry,
P::Error: std::error::Error + 'static,
{
type SecretBox = SecretBox;
type Error = SecretBoxError<P::Error>;
fn seal<K: AsRef<[u8]>>(&self, secret: K) -> Result<Self::SecretBox, Self::Error> {
use rand::RngCore;
let passphrase = self
.pinentry
.get_passphrase()
.map_err(SecretBoxError::Pinentry)?;
let mut rng = rand::thread_rng();
let mut nonce = [0; 12];
rng.fill_bytes(&mut nonce);
let mut salt: Salt = [0; SALT_SIZE];
rng.fill_bytes(&mut salt);
let nonce = *Nonce::from_slice(&nonce[..]);
let derived = derive_key(&salt, &passphrase, &self.params);
let key = chacha20poly1305::Key::from_slice(&derived[..]);
let cipher = chacha20poly1305::ChaCha20Poly1305::new(key);
let sealed = cipher
.encrypt(&nonce, secret.as_ref())
.map_err(|_| Self::Error::CryptoError)?;
Ok(SecretBox {
nonce,
salt,
sealed,
})
}
fn unseal(&self, secret_box: Self::SecretBox) -> Result<SecStr, Self::Error> {
let passphrase = self
.pinentry
.get_passphrase()
.map_err(SecretBoxError::Pinentry)?;
let derived = derive_key(&secret_box.salt, &passphrase, &self.params);
let key = chacha20poly1305::Key::from_slice(&derived[..]);
let cipher = chacha20poly1305::ChaCha20Poly1305::new(key);
cipher
.decrypt(&secret_box.nonce, secret_box.sealed.as_slice())
.map_err(|_| SecretBoxError::InvalidKey)
.map(SecStr::new)
}
}
fn derive_key(salt: &Salt, passphrase: &SecUtf8, params: &KdfParams) -> [u8; 32] {
let mut key = [0u8; 32];
scrypt::scrypt(passphrase.unsecure().as_bytes(), salt, params, &mut key)
.expect("Output length must not be zero");
key
}