/* 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. /// More details here: https://mozilla-services.readthedocs.io/en/latest/sync/storageformat5.html#record-encryption 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> { 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> { 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> { 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::*; use nss::ensure_initialized; // 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() { ensure_initialized(); 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() { ensure_initialized(); 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() { ensure_initialized(); 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() { ensure_initialized(); 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() { ensure_initialized(); 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() { ensure_initialized(); 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() { ensure_initialized(); 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"), } } }