radicle_keystore/
crypto.rs1use chacha20poly1305::{
19 aead,
20 aead::{Aead, NewAead},
21};
22use generic_array::GenericArray;
23use secstr::{SecStr, SecUtf8};
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26
27use crate::pinentry::Pinentry;
28
29pub type KdfParams = scrypt::Params;
31
32lazy_static! {
33 pub static ref KDF_PARAMS_PROD: KdfParams = scrypt::Params::new(15, 8, 1).unwrap();
35
36 pub static ref KDF_PARAMS_TEST: KdfParams = scrypt::Params::new(4, 8, 1).unwrap();
45}
46
47type Nonce = GenericArray<u8, <chacha20poly1305::ChaCha20Poly1305 as aead::AeadCore>::NonceSize>;
49
50const SALT_SIZE: usize = 24;
52
53type Salt = [u8; SALT_SIZE];
55
56pub trait Crypto: Sized {
61 type SecretBox;
62 type Error;
63
64 fn seal<K: AsRef<[u8]>>(&self, secret: K) -> Result<Self::SecretBox, Self::Error>;
65 fn unseal(&self, secret_box: Self::SecretBox) -> Result<SecStr, Self::Error>;
66}
67
68#[derive(Clone, Serialize, Deserialize)]
69pub struct SecretBox {
70 nonce: Nonce,
71 salt: Salt,
72 sealed: Vec<u8>,
73}
74
75#[derive(Debug, Error)]
76pub enum SecretBoxError<PinentryError: std::error::Error + 'static> {
77 #[error("Unable to decrypt secret box using the derived key")]
78 InvalidKey,
79
80 #[error("Error returned from underlying crypto")]
81 CryptoError,
82
83 #[error("Error getting passphrase")]
84 Pinentry(#[from] PinentryError),
85}
86
87#[derive(Clone)]
96pub struct Pwhash<P> {
97 pinentry: P,
98 params: KdfParams,
99}
100
101impl<P> Pwhash<P> {
102 pub fn new(pinentry: P, params: KdfParams) -> Self {
104 Self { pinentry, params }
105 }
106}
107
108impl<P> Crypto for Pwhash<P>
109where
110 P: Pinentry,
111 P::Error: std::error::Error + 'static,
112{
113 type SecretBox = SecretBox;
114 type Error = SecretBoxError<P::Error>;
115
116 fn seal<K: AsRef<[u8]>>(&self, secret: K) -> Result<Self::SecretBox, Self::Error> {
117 use rand::RngCore;
118
119 let passphrase = self
120 .pinentry
121 .get_passphrase()
122 .map_err(SecretBoxError::Pinentry)?;
123
124 let mut rng = rand::thread_rng();
125
126 let mut nonce = [0; 12];
128 rng.fill_bytes(&mut nonce);
129
130 let mut salt: Salt = [0; SALT_SIZE];
132 rng.fill_bytes(&mut salt);
133
134 let nonce = *Nonce::from_slice(&nonce[..]);
136 let derived = derive_key(&salt, &passphrase, &self.params);
137 let key = chacha20poly1305::Key::from_slice(&derived[..]);
138 let cipher = chacha20poly1305::ChaCha20Poly1305::new(key);
139
140 let sealed = cipher
141 .encrypt(&nonce, secret.as_ref())
142 .map_err(|_| Self::Error::CryptoError)?;
143
144 Ok(SecretBox {
145 nonce,
146 salt,
147 sealed,
148 })
149 }
150
151 fn unseal(&self, secret_box: Self::SecretBox) -> Result<SecStr, Self::Error> {
152 let passphrase = self
153 .pinentry
154 .get_passphrase()
155 .map_err(SecretBoxError::Pinentry)?;
156
157 let derived = derive_key(&secret_box.salt, &passphrase, &self.params);
158 let key = chacha20poly1305::Key::from_slice(&derived[..]);
159 let cipher = chacha20poly1305::ChaCha20Poly1305::new(key);
160
161 cipher
162 .decrypt(&secret_box.nonce, secret_box.sealed.as_slice())
163 .map_err(|_| SecretBoxError::InvalidKey)
164 .map(SecStr::new)
165 }
166}
167
168fn derive_key(salt: &Salt, passphrase: &SecUtf8, params: &KdfParams) -> [u8; 32] {
169 let mut key = [0u8; 32];
170 scrypt::scrypt(passphrase.unsecure().as_bytes(), salt, params, &mut key)
171 .expect("Output length must not be zero");
172
173 key
174}