use anyhow::Error;
use crypto::{
aead::{AeadDecryptor, AeadEncryptor},
aes, aes_gcm,
digest::Digest,
scrypt::{scrypt, ScryptParams as Params},
sha3::Sha3,
};
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;
use crate::{
alloc::{vec, Vec},
Cipher, CipherOutput, CipherWithMac, DeriveKey, Eraser, Mac, MacMismatch, ScryptParams, Suite,
UnauthenticatedCipher,
};
#[derive(Debug)]
pub struct Aes128Ctr(());
impl UnauthenticatedCipher for Aes128Ctr {
const KEY_LEN: usize = 16;
const NONCE_LEN: usize = 16;
fn seal_or_open(message: &mut [u8], nonce: &[u8], key: &[u8]) {
let mut output = Zeroizing::new(vec![0; message.len()]);
aes::ctr(aes::KeySize::KeySize128, key, nonce).process(message, &mut *output);
message.copy_from_slice(&output);
}
}
#[derive(Debug)]
pub struct Keccak256(());
impl Mac for Keccak256 {
const KEY_LEN: usize = 16;
const MAC_LEN: usize = 32;
fn digest(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut hasher = Sha3::keccak256();
hasher.input(key);
hasher.input(message);
let mut output = vec![0_u8; Self::MAC_LEN];
hasher.result(&mut output);
output
}
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Scrypt(pub ScryptParams);
impl DeriveKey for Scrypt {
fn salt_len(&self) -> usize {
32
}
fn derive_key(&self, buf: &mut [u8], password: &[u8], salt: &[u8]) -> Result<(), Error> {
let params = Params::new(self.0.log_n, self.0.r, self.0.p);
scrypt(password, salt, ¶ms, buf);
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Aes128Gcm;
impl Cipher for Aes128Gcm {
const KEY_LEN: usize = 16;
const NONCE_LEN: usize = 12;
const MAC_LEN: usize = 16;
fn seal(message: &[u8], nonce: &[u8], key: &[u8]) -> CipherOutput {
let mut cipher = aes_gcm::AesGcm::new(aes::KeySize::KeySize128, key, nonce, &[]);
let mut ciphertext = vec![0_u8; message.len()];
let mut mac = vec![0_u8; Self::MAC_LEN];
cipher.encrypt(message, &mut ciphertext, &mut mac);
CipherOutput { ciphertext, mac }
}
fn open(
output: &mut [u8],
enc: &CipherOutput,
nonce: &[u8],
key: &[u8],
) -> Result<(), MacMismatch> {
let mut cipher = aes_gcm::AesGcm::new(aes::KeySize::KeySize128, key, nonce, &[]);
if cipher.decrypt(&enc.ciphertext, output, &enc.mac) {
Ok(())
} else {
Err(MacMismatch)
}
}
}
#[derive(Debug)]
pub struct RustCrypto(());
impl Suite for RustCrypto {
type Cipher = CipherWithMac<Aes128Ctr, Keccak256>;
type DeriveKey = Scrypt;
fn add_ciphers_and_kdfs(eraser: &mut Eraser) {
eraser
.add_cipher::<Self::Cipher>("aes-128-ctr")
.add_cipher::<Aes128Gcm>("aes-128-gcm")
.add_kdf::<Scrypt>("scrypt");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
erased::{test_kdf_and_cipher_corruption, ErasedPwBox},
test_kdf_and_cipher,
};
#[test]
fn aes_with_keccak_mac() {
use rand::{thread_rng, RngCore};
const MESSAGE: &[u8] = b"battery staple";
type Ci = CipherWithMac<Aes128Ctr, Keccak256>;
let mut rng = thread_rng();
let mut key = vec![0; Ci::KEY_LEN];
rng.fill_bytes(&mut key);
let mut nonce = vec![0; Ci::NONCE_LEN];
rng.fill_bytes(&mut nonce);
let mut sealed = Ci::seal(MESSAGE, &nonce, &key);
let mut plaintext = vec![0; MESSAGE.len()];
Ci::open(&mut plaintext, &sealed, &nonce, &key).unwrap();
assert_eq!(&*plaintext, MESSAGE);
sealed.mac[0] ^= 1;
let mut plaintext = vec![0; MESSAGE.len()];
assert!(Ci::open(&mut plaintext, &sealed, &nonce, &key).is_err());
}
fn light_scrypt() -> Scrypt {
Scrypt(ScryptParams::custom(6, 16))
}
#[test]
fn scrypt_and_aes128ctr() {
test_kdf_and_cipher::<_, CipherWithMac<Aes128Ctr, Keccak256>>(light_scrypt());
}
#[test]
fn scrypt_and_aes128ctr_corruption() {
test_kdf_and_cipher_corruption::<_, CipherWithMac<Aes128Ctr, Keccak256>>(light_scrypt());
}
#[test]
fn scrypt_and_aes128gcm() {
test_kdf_and_cipher::<_, Aes128Gcm>(light_scrypt());
}
#[test]
fn scrypt_and_aes128gcm_corruption() {
test_kdf_and_cipher_corruption::<_, Aes128Gcm>(light_scrypt());
}
#[test]
fn ethstore_functionality() {
use rand::thread_rng;
const PASSWORD: &str = "correct horse battery staple";
const MESSAGE: &[u8] = b"1234567890";
let mut eraser = Eraser::new();
let eraser = eraser.add_suite::<RustCrypto>();
let mut rng = thread_rng();
let pwbox = RustCrypto::build_box(&mut rng)
.kdf(light_scrypt())
.seal(PASSWORD, MESSAGE)
.unwrap();
let erased_box = eraser.erase(&pwbox).unwrap();
let pwbox_copy = eraser.restore(&erased_box).unwrap();
assert_eq!(MESSAGE, &*pwbox_copy.open(PASSWORD).unwrap());
}
#[test]
fn ethstore_compatibility() {
const PASSWORD: &str = "foo";
const MESSAGE_HEX: &str = "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35";
const PWBOX: &str = r#"{
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "e0c41130a323adc1446fc82f724bca2f"
},
"ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 2,
"r" : 8,
"p" : 1,
"salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
},
"mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
}"#;
let mut eraser = Eraser::new();
let eraser = eraser.add_suite::<RustCrypto>();
let message = hex::decode(MESSAGE_HEX).unwrap();
let erased_box: ErasedPwBox = serde_json::from_str(&PWBOX).unwrap();
let pwbox = eraser.restore(&erased_box).unwrap();
assert_eq!(message, &*pwbox.open(PASSWORD).unwrap());
}
}