1use forensicnomicon::dpapi::{CHROME_COOKIE_V10, CHROME_COOKIE_V20};
2
3use crate::error::DpapiError;
4
5#[derive(Debug, PartialEq)]
7pub enum ChromeCookieEncoding {
8 Raw,
10 DpapiBlob(Vec<u8>),
12 V10 {
14 nonce: [u8; 12],
15 ciphertext: Vec<u8>,
16 },
17 V20 {
19 nonce: [u8; 12],
20 ciphertext: Vec<u8>,
21 },
22}
23
24pub fn detect_chrome_cookie_encoding(data: &[u8]) -> ChromeCookieEncoding {
26 if data.len() > 15 {
28 if data.starts_with(CHROME_COOKIE_V20) {
29 let mut nonce = [0u8; 12];
30 nonce.copy_from_slice(&data[3..15]);
31 return ChromeCookieEncoding::V20 {
32 nonce,
33 ciphertext: data[15..].to_vec(),
34 };
35 }
36 if data.starts_with(CHROME_COOKIE_V10) {
37 let mut nonce = [0u8; 12];
38 nonce.copy_from_slice(&data[3..15]);
39 return ChromeCookieEncoding::V10 {
40 nonce,
41 ciphertext: data[15..].to_vec(),
42 };
43 }
44 }
45 if data.starts_with(b"DPAPI") {
46 return ChromeCookieEncoding::DpapiBlob(data[5..].to_vec());
47 }
48 ChromeCookieEncoding::Raw
49}
50
51pub fn decrypt_v10_cookie(
54 nonce: &[u8; 12],
55 ciphertext: &[u8],
56 key: &[u8; 32],
57) -> Result<Vec<u8>, DpapiError> {
58 #[allow(deprecated)]
59 use aes_gcm::{
61 aead::{Aead, Nonce},
62 Aes256Gcm, KeyInit,
63 };
64 let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| DpapiError::InvalidKeyLength)?;
65 #[allow(deprecated)]
66 let nonce_ga = Nonce::<Aes256Gcm>::from_slice(nonce);
67 cipher
68 .decrypt(nonce_ga, ciphertext)
69 .map_err(|_| DpapiError::DecryptionFailed)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn detect_v10_prefix() {
78 let mut data = vec![0u8; 20];
79 data[0..3].copy_from_slice(b"v10");
80 let enc = detect_chrome_cookie_encoding(&data);
81 assert!(matches!(enc, ChromeCookieEncoding::V10 { .. }));
82 }
83
84 #[test]
85 fn detect_v20_prefix() {
86 let mut data = vec![0u8; 20];
87 data[0..3].copy_from_slice(b"v20");
88 let enc = detect_chrome_cookie_encoding(&data);
89 assert!(matches!(enc, ChromeCookieEncoding::V20 { .. }));
90 }
91
92 #[test]
93 fn detect_dpapi_prefix() {
94 let data = b"DPAPI\x00\x01\x02\x03".to_vec();
95 let enc = detect_chrome_cookie_encoding(&data);
96 assert!(matches!(enc, ChromeCookieEncoding::DpapiBlob(_)));
97 }
98
99 #[test]
100 fn detect_plaintext_is_raw() {
101 let enc = detect_chrome_cookie_encoding(b"plaintext_value");
102 assert_eq!(enc, ChromeCookieEncoding::Raw);
103 }
104
105 #[test]
106 #[allow(deprecated)]
107 fn decrypt_v10_roundtrip() {
108 use aes_gcm::{
109 aead::{Aead, Nonce},
110 Aes256Gcm, KeyInit,
111 };
112 let key = [0x42u8; 32];
113 let nonce_bytes = [0x11u8; 12];
114 let plaintext = b"session_token_value";
115 let cipher = Aes256Gcm::new_from_slice(&key).unwrap();
116 #[allow(deprecated)]
117 let nonce = Nonce::<Aes256Gcm>::from_slice(&nonce_bytes);
118 let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
119 let recovered = decrypt_v10_cookie(&nonce_bytes, &ciphertext, &key).expect("ok");
120 assert_eq!(recovered, plaintext);
121 }
122}