1pub mod envs;
2mod error;
3
4use std::fs;
5use std::io::Write;
6use std::path::PathBuf;
7
8use argon2::password_hash::SaltString;
9use argon2::{Argon2, Params};
10use rand::Rng;
11use rand::rngs::OsRng;
12use ring::aead::Nonce;
13pub use ring::aead::{Aad, LessSafeKey, NONCE_LEN, UnboundKey};
14
15use crate::envs::FM_TEST_FAST_WEAK_CRYPTO_ENV;
16pub use crate::error::{
17 DecryptError, EncryptError, EncryptedReadError, EncryptedWriteError, EncryptionKeyError,
18};
19
20pub fn get_random_nonce() -> ring::aead::Nonce {
22 Nonce::assume_unique_for_key(OsRng.r#gen())
23}
24
25pub fn encrypt(mut plaintext: Vec<u8>, key: &LessSafeKey) -> Result<Vec<u8>, EncryptError> {
29 let nonce = get_random_nonce();
30 let mut ciphertext: Vec<u8> = nonce.as_ref().to_vec();
32
33 key.seal_in_place_append_tag(nonce, Aad::empty(), &mut plaintext)
34 .map_err(|_| EncryptError)?;
35
36 ciphertext.append(&mut plaintext);
37
38 Ok(ciphertext)
39}
40
41pub fn decrypt<'c>(ciphertext: &'c mut [u8], key: &LessSafeKey) -> Result<&'c [u8], DecryptError> {
45 if ciphertext.len() < NONCE_LEN {
46 return Err(DecryptError::CiphertextTooShort {
47 len: ciphertext.len(),
48 });
49 }
50
51 let (nonce_bytes, encrypted_bytes) = ciphertext.split_at_mut(NONCE_LEN);
52
53 key.open_in_place(
54 Nonce::assume_unique_for_key(nonce_bytes.try_into().expect("nonce size known")),
55 Aad::empty(),
56 encrypted_bytes,
57 )
58 .map_err(|_| DecryptError::Open)?;
59
60 Ok(&encrypted_bytes[..encrypted_bytes.len() - key.algorithm().tag_len()])
61}
62
63pub fn encrypted_write(
66 data: Vec<u8>,
67 key: &LessSafeKey,
68 file: PathBuf,
69) -> Result<(), EncryptedWriteError> {
70 let mut file = fs::File::options()
71 .write(true)
72 .create_new(true)
73 .open(file)?;
74
75 file.write_all(hex::encode(encrypt(data, key)?).as_bytes())?;
76 file.sync_all()?;
77
78 Ok(())
79}
80
81pub fn encrypted_read(key: &LessSafeKey, file: PathBuf) -> Result<Vec<u8>, EncryptedReadError> {
83 let hex = fs::read_to_string(file)?;
84 let mut bytes = hex::decode(hex)?;
85
86 Ok(decrypt(&mut bytes, key)?.to_vec())
87}
88
89pub fn get_encryption_key(password: &str, salt: &str) -> Result<LessSafeKey, EncryptionKeyError> {
108 let mut key = [0u8; ring::digest::SHA256_OUTPUT_LEN];
109
110 argon2()
111 .hash_password_into(password.as_bytes(), salt.as_bytes(), &mut key)
112 .map_err(EncryptionKeyError)?;
113 let key = UnboundKey::new(&ring::aead::CHACHA20_POLY1305, &key)
114 .expect("A SHA-256 output is as long as a ChaCha20-Poly1305 key");
115 Ok(LessSafeKey::new(key))
116}
117
118pub fn random_salt() -> String {
120 SaltString::generate(OsRng).to_string()
121}
122
123fn argon2() -> Argon2<'static> {
126 let mut params = argon2::ParamsBuilder::default();
127 if let Ok("1") = std::env::var(FM_TEST_FAST_WEAK_CRYPTO_ENV).as_deref() {
128 params.m_cost(Params::MIN_M_COST);
129 }
130 Argon2::from(params.build().expect("valid params"))
131}
132
133#[cfg(test)]
134mod tests;