Revision control

Copy as Markdown

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file contains code that was copied from the ring crate which is under
// the ISC license, reproduced below:
// Copyright 2015-2017 Brian Smith.
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use crate::{aead, digest, error::*, hmac};
use base64::{engine::general_purpose::STANDARD, Engine};
use nss::aes;
/// AES-256 in CBC mode with HMAC-SHA256 tags and 128 bit nonces.
/// This is a Sync 1.5 specific encryption scheme, do not use for new
/// applications, there are better options out there nowadays.
/// Important note: The HMAC tag verification is done against the
/// base64 representation of the ciphertext.
pub static LEGACY_SYNC_AES_256_CBC_HMAC_SHA256: aead::Algorithm = aead::Algorithm {
key_len: 64, // 32 bytes for the AES key, 32 bytes for the HMAC key.
tag_len: 32,
nonce_len: 128 / 8,
open,
seal,
};
// Warning: This does not run in constant time (which is fine for our usage).
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);
// 1. Tag (HMAC signature) check.
let hmac_key = hmac::VerificationKey::new(&digest::SHA256, hmac_key_bytes);
hmac::verify(
&hmac_key,
STANDARD.encode(ciphertext).as_bytes(),
hmac_signature,
)?;
// 2. Decryption.
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);
// 1. Encryption.
let mut ciphertext = aes_cbc(aes_key, nonce, aad, plaintext, aead::Direction::Sealing)?;
// 2. Tag (HMAC signature) generation.
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]) {
// Always split at 32 since we only do AES 256 w/ HMAC 256 tag.
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() {
// CBC mode does not support AAD.
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::*;
// These are the test vectors used by the sync15 crate, but concatenated
// together rather than split into individual pieces.
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"),
}
}
}