1use aes_gcm::aead::{Aead, KeyInit};
2use aes_gcm::{Aes256Gcm, Key, Nonce};
3use argon2::Argon2;
4use argon2::password_hash::rand_core::OsRng;
5use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
6use rand::RngCore;
7use serde::{Deserialize, Serialize};
8
9use crate::error::{Error, Result};
10
11pub trait SecretHasher: Send + Sync {
13 fn hash_secret(&self, secret: &[u8]) -> Result<SecretHash>;
15 fn verify_secret(&self, secret: &[u8], hash: &SecretHash) -> Result<bool>;
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct SecretHash(String);
22
23impl SecretHash {
24 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28}
29
30impl From<String> for SecretHash {
31 fn from(value: String) -> Self {
32 Self(value)
33 }
34}
35
36impl AsRef<str> for SecretHash {
37 fn as_ref(&self) -> &str {
38 self.as_str()
39 }
40}
41
42pub struct Argon2SecretHasher {
44 argon2: Argon2<'static>,
45}
46
47impl Argon2SecretHasher {
48 pub fn new() -> Self {
50 Self {
51 argon2: Argon2::default(),
52 }
53 }
54}
55
56impl Default for Argon2SecretHasher {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62impl SecretHasher for Argon2SecretHasher {
63 fn hash_secret(&self, secret: &[u8]) -> Result<SecretHash> {
64 let salt = SaltString::generate(&mut OsRng);
65 let hash = self
66 .argon2
67 .hash_password(secret, &salt)
68 .map_err(|err| Error::Crypto(err.to_string()))?;
69 Ok(SecretHash(hash.to_string()))
70 }
71
72 fn verify_secret(&self, secret: &[u8], hash: &SecretHash) -> Result<bool> {
73 let parsed =
74 PasswordHash::new(hash.as_str()).map_err(|err| Error::Crypto(err.to_string()))?;
75 Ok(self.argon2.verify_password(secret, &parsed).is_ok())
76 }
77}
78
79pub trait EnvelopeEncryptor: Send + Sync {
81 fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedBlob>;
83 fn decrypt(&self, blob: &EncryptedBlob) -> Result<Vec<u8>>;
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
89pub struct EncryptedBlob {
90 pub nonce: [u8; 12],
92 pub ciphertext: Vec<u8>,
94}
95
96pub struct AesGcmEncryptor {
98 cipher: Aes256Gcm,
99}
100
101impl AesGcmEncryptor {
102 pub fn new(key_bytes: [u8; 32]) -> Self {
104 Self {
105 cipher: Aes256Gcm::new(&Key::<Aes256Gcm>::from_slice(&key_bytes)),
106 }
107 }
108
109 pub fn from_slice(slice: &[u8]) -> Result<Self> {
111 let key: [u8; 32] = slice
112 .try_into()
113 .map_err(|_| Error::Crypto("encryption key must be 32 bytes".into()))?;
114 Ok(Self::new(key))
115 }
116}
117
118impl EnvelopeEncryptor for AesGcmEncryptor {
119 fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedBlob> {
120 let mut nonce_bytes = [0u8; 12];
121 rand::thread_rng().fill_bytes(&mut nonce_bytes);
122 let nonce = Nonce::from_slice(&nonce_bytes);
123 let ciphertext = self
124 .cipher
125 .encrypt(nonce, plaintext)
126 .map_err(|err| Error::Crypto(err.to_string()))?;
127 Ok(EncryptedBlob {
128 nonce: nonce_bytes,
129 ciphertext,
130 })
131 }
132
133 fn decrypt(&self, blob: &EncryptedBlob) -> Result<Vec<u8>> {
134 let nonce = Nonce::from_slice(&blob.nonce);
135 self.cipher
136 .decrypt(nonce, blob.ciphertext.as_ref())
137 .map_err(|err| Error::Crypto(err.to_string()))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn argon2_round_trips() {
147 let hasher = Argon2SecretHasher::default();
148 let hash = hasher.hash_secret(b"secret").unwrap();
149 assert!(hasher.verify_secret(b"secret", &hash).unwrap());
150 assert!(!hasher.verify_secret(b"wrong", &hash).unwrap());
151 }
152
153 #[test]
154 fn aes_gcm_encrypt_decrypt() {
155 let encryptor = AesGcmEncryptor::new([42u8; 32]);
156 let blob = encryptor.encrypt(b"payload").unwrap();
157 let plaintext = encryptor.decrypt(&blob).unwrap();
158 assert_eq!(plaintext, b"payload");
159 }
160}