1use crate::{
2 backend::{BackendType, WrappedKey},
3 error::{HydeError, Result},
4 recovery::{BackupBundle, RecoveryStrategy, RecoveryType},
5};
6
7pub struct PassphraseRecovery;
18
19impl RecoveryStrategy for PassphraseRecovery {
20 fn backup(&self, key: &WrappedKey, secret: Option<&[u8]>) -> Result<BackupBundle> {
21 let passphrase = secret.ok_or_else(|| {
22 HydeError::RecoveryFailed("passphrase is required for PassphraseRecovery".into())
23 })?;
24
25 let mut derived = derive_key_from_passphrase(passphrase)?;
26 let encrypted = aes_gcm_encrypt(&derived.key, &key.blob);
27 zeroize::Zeroize::zeroize(&mut derived.key);
28
29 let encrypted = encrypted?;
30
31 let mut data = Vec::with_capacity(16 + encrypted.len());
33 data.extend_from_slice(&derived.salt);
34 data.extend_from_slice(&encrypted);
35
36 Ok(BackupBundle {
37 recovery_type: RecoveryType::Passphrase,
38 data,
39 user_secret: None,
40 })
41 }
42
43 fn restore(&self, bundle: &BackupBundle, secret: &[u8]) -> Result<WrappedKey> {
44 if bundle.data.len() < 16 + 12 + 16 {
45 return Err(HydeError::RecoveryFailed("backup too short".into()));
46 }
47
48 let salt = &bundle.data[..16];
49 let encrypted = &bundle.data[16..];
50
51 let mut derived = derive_key_with_salt(secret, salt)?;
52 let blob = aes_gcm_decrypt(&derived.key, encrypted)
53 .map_err(|_| HydeError::RecoveryFailed("wrong passphrase or corrupted backup".into()));
54 zeroize::Zeroize::zeroize(&mut derived.key);
55
56 Ok(WrappedKey {
57 blob: blob?,
58 backend: BackendType::Tpm,
59 })
60 }
61
62 fn recovery_type(&self) -> RecoveryType {
63 RecoveryType::Passphrase
64 }
65}
66
67pub(crate) fn aes_gcm_encrypt(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
72 use aes_gcm::{aead::Aead, aead::OsRng, Aes256Gcm, AeadCore, KeyInit};
73
74 let cipher = Aes256Gcm::new_from_slice(key)
75 .map_err(|e| HydeError::Serialization(e.to_string()))?;
76
77 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
78
79 let ciphertext = cipher
80 .encrypt(&nonce, plaintext)
81 .map_err(|_| HydeError::SealMismatch)?;
82
83 let mut output = Vec::with_capacity(12 + ciphertext.len());
84 output.extend_from_slice(&nonce);
85 output.extend_from_slice(&ciphertext);
86 Ok(output)
87}
88
89pub(crate) fn aes_gcm_decrypt(key: &[u8], sealed: &[u8]) -> Result<Vec<u8>> {
90 use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
91
92 if sealed.len() < 12 {
93 return Err(HydeError::InvalidKey);
94 }
95
96 let cipher = Aes256Gcm::new_from_slice(key)
97 .map_err(|e| HydeError::Serialization(e.to_string()))?;
98
99 let nonce = Nonce::from_slice(&sealed[..12]);
100 let ciphertext = &sealed[12..];
101
102 cipher
103 .decrypt(nonce, ciphertext)
104 .map_err(|_| HydeError::SealMismatch)
105}
106
107struct DerivedKey {
112 key: Vec<u8>,
113 salt: [u8; 16],
114}
115
116fn derive_key_from_passphrase(passphrase: &[u8]) -> Result<DerivedKey> {
117 let mut salt = [0u8; 16];
118 getrandom::getrandom(&mut salt)
119 .map_err(|e| HydeError::RecoveryFailed(format!("random salt generation failed: {e}")))?;
120 derive_key_with_salt(passphrase, &salt)
121}
122
123fn derive_key_with_salt(passphrase: &[u8], salt: &[u8]) -> Result<DerivedKey> {
124 use argon2::Argon2;
125
126 let mut key = vec![0u8; 32];
127 let argon2 = Argon2::default();
128 argon2
129 .hash_password_into(passphrase, salt, &mut key)
130 .map_err(|e| HydeError::RecoveryFailed(format!("key derivation failed: {e}")))?;
131
132 let mut salt_arr = [0u8; 16];
133 salt_arr.copy_from_slice(&salt[..16]);
134
135 Ok(DerivedKey {
136 key,
137 salt: salt_arr,
138 })
139}