use crate::{aead, digest, error::*, hmac};
use base64::{engine::general_purpose::STANDARD, Engine};
use nss::aes;
pub static LEGACY_SYNC_AES_256_CBC_HMAC_SHA256: aead::Algorithm = aead::Algorithm {
key_len: 64, tag_len: 32,
nonce_len: 128 / 8,
open,
seal,
};
pub(crate) fn open(
key: &aead::Key,
nonce: aead::Nonce,
aad: &aead::Aad<'_>,
ciphertext_and_tag: &[u8],
) -> Result<Vec<u8>> {
let ciphertext_len = ciphertext_and_tag
.len()
.checked_sub(key.algorithm().tag_len())
.ok_or(ErrorKind::InternalError)?;
let (ciphertext, hmac_signature) = ciphertext_and_tag.split_at(ciphertext_len);
let (aes_key, hmac_key_bytes) = extract_keys(key);
let hmac_key = hmac::VerificationKey::new(&digest::SHA256, hmac_key_bytes);
hmac::verify(
&hmac_key,
STANDARD.encode(ciphertext).as_bytes(),
hmac_signature,
)?;
aes_cbc(aes_key, nonce, aad, ciphertext, aead::Direction::Opening)
}
pub(crate) fn seal(
key: &aead::Key,
nonce: aead::Nonce,
aad: &aead::Aad<'_>,
plaintext: &[u8],
) -> Result<Vec<u8>> {
let (aes_key, hmac_key_bytes) = extract_keys(key);
let mut ciphertext = aes_cbc(aes_key, nonce, aad, plaintext, aead::Direction::Sealing)?;
let hmac_key = hmac::SigningKey::new(&digest::SHA256, hmac_key_bytes);
let signature = hmac::sign(&hmac_key, STANDARD.encode(&ciphertext).as_bytes())?;
ciphertext.extend(&signature.0.value);
Ok(ciphertext)
}
fn extract_keys(key: &aead::Key) -> (&[u8], &[u8]) {
let (aes_key, hmac_key_bytes) = key.key_value.split_at(32);
(aes_key, hmac_key_bytes)
}
fn aes_cbc(
aes_key: &[u8],
nonce: aead::Nonce,
aad: &aead::Aad<'_>,
data: &[u8],
direction: aead::Direction,
) -> Result<Vec<u8>> {
if !aad.0.is_empty() {
return Err(ErrorKind::InternalError.into());
}
Ok(aes::aes_cbc_crypt(
aes_key,
&nonce.0,
data,
direction.to_nss_operation(),
)?)
}
#[cfg(test)]
mod test {
use super::*;
const IV_B64: &str = "GX8L37AAb2FZJMzIoXlX8w==";
const KEY_B64: &str = "9K/wLdXdw+nrTtXo4ZpECyHFNr4d7aYHqeg3KW9+m6Qwye0R+62At\
NzwWVMtAWazz/Ew+YKV2o+Wr9BBcSPHvQ==";
const CIPHERTEXT_AND_TAG_B64: &str =
"NMsdnRulLwQsVcwxKW9XwaUe7ouJk5Wn80QhbD80l0HEcZGCynh45qIbeYBik0lgcHbKm\
lIxTJNwU+OeqipN+/j7MqhjKOGIlvbpiPQQLC6/ffF2vbzL0nzMUuSyvaQzyGGkSYM2xU\
Ft06aNivoQTvU2GgGmUK6MvadoY38hhW2LCMkoZcNfgCqJ26lO1O0sEO6zHsk3IVz6vsK\
iJ2Hq6VCo7hu123wNegmujHWQSGyf8JeudZjKzfi0OFRRvvm4QAKyBWf0MgrW1F8SFDnV\
fkq8amCB7NhdwhgLWbN+21NitNwWYknoEWe1m6hmGZDgDT32uxzWxCV8QqqrpH/ZggViE\
r9uMgoy4lYaWqP7G5WKvvechc62aqnsNEYhH26A5QgzmlNyvB+KPFvPsYzxDnSCjOoRSL\
x7GG86wT59QZyx5sGKww3rcCNrwNZaRvek3OO4sOAs+SGCuRTjr6XuvA==";
const CLEARTEXT_B64: &str =
"eyJpZCI6IjVxUnNnWFdSSlpYciIsImhpc3RVcmkiOiJmaWxlOi8vL1VzZXJzL2phc29u\
L0xpYnJhcnkvQXBwbGljYXRpb24lMjBTdXBwb3J0L0ZpcmVmb3gvUHJvZmlsZXMva3Nn\
ZDd3cGsuTG9jYWxTeW5jU2VydmVyL3dlYXZlL2xvZ3MvIiwidGl0bGUiOiJJbmRleCBv\
ZiBmaWxlOi8vL1VzZXJzL2phc29uL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9G\
aXJlZm94L1Byb2ZpbGVzL2tzZ2Q3d3BrLkxvY2FsU3luY1NlcnZlci93ZWF2ZS9sb2dz\
LyIsInZpc2l0cyI6W3siZGF0ZSI6MTMxOTE0OTAxMjM3MjQyNSwidHlwZSI6MX1dfQ==";
#[test]
fn test_decrypt() {
let key_bytes = STANDARD.decode(KEY_B64).unwrap();
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let cleartext_bytes = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap();
let expected_cleartext_bytes = STANDARD.decode(CLEARTEXT_B64).unwrap();
assert_eq!(&expected_cleartext_bytes, &cleartext_bytes);
}
#[test]
fn test_encrypt() {
let key_bytes = STANDARD.decode(KEY_B64).unwrap();
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let cleartext = STANDARD.decode(CLEARTEXT_B64).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap();
let expected_ciphertext_bytes = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
assert_eq!(&expected_ciphertext_bytes, &ciphertext_bytes);
}
#[test]
fn test_roundtrip() {
let key_bytes = STANDARD.decode(KEY_B64).unwrap();
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let cleartext = STANDARD.decode(CLEARTEXT_B64).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let roundtriped_cleartext_bytes =
open(&key, nonce, &aead::Aad::empty(), &ciphertext_bytes).unwrap();
assert_eq!(roundtriped_cleartext_bytes, cleartext);
}
#[test]
fn test_decrypt_fails_with_wrong_aes_key() {
let mut key_bytes = STANDARD.decode(KEY_B64).unwrap();
key_bytes[1] = b'X';
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
match err.kind() {
ErrorKind::NSSError(_) | ErrorKind::InternalError => {}
_ => panic!("unexpected error kind"),
}
}
#[test]
fn test_decrypt_fails_with_wrong_hmac_key() {
let mut key_bytes = STANDARD.decode(KEY_B64).unwrap();
key_bytes[60] = b'X';
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
match err.kind() {
ErrorKind::InternalError => {}
_ => panic!("unexpected error kind"),
}
}
#[test]
fn test_decrypt_fails_with_modified_ciphertext() {
let key_bytes = STANDARD.decode(KEY_B64).unwrap();
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let mut ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
ciphertext_and_tag[4] = b'Z';
let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
match err.kind() {
ErrorKind::InternalError => {}
_ => panic!("unexpected error kind"),
}
}
#[test]
fn test_decrypt_fails_with_modified_tag() {
let key_bytes = STANDARD.decode(KEY_B64).unwrap();
let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
let iv = STANDARD.decode(IV_B64).unwrap();
let nonce =
aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
.unwrap();
let mut ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
let end = ciphertext_and_tag.len();
ciphertext_and_tag[end - 4] = b'Z';
let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
match err.kind() {
ErrorKind::InternalError => {}
_ => panic!("unexpected error kind"),
}
}
}