1use std::{
5 fmt::Display,
6 fs::{File, create_dir},
7 io::{BufReader, BufWriter, ErrorKind, Read, Write},
8 path::{Path, PathBuf},
9};
10
11use crate::{
12 shim::crypto::SignatureType,
13 utils::{encoding::from_slice_with_fallback, io::create_new_sensitive_file},
14};
15use ahash::{HashMap, HashMapExt};
16use argon2::{
17 Argon2, ParamsBuilder, PasswordHasher, RECOMMENDED_SALT_LEN, password_hash::SaltString,
18};
19use base64::{Engine, prelude::BASE64_STANDARD};
20use crypto_secretbox::{
21 KeyInit, SecretBox, XSalsa20Poly1305,
22 aead::{Aead, generic_array::GenericArray},
23};
24use rand::RngCore;
25use serde::{Deserialize, Serialize};
26use thiserror::Error;
27use tracing::{error, warn};
28
29use super::errors::Error;
30
31const NONCE_SIZE: usize = SecretBox::<Box<dyn std::any::Any>>::NONCE_SIZE;
32
33pub const KEYSTORE_NAME: &str = "keystore.json";
34pub const ENCRYPTED_KEYSTORE_NAME: &str = "keystore";
35
36pub const FOREST_KEYSTORE_PHRASE_ENV: &str = "FOREST_KEYSTORE_PHRASE";
38
39type SaltByteArray = [u8; RECOMMENDED_SALT_LEN];
40
41#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
44#[derive(Clone, PartialEq, Debug, Eq, Serialize, Deserialize)]
45pub struct KeyInfo {
46 key_type: SignatureType,
47 private_key: Vec<u8>,
49}
50
51#[derive(Clone, PartialEq, Debug, Eq, Serialize, Deserialize)]
52pub struct PersistentKeyInfo {
53 key_type: SignatureType,
54 private_key: String,
55}
56
57impl KeyInfo {
58 pub fn new(key_type: SignatureType, private_key: Vec<u8>) -> Self {
60 KeyInfo {
61 key_type,
62 private_key,
63 }
64 }
65
66 pub fn key_type(&self) -> &SignatureType {
68 &self.key_type
69 }
70
71 pub fn private_key(&self) -> &Vec<u8> {
73 &self.private_key
74 }
75}
76
77#[derive(Clone, PartialEq, Debug, Eq)]
79pub struct KeyStore {
80 key_info: HashMap<String, KeyInfo>,
81 persistence: Option<PersistentKeyStore>,
82 encryption: Option<EncryptedKeyStore>,
83}
84
85pub enum KeyStoreConfig {
86 Memory,
87 Persistent(PathBuf),
88 Encrypted(PathBuf, String),
89}
90
91#[derive(Clone, PartialEq, Debug, Eq)]
93struct PersistentKeyStore {
94 file_path: PathBuf,
95}
96
97#[derive(Clone, PartialEq, Debug, Eq)]
102struct EncryptedKeyStore {
103 salt: SaltByteArray,
104 encryption_key: Vec<u8>,
105}
106
107#[derive(Debug, Error)]
108pub enum EncryptedKeyStoreError {
109 #[error("Error encrypting data")]
111 EncryptionError,
112}
113
114impl KeyStore {
115 pub fn new(config: KeyStoreConfig) -> Result<Self, Error> {
116 match config {
117 KeyStoreConfig::Memory => Ok(Self {
118 key_info: HashMap::new(),
119 persistence: None,
120 encryption: None,
121 }),
122 KeyStoreConfig::Persistent(location) => {
123 let file_path = location.join(KEYSTORE_NAME);
124
125 match File::open(&file_path) {
126 Ok(file) => {
127 let reader = BufReader::new(file);
128
129 let persisted_key_info: HashMap<String, PersistentKeyInfo> =
131 serde_json::from_reader(reader)
132 .inspect_err(|error| {
133 error!(%error, "failed to deserialize keyfile, initializing new keystore at: {}.", file_path.display());
134 })
135 .unwrap_or_default();
136
137 let mut key_info = HashMap::new();
138 for (key, value) in persisted_key_info.iter() {
139 key_info.insert(
140 key.to_string(),
141 KeyInfo {
142 private_key: BASE64_STANDARD
143 .decode(value.private_key.clone())
144 .map_err(|error| Error::Other(error.to_string()))?,
145 key_type: value.key_type,
146 },
147 );
148 }
149
150 Ok(Self {
151 key_info,
152 persistence: Some(PersistentKeyStore { file_path }),
153 encryption: None,
154 })
155 }
156 Err(e) => {
157 if e.kind() == ErrorKind::NotFound {
158 warn!(
159 "Keystore does not exist, initializing new keystore at: {}",
160 file_path.display()
161 );
162 Ok(Self {
163 key_info: HashMap::new(),
164 persistence: Some(PersistentKeyStore { file_path }),
165 encryption: None,
166 })
167 } else {
168 Err(Error::Other(e.to_string()))
169 }
170 }
171 }
172 }
173 KeyStoreConfig::Encrypted(location, passphrase) => {
174 if !location.exists() {
175 create_dir(location.clone())?;
176 }
177
178 let file_path = location.join(Path::new(ENCRYPTED_KEYSTORE_NAME));
179
180 if !file_path.exists() {
181 File::create(file_path.clone())?;
182 }
183
184 match File::open(&file_path) {
185 Ok(file) => {
186 let mut reader = BufReader::new(file);
187 let mut buf = vec![];
188 let read_bytes = reader.read_to_end(&mut buf)?;
189
190 if read_bytes == 0 {
191 warn!(
193 "Keystore does not exist, initializing new keystore at {:?}",
194 file_path
195 );
196
197 let (salt, encryption_key) =
198 EncryptedKeyStore::derive_key(&passphrase, None).map_err(
199 |error| {
200 error!("Failed to create key from passphrase");
201 Error::Other(error.to_string())
202 },
203 )?;
204 Ok(Self {
205 key_info: HashMap::new(),
206 persistence: Some(PersistentKeyStore { file_path }),
207 encryption: Some(EncryptedKeyStore {
208 salt,
209 encryption_key,
210 }),
211 })
212 } else {
213 let data = buf.split_off(RECOMMENDED_SALT_LEN);
216 let mut prev_salt = [0; RECOMMENDED_SALT_LEN];
217 prev_salt.copy_from_slice(&buf);
218 let (salt, encryption_key) =
219 EncryptedKeyStore::derive_key(&passphrase, Some(prev_salt))
220 .map_err(|error| {
221 error!("Failed to create key from passphrase");
222 Error::Other(error.to_string())
223 })?;
224
225 let decrypted_data = EncryptedKeyStore::decrypt(&encryption_key, &data)
226 .map_err(|error| Error::Other(error.to_string()))?;
227
228 let key_info = from_slice_with_fallback(&decrypted_data)
229 .inspect_err(|error| {
230 error!(%error, "Failed to deserialize keyfile, initializing new");
231 })
232 .unwrap_or_default();
233
234 Ok(Self {
235 key_info,
236 persistence: Some(PersistentKeyStore { file_path }),
237 encryption: Some(EncryptedKeyStore {
238 salt,
239 encryption_key,
240 }),
241 })
242 }
243 }
244 Err(_) => {
245 warn!("Encrypted keystore does not exist, initializing new keystore");
246
247 let (salt, encryption_key) =
248 EncryptedKeyStore::derive_key(&passphrase, None).map_err(|error| {
249 error!("Failed to create key from passphrase");
250 Error::Other(error.to_string())
251 })?;
252
253 Ok(Self {
254 key_info: HashMap::new(),
255 persistence: Some(PersistentKeyStore { file_path }),
256 encryption: Some(EncryptedKeyStore {
257 salt,
258 encryption_key,
259 }),
260 })
261 }
262 }
263 }
264 }
265 }
266
267 pub fn flush(&self) -> anyhow::Result<()> {
268 match &self.persistence {
269 Some(persistent_keystore) => {
270 let file = create_new_sensitive_file(&persistent_keystore.file_path)?;
271
272 let mut writer = BufWriter::new(file);
273
274 match &self.encryption {
275 Some(encrypted_keystore) => {
276 let data = serde_ipld_dagcbor::to_vec(&self.key_info).map_err(|e| {
278 Error::Other(format!("failed to serialize and write key info: {e}"))
279 })?;
280
281 let encrypted_data =
282 EncryptedKeyStore::encrypt(&encrypted_keystore.encryption_key, &data)?;
283 let mut salt_vec = encrypted_keystore.salt.to_vec();
284 salt_vec.extend(encrypted_data);
285 writer.write_all(&salt_vec)?;
286
287 Ok(())
288 }
289 None => {
290 let mut key_info: HashMap<String, PersistentKeyInfo> = HashMap::new();
291 for (key, value) in self.key_info.iter() {
292 key_info.insert(
293 key.to_string(),
294 PersistentKeyInfo {
295 private_key: BASE64_STANDARD.encode(value.private_key.clone()),
296 key_type: value.key_type,
297 },
298 );
299 }
300
301 serde_json::to_writer_pretty(writer, &key_info).map_err(|e| {
303 Error::Other(format!("failed to serialize and write key info: {e}"))
304 })?;
305
306 Ok(())
307 }
308 }
309 }
310 None => {
311 Ok(())
313 }
314 }
315 }
316
317 pub fn list(&self) -> Vec<String> {
319 self.key_info.keys().cloned().collect()
320 }
321
322 pub fn get(&self, k: &str) -> Result<KeyInfo, Error> {
324 self.key_info.get(k).cloned().ok_or(Error::KeyInfo)
325 }
326
327 pub fn put(&mut self, key: &str, key_info: KeyInfo) -> Result<(), Error> {
329 if self.key_info.contains_key(key) {
330 return Err(Error::KeyExists);
331 }
332 self.key_info.insert(key.to_string(), key_info);
333
334 if self.persistence.is_some() {
335 self.flush().map_err(|err| Error::Other(err.to_string()))?;
336 }
337
338 Ok(())
339 }
340
341 pub fn remove(&mut self, key: &str) -> anyhow::Result<KeyInfo> {
343 let key_out = self.key_info.remove(key).ok_or(Error::KeyInfo)?;
344
345 if self.persistence.is_some() {
346 self.flush()?;
347 }
348
349 Ok(key_out)
350 }
351}
352
353impl EncryptedKeyStore {
354 fn derive_key(
355 passphrase: &str,
356 prev_salt: Option<SaltByteArray>,
357 ) -> anyhow::Result<(SaltByteArray, Vec<u8>)> {
358 let salt = match prev_salt {
359 Some(prev_salt) => prev_salt,
360 None => {
361 let mut salt = [0; RECOMMENDED_SALT_LEN];
362 crate::utils::rand::forest_os_rng().fill_bytes(&mut salt);
363 salt
364 }
365 };
366
367 let mut param_builder = ParamsBuilder::new();
368 const CRYPTO_PWHASH_ARGON2ID_MEMLIMIT_INTERACTIVE: u32 = 67108864;
371 const CRYPTO_PWHASH_ARGON2ID_OPSLIMIT_INTERACTIVE: u32 = 2;
374 param_builder
375 .m_cost(CRYPTO_PWHASH_ARGON2ID_MEMLIMIT_INTERACTIVE / 1024)
376 .t_cost(CRYPTO_PWHASH_ARGON2ID_OPSLIMIT_INTERACTIVE);
377 let hasher = Argon2::new(
381 argon2::Algorithm::Argon2id,
382 argon2::Version::V0x13,
383 param_builder.build().map_err(map_err_to_anyhow)?,
384 );
385 let salt_string = SaltString::encode_b64(&salt).map_err(map_err_to_anyhow)?;
386 let pw_hash = hasher
387 .hash_password(passphrase.as_bytes(), &salt_string)
388 .map_err(map_err_to_anyhow)?;
389 if let Some(hash) = pw_hash.hash {
390 Ok((salt, hash.as_bytes().to_vec()))
391 } else {
392 anyhow::bail!(EncryptedKeyStoreError::EncryptionError)
393 }
394 }
395
396 fn encrypt(encryption_key: &[u8], msg: &[u8]) -> anyhow::Result<Vec<u8>> {
397 let mut nonce = [0; NONCE_SIZE];
398 crate::utils::rand::forest_os_rng().fill_bytes(&mut nonce);
399 let nonce = GenericArray::from_slice(&nonce);
400 let key = GenericArray::from_slice(encryption_key);
401 let cipher = XSalsa20Poly1305::new(key);
402 let mut ciphertext = cipher.encrypt(nonce, msg).map_err(map_err_to_anyhow)?;
403 ciphertext.extend(nonce.iter());
404 Ok(ciphertext)
405 }
406
407 #[allow(clippy::indexing_slicing)]
408 fn decrypt(encryption_key: &[u8], msg: &[u8]) -> anyhow::Result<Vec<u8>> {
409 anyhow::ensure!(msg.len() > NONCE_SIZE);
410 let cyphertext_len = msg.len() - NONCE_SIZE;
411 let ciphertext = &msg[..cyphertext_len];
412 let nonce = GenericArray::from_slice(&msg[cyphertext_len..]);
413 let key = GenericArray::from_slice(encryption_key);
414 let cipher = XSalsa20Poly1305::new(key);
415 let plaintext = cipher
416 .decrypt(nonce, ciphertext)
417 .map_err(map_err_to_anyhow)?;
418 Ok(plaintext)
419 }
420}
421
422fn map_err_to_anyhow<T: Display>(e: T) -> anyhow::Error {
423 anyhow::Error::msg(e.to_string())
424}
425
426#[cfg(test)]
427mod test {
428 use base64::{Engine, prelude::BASE64_STANDARD};
429
430 use super::*;
431 use crate::key_management::wallet;
432
433 const PASSPHRASE: &str = "foobarbaz";
434
435 #[test]
436 fn test_generate_key() {
437 let (salt, encryption_key) = EncryptedKeyStore::derive_key(PASSPHRASE, None).unwrap();
438 let (second_salt, second_key) =
439 EncryptedKeyStore::derive_key(PASSPHRASE, Some(salt)).unwrap();
440
441 assert_eq!(
442 encryption_key, second_key,
443 "Derived key must be deterministic"
444 );
445 assert_eq!(salt, second_salt, "Salts must match");
446 }
447
448 #[test]
449 fn test_encrypt_message() {
450 let (_, private_key) = EncryptedKeyStore::derive_key(PASSPHRASE, None).unwrap();
451 let message = "foo is coming";
452 let ciphertext = EncryptedKeyStore::encrypt(&private_key, message.as_bytes()).unwrap();
453 let second_pass = EncryptedKeyStore::encrypt(&private_key, message.as_bytes()).unwrap();
454 assert_ne!(
455 ciphertext, second_pass,
456 "Ciphertexts use secure initialization vectors"
457 );
458 }
459
460 #[test]
461 fn test_decrypt_message() {
462 let (_, private_key) = EncryptedKeyStore::derive_key(PASSPHRASE, None).unwrap();
463 let message = "foo is coming";
464 let ciphertext = EncryptedKeyStore::encrypt(&private_key, message.as_bytes()).unwrap();
465 let plaintext = EncryptedKeyStore::decrypt(&private_key, &ciphertext).unwrap();
466 assert_eq!(plaintext, message.as_bytes());
467 }
468
469 #[test]
470 fn test_read_old_encrypted_keystore() {
471 let dir: PathBuf = "src/key_management/tests/keystore_encrypted_old".into();
472 assert!(dir.exists());
473 let ks = KeyStore::new(KeyStoreConfig::Encrypted(dir, PASSPHRASE.to_string())).unwrap();
474 assert!(ks.persistence.is_some());
475 }
476
477 #[test]
478 fn test_read_write_encrypted_keystore() {
479 let keystore_location = tempfile::tempdir().unwrap().keep();
480 let ks = KeyStore::new(KeyStoreConfig::Encrypted(
481 keystore_location.clone(),
482 PASSPHRASE.to_string(),
483 ))
484 .unwrap();
485 ks.flush().unwrap();
486
487 let ks_read = KeyStore::new(KeyStoreConfig::Encrypted(
488 keystore_location,
489 PASSPHRASE.to_string(),
490 ))
491 .unwrap();
492
493 assert_eq!(ks, ks_read);
494 }
495
496 #[test]
497 fn test_read_write_keystore() {
498 let keystore_location = tempfile::tempdir().unwrap().keep();
499 let mut ks = KeyStore::new(KeyStoreConfig::Persistent(keystore_location.clone())).unwrap();
500
501 let key = wallet::generate_key(SignatureType::Bls).unwrap();
502
503 let addr = format!("wallet-{}", key.address);
504 ks.put(&addr, key.key_info).unwrap();
505 ks.flush().unwrap();
506
507 let default = ks.get(&addr).unwrap();
508
509 let keystore_file = keystore_location.join(KEYSTORE_NAME);
511 let reader = BufReader::new(File::open(keystore_file).unwrap());
512 let persisted_keystore: HashMap<String, PersistentKeyInfo> =
513 serde_json::from_reader(reader).unwrap();
514
515 let default_key_info = persisted_keystore.get(&addr).unwrap();
516 let actual = BASE64_STANDARD
517 .decode(default_key_info.private_key.clone())
518 .unwrap();
519
520 assert_eq!(
521 default.private_key, actual,
522 "persisted key matches key from key store"
523 );
524
525 let ks_read = KeyStore::new(KeyStoreConfig::Persistent(keystore_location)).unwrap();
527 assert_eq!(ks, ks_read);
528 }
529}