use aes::Aes128;
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use anyhow::{anyhow, Context, Result};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use cbc::cipher::{block_padding::Pkcs7, BlockModeEncrypt, KeyIvInit};
type Aes128CbcEncryptor = cbc::Encryptor<Aes128>;
const CBC_IV: [u8; 16] = [0x20; 16];
const GCM_NONCE_BYTES: usize = 12;
pub(crate) fn random_bytes(length: usize) -> Result<Vec<u8>> {
let mut buffer = vec![0_u8; length];
getrandom::fill(&mut buffer).map_err(|error| anyhow!("Could not read randomness: {error}"))?;
Ok(buffer)
}
pub(crate) fn encrypt_chromium_value(
plaintext: &[u8],
key: &[u8],
platform: &str,
prefix: Option<&str>,
) -> Result<Vec<u8>> {
if key.is_empty() {
return Err(anyhow!("a target encryption key is required"));
}
let prefix = prefix.unwrap_or("v10").as_bytes();
match platform {
"win32" => {
let nonce_bytes = random_bytes(GCM_NONCE_BYTES)?;
let cipher = Aes256Gcm::new_from_slice(key).context("invalid AES-256-GCM key")?;
let nonce = Nonce::try_from(nonce_bytes.as_slice())
.map_err(|_| anyhow!("AES-GCM nonce is not 12 bytes"))?;
let sealed = cipher
.encrypt(&nonce, plaintext)
.map_err(|_| anyhow!("AES-256-GCM encryption failed"))?;
Ok([prefix, &nonce_bytes, &sealed].concat())
}
"darwin" | "linux" => {
let ciphertext = Aes128CbcEncryptor::new_from_slices(key, &CBC_IV)
.context("invalid AES-128-CBC key")?
.encrypt_padded_vec::<Pkcs7>(plaintext);
Ok([prefix, &ciphertext].concat())
}
_ => Err(anyhow!(
"Chromium value encryption is unsupported on {platform}"
)),
}
}
#[cfg_attr(not(test), allow(dead_code))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct WindowsProfileKey {
pub key: Vec<u8>,
pub encrypted_key_base64: String,
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn create_windows_profile_key(
encrypt_dpapi: &dyn Fn(&[u8]) -> Result<Vec<u8>>,
generate_key: Option<&dyn Fn() -> Result<Vec<u8>>>,
) -> Result<WindowsProfileKey> {
let key = match generate_key {
Some(generate) => generate()?,
None => random_bytes(32)?,
};
let protected = encrypt_dpapi(&key)?;
let tagged = [b"DPAPI".as_slice(), &protected].concat();
Ok(WindowsProfileKey {
key,
encrypted_key_base64: BASE64.encode(tagged),
})
}