browser_commander/browser/migration/
firefox_nss.rs1use aes::Aes256;
30use anyhow::{anyhow, Context, Result};
31use base64::engine::general_purpose::STANDARD as BASE64;
32use base64::Engine;
33use cbc::cipher::block_padding::{NoPadding, Pkcs7};
34use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
35use des::TdesEde3;
36use hmac::{Hmac, KeyInit, Mac};
37use pbkdf2::pbkdf2_hmac;
38use rusqlite::{Connection, OptionalExtension};
39use sha1::{Digest, Sha1};
40use sha2::Sha256;
41
42use super::firefox_der::{decode_der_element, der_children, oid_to_string, DerElement};
43
44type Aes256CbcDecryptor = cbc::Decryptor<Aes256>;
45type TdesCbcDecryptor = cbc::Decryptor<TdesEde3>;
46type HmacSha1 = Hmac<Sha1>;
47
48const OID_PBES2: &str = "1.2.840.113549.1.5.13";
49const OID_PBE_SHA1_3DES: &str = "1.2.840.113549.1.12.5.1.3";
50
51const PASSWORD_CHECK: &[u8] = b"password-check\x02\x02";
52
53#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
55#[error("Firefox primary password is set")]
56pub struct PrimaryPasswordError;
57
58fn child(children: &[DerElement], index: usize) -> Result<&DerElement> {
59 children
60 .get(index)
61 .ok_or_else(|| anyhow!("NSS DER structure is missing a field"))
62}
63
64fn integer_value(element: &DerElement) -> Result<u64> {
65 element.content.iter().try_fold(0_u64, |value, byte| {
66 value
67 .checked_mul(256)
68 .map(|value| value + u64::from(*byte))
69 .ok_or_else(|| anyhow!("NSS DER integer is too large"))
70 })
71}
72
73fn password_hash(global_salt: &[u8], primary_password: &[u8]) -> Vec<u8> {
81 Sha1::new()
82 .chain_update(global_salt)
83 .chain_update(primary_password)
84 .finalize()
85 .to_vec()
86}
87
88fn decrypt_pbes2(
90 algorithm_params: &DerElement,
91 ciphertext: &[u8],
92 global_salt: &[u8],
93 primary_password: &[u8],
94) -> Result<Vec<u8>> {
95 let scheme = der_children(algorithm_params)?;
96 let kdf = der_children(child(&scheme, 0)?)?;
97 let encryption_scheme = der_children(child(&scheme, 1)?)?;
98 let params = der_children(child(&kdf, 1)?)?;
99 let entry_salt = &child(¶ms, 0)?.content;
100 let iterations = u32::try_from(integer_value(child(¶ms, 1)?)?)
101 .context("NSS PBKDF2 iteration count is too large")?;
102 let key_length = usize::try_from(integer_value(child(¶ms, 2)?)?)
103 .ok()
104 .filter(|length| *length <= 1024)
105 .ok_or_else(|| anyhow!("NSS PBKDF2 key length is invalid"))?;
106 let iv_bytes = &child(&encryption_scheme, 1)?.content;
107
108 let hash = password_hash(global_salt, primary_password);
109 let mut key = vec![0_u8; key_length];
110 pbkdf2_hmac::<Sha256>(&hash, entry_salt, iterations, &mut key);
111 let iv = [&[0x04_u8, 0x0e][..], iv_bytes].concat();
112 Aes256CbcDecryptor::new_from_slices(&key, &iv)
113 .map_err(|_| anyhow!("NSS AES-256-CBC key or IV has the wrong length"))?
114 .decrypt_padded_vec::<Pkcs7>(ciphertext)
115 .map_err(|_| anyhow!("NSS AES-256-CBC padding is invalid"))
116}
117
118fn pkcs7_unpad(mut buffer: Vec<u8>) -> Vec<u8> {
121 if let Some(&pad) = buffer.last() {
122 let pad = usize::from(pad);
123 if pad > 0 && pad <= buffer.len() {
124 buffer.truncate(buffer.len() - pad);
125 }
126 }
127 buffer
128}
129
130fn hmac_sha1(key: &[u8], parts: &[&[u8]]) -> Result<Vec<u8>> {
131 let mut mac = HmacSha1::new_from_slice(key).map_err(|_| anyhow!("invalid HMAC key"))?;
132 for part in parts {
133 mac.update(part);
134 }
135 Ok(mac.finalize().into_bytes().to_vec())
136}
137
138fn decrypt_3des_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
139 let plaintext = TdesCbcDecryptor::new_from_slices(key, iv)
142 .map_err(|_| anyhow!("3DES key or IV has the wrong length"))?
143 .decrypt_padded_vec::<NoPadding>(ciphertext)
144 .map_err(|_| anyhow!("3DES ciphertext is not a whole number of blocks"))?;
145 Ok(pkcs7_unpad(plaintext))
146}
147
148fn decrypt_pbe_sha1_3des(
150 algorithm_params: &DerElement,
151 ciphertext: &[u8],
152 global_salt: &[u8],
153 primary_password: &[u8],
154) -> Result<Vec<u8>> {
155 let params = der_children(algorithm_params)?;
156 let entry_salt = &child(¶ms, 0)?.content;
157
158 let hp = password_hash(global_salt, primary_password);
162 let mut pes = entry_salt.clone();
163 pes.resize(20, 0);
164 let chp = Sha1::new()
165 .chain_update(&hp)
166 .chain_update(entry_salt)
167 .finalize()
168 .to_vec();
169 let k1 = hmac_sha1(&chp, &[&pes, entry_salt])?;
170 let tk = hmac_sha1(&chp, &[&pes])?;
171 let k2 = hmac_sha1(&chp, &[&tk, entry_salt])?;
172 let derived = [k1, k2].concat();
173 let key = &derived[..24];
174 let iv = &derived[derived.len() - 8..];
175 decrypt_3des_cbc(key, iv, ciphertext)
176}
177
178fn decrypt_nss_blob(blob: &[u8], global_salt: &[u8], primary_password: &[u8]) -> Result<Vec<u8>> {
179 let top = decode_der_element(blob, 0)?;
180 let top_children = der_children(&top)?;
181 let algorithm = der_children(child(&top_children, 0)?)?;
182 let ciphertext = &child(&top_children, 1)?.content;
183 let oid = oid_to_string(&child(&algorithm, 0)?.content);
184 let params = child(&algorithm, 1)?;
185 match oid.as_str() {
186 OID_PBES2 => decrypt_pbes2(params, ciphertext, global_salt, primary_password),
187 OID_PBE_SHA1_3DES => {
188 decrypt_pbe_sha1_3des(params, ciphertext, global_salt, primary_password)
189 }
190 _ => Err(anyhow!("Unsupported NSS key algorithm {oid}")),
191 }
192}
193
194pub(crate) fn recover_firefox_key_from_database(
197 database: &Connection,
198 primary_password: &[u8],
199) -> Result<Vec<u8>> {
200 let meta: Option<(Vec<u8>, Vec<u8>)> = database
201 .query_row(
202 "SELECT item1, item2 FROM metadata WHERE id = 'password'",
203 [],
204 |row| Ok((row.get(0)?, row.get(1)?)),
205 )
206 .optional()?;
207 let (global_salt, check_blob) =
208 meta.ok_or_else(|| anyhow!("key4.db has no password metadata"))?;
209 let check = decrypt_nss_blob(&check_blob, &global_salt, primary_password)
212 .map_err(|_| anyhow!(PrimaryPasswordError))?;
213 if !check.starts_with(PASSWORD_CHECK) {
214 return Err(anyhow!(PrimaryPasswordError));
215 }
216 let private: Option<Vec<u8>> = database
217 .query_row("SELECT a11, a102 FROM nssPrivate", [], |row| row.get(0))
218 .optional()?;
219 let private = private.ok_or_else(|| anyhow!("key4.db has no nssPrivate key"))?;
220 let mut decrypted = decrypt_nss_blob(&private, &global_salt, primary_password)?;
221 decrypted.truncate(24);
222 Ok(decrypted)
223}
224
225pub(crate) fn decrypt_firefox_field(base64_value: &str, key: &[u8]) -> Result<String> {
227 let blob = BASE64
228 .decode(base64_value)
229 .context("NSS login field is not base64")?;
230 let top = decode_der_element(&blob, 0)?;
231 let top_children = der_children(&top)?;
232 let algorithm = der_children(child(&top_children, 1)?)?;
233 let iv = &child(&algorithm, 1)?.content;
234 let ciphertext = &child(&top_children, 2)?.content;
235 let plaintext = decrypt_3des_cbc(key, iv, ciphertext)?;
236 String::from_utf8(plaintext).context("decrypted login field is not UTF-8")
237}