use forensicnomicon::dpapi::{CHROME_COOKIE_V10, CHROME_COOKIE_V20};
use crate::error::DpapiError;
#[derive(Debug, PartialEq)]
pub enum ChromeCookieEncoding {
Raw,
DpapiBlob(Vec<u8>),
V10 {
nonce: [u8; 12],
ciphertext: Vec<u8>,
},
V20 {
nonce: [u8; 12],
ciphertext: Vec<u8>,
},
}
pub fn detect_chrome_cookie_encoding(data: &[u8]) -> ChromeCookieEncoding {
if data.len() > 15 {
if data.starts_with(CHROME_COOKIE_V20) {
let mut nonce = [0u8; 12];
nonce.copy_from_slice(&data[3..15]);
return ChromeCookieEncoding::V20 {
nonce,
ciphertext: data[15..].to_vec(),
};
}
if data.starts_with(CHROME_COOKIE_V10) {
let mut nonce = [0u8; 12];
nonce.copy_from_slice(&data[3..15]);
return ChromeCookieEncoding::V10 {
nonce,
ciphertext: data[15..].to_vec(),
};
}
}
if data.starts_with(b"DPAPI") {
return ChromeCookieEncoding::DpapiBlob(data[5..].to_vec());
}
ChromeCookieEncoding::Raw
}
pub fn decrypt_v10_cookie(
nonce: &[u8; 12],
ciphertext: &[u8],
key: &[u8; 32],
) -> Result<Vec<u8>, DpapiError> {
#[allow(deprecated)]
use aes_gcm::{
aead::{Aead, Nonce},
Aes256Gcm, KeyInit,
};
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| DpapiError::InvalidKeyLength)?;
#[allow(deprecated)]
let nonce_ga = Nonce::<Aes256Gcm>::from_slice(nonce);
cipher
.decrypt(nonce_ga, ciphertext)
.map_err(|_| DpapiError::DecryptionFailed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_v10_prefix() {
let mut data = vec![0u8; 20];
data[0..3].copy_from_slice(b"v10");
let enc = detect_chrome_cookie_encoding(&data);
assert!(matches!(enc, ChromeCookieEncoding::V10 { .. }));
}
#[test]
fn detect_v20_prefix() {
let mut data = vec![0u8; 20];
data[0..3].copy_from_slice(b"v20");
let enc = detect_chrome_cookie_encoding(&data);
assert!(matches!(enc, ChromeCookieEncoding::V20 { .. }));
}
#[test]
fn detect_dpapi_prefix() {
let data = b"DPAPI\x00\x01\x02\x03".to_vec();
let enc = detect_chrome_cookie_encoding(&data);
assert!(matches!(enc, ChromeCookieEncoding::DpapiBlob(_)));
}
#[test]
fn detect_plaintext_is_raw() {
let enc = detect_chrome_cookie_encoding(b"plaintext_value");
assert_eq!(enc, ChromeCookieEncoding::Raw);
}
#[test]
#[allow(deprecated)]
fn decrypt_v10_roundtrip() {
use aes_gcm::{
aead::{Aead, Nonce},
Aes256Gcm, KeyInit,
};
let key = [0x42u8; 32];
let nonce_bytes = [0x11u8; 12];
let plaintext = b"session_token_value";
let cipher = Aes256Gcm::new_from_slice(&key).unwrap();
#[allow(deprecated)]
let nonce = Nonce::<Aes256Gcm>::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
let recovered = decrypt_v10_cookie(&nonce_bytes, &ciphertext, &key).expect("ok");
assert_eq!(recovered, plaintext);
}
}