diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/rc_crypto/src | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rc_crypto/src')
-rw-r--r-- | third_party/rust/rc_crypto/src/aead.rs | 317 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/aead/aes_cbc.rs | 265 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/aead/aes_gcm.rs | 146 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/agreement.rs | 414 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/constant_time.rs | 45 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/digest.rs | 75 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/ece_crypto.rs | 437 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/error.rs | 20 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/hawk_crypto.rs | 157 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/hkdf.rs | 127 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/hmac.rs | 199 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/lib.rs | 68 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/pbkdf2.rs | 196 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/rand.rs | 70 | ||||
-rw-r--r-- | third_party/rust/rc_crypto/src/signature.rs | 111 |
15 files changed, 2647 insertions, 0 deletions
diff --git a/third_party/rust/rc_crypto/src/aead.rs b/third_party/rust/rc_crypto/src/aead.rs new file mode 100644 index 0000000000..d0e60e5f61 --- /dev/null +++ b/third_party/rust/rc_crypto/src/aead.rs @@ -0,0 +1,317 @@ +/* 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. + +mod aes_cbc; +mod aes_gcm; + +use crate::error::*; +pub use aes_cbc::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256; +pub use aes_gcm::{AES_128_GCM, AES_256_GCM}; +use nss::aes; + +pub fn open( + key: &OpeningKey, + nonce: Nonce, + aad: Aad<'_>, + ciphertext_and_tag: &[u8], +) -> Result<Vec<u8>> { + (key.algorithm().open)(&key.key, nonce, &aad, ciphertext_and_tag) +} + +pub fn seal(key: &SealingKey, nonce: Nonce, aad: Aad<'_>, plaintext: &[u8]) -> Result<Vec<u8>> { + (key.algorithm().seal)(&key.key, nonce, &aad, plaintext) +} + +/// The additional authenticated data (AAD) for an opening or sealing +/// operation. This data is authenticated but is **not** encrypted. +/// This is a type-safe wrapper around the raw bytes designed to encourage +/// correct use of the API. +#[repr(transparent)] +pub struct Aad<'a>(&'a [u8]); + +impl<'a> Aad<'a> { + /// Construct the `Aad` by borrowing a contiguous sequence of bytes. + #[inline] + pub fn from(aad: &'a [u8]) -> Self { + Aad(aad) + } +} + +impl Aad<'static> { + /// Construct an empty `Aad`. + pub fn empty() -> Self { + Self::from(&[]) + } +} + +/// The nonce for an opening or sealing operation. +/// This is a type-safe wrapper around the raw bytes designed to encourage +/// correct use of the API. +pub struct Nonce(Vec<u8>); + +impl Nonce { + #[inline] + pub fn try_assume_unique_for_key(algorithm: &'static Algorithm, value: &[u8]) -> Result<Self> { + if value.len() != algorithm.nonce_len() { + return Err(ErrorKind::InternalError.into()); + } + Ok(Self(value.to_vec())) + } +} + +pub struct OpeningKey { + key: Key, +} + +impl OpeningKey { + /// Create a new opening key. + /// + /// `key_bytes` must be exactly `algorithm.key_len` bytes long. + #[inline] + pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> { + Ok(Self { + key: Key::new(algorithm, key_bytes)?, + }) + } + + /// The key's AEAD algorithm. + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.key.algorithm() + } +} + +pub struct SealingKey { + key: Key, +} + +impl SealingKey { + /// Create a new sealing key. + /// + /// `key_bytes` must be exactly `algorithm.key_len` bytes long. + #[inline] + pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> { + Ok(Self { + key: Key::new(algorithm, key_bytes)?, + }) + } + + /// The key's AEAD algorithm. + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.key.algorithm() + } +} + +/// `OpeningKey` and `SealingKey` are type-safety wrappers around `Key`. +pub(crate) struct Key { + key_value: Vec<u8>, + algorithm: &'static Algorithm, +} + +impl Key { + fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> { + if key_bytes.len() != algorithm.key_len() { + return Err(ErrorKind::InternalError.into()); + } + Ok(Key { + key_value: key_bytes.to_vec(), + algorithm, + }) + } + + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.algorithm + } +} + +// An AEAD algorithm. +#[allow(clippy::type_complexity)] +pub struct Algorithm { + tag_len: usize, + key_len: usize, + nonce_len: usize, + open: fn(key: &Key, nonce: Nonce, aad: &Aad<'_>, ciphertext_and_tag: &[u8]) -> Result<Vec<u8>>, + seal: fn(key: &Key, nonce: Nonce, aad: &Aad<'_>, plaintext: &[u8]) -> Result<Vec<u8>>, +} + +impl Algorithm { + /// The length of the key. + #[inline] + pub const fn key_len(&self) -> usize { + self.key_len + } + + /// The length of a tag. + #[inline] + pub const fn tag_len(&self) -> usize { + self.tag_len + } + + /// The length of the nonces. + #[inline] + pub const fn nonce_len(&self) -> usize { + self.nonce_len + } +} + +pub(crate) enum Direction { + Opening, + Sealing, +} + +impl Direction { + fn to_nss_operation(&self) -> aes::Operation { + match self { + Direction::Opening => aes::Operation::Decrypt, + Direction::Sealing => aes::Operation::Encrypt, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static ALL_ALGORITHMS: &[&Algorithm] = &[ + &LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, + &AES_128_GCM, + &AES_256_GCM, + ]; + static ALL_ALGORITHMS_THAT_SUPPORT_AAD: &[&Algorithm] = &[&AES_128_GCM, &AES_256_GCM]; + + #[test] + fn test_roundtrip() { + for algorithm in ALL_ALGORITHMS { + let mut cleartext_bytes = vec![0u8; 127]; + crate::rand::fill(&mut cleartext_bytes).unwrap(); + + let mut key_bytes = vec![0u8; algorithm.key_len()]; + crate::rand::fill(&mut key_bytes).unwrap(); + + let nonce_bytes = vec![0u8; algorithm.nonce_len()]; + + let key = SealingKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &cleartext_bytes).unwrap(); + + let key = OpeningKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let roundtriped_cleartext_bytes = + open(&key, nonce, Aad::empty(), &ciphertext_bytes).unwrap(); + assert_eq!(roundtriped_cleartext_bytes, cleartext_bytes); + } + } + + #[test] + fn test_cant_open_with_mismatched_key() { + let mut key_bytes_1 = vec![0u8; AES_256_GCM.key_len()]; + crate::rand::fill(&mut key_bytes_1).unwrap(); + + let mut key_bytes_2 = vec![0u8; AES_128_GCM.key_len()]; + crate::rand::fill(&mut key_bytes_2).unwrap(); + + let nonce_bytes = vec![0u8; AES_256_GCM.nonce_len()]; + + let key = SealingKey::new(&AES_256_GCM, &key_bytes_1).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(&AES_256_GCM, &nonce_bytes).unwrap(); + let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &[0u8; 0]).unwrap(); + + let key = OpeningKey::new(&AES_128_GCM, &key_bytes_2).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(&AES_128_GCM, &nonce_bytes).unwrap(); + let result = open(&key, nonce, Aad::empty(), &ciphertext_bytes); + assert!(result.is_err()); + } + + #[test] + fn test_cant_open_modified_ciphertext() { + for algorithm in ALL_ALGORITHMS { + let mut key_bytes = vec![0u8; algorithm.key_len()]; + crate::rand::fill(&mut key_bytes).unwrap(); + + let nonce_bytes = vec![0u8; algorithm.nonce_len()]; + + let key = SealingKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &[0u8; 0]).unwrap(); + + for i in 0..ciphertext_bytes.len() { + let mut modified_ciphertext = ciphertext_bytes.clone(); + modified_ciphertext[i] = modified_ciphertext[i].wrapping_add(1); + + let key = OpeningKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let result = open(&key, nonce, Aad::empty(), &modified_ciphertext); + assert!(result.is_err()); + } + } + } + + #[test] + fn test_cant_open_with_incorrect_associated_data() { + for algorithm in ALL_ALGORITHMS_THAT_SUPPORT_AAD { + let mut key_bytes = vec![0u8; algorithm.key_len()]; + crate::rand::fill(&mut key_bytes).unwrap(); + + let nonce_bytes = vec![0u8; algorithm.nonce_len()]; + + let key = SealingKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let ciphertext_bytes = seal(&key, nonce, Aad::from(&[1, 2, 3]), &[0u8; 0]).unwrap(); + + let key = OpeningKey::new(algorithm, &key_bytes).unwrap(); + let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap(); + let result = open(&key, nonce, Aad::empty(), &ciphertext_bytes); + assert!(result.is_err()); + + let nonce = Nonce::try_assume_unique_for_key(&AES_256_GCM, &nonce_bytes).unwrap(); + let result = open(&key, nonce, Aad::from(&[2, 3, 4]), &ciphertext_bytes); + assert!(result.is_err()); + } + } + + #[test] + fn test_cant_use_incorrectly_sized_key() { + for algorithm in ALL_ALGORITHMS { + let key_bytes = vec![0u8; algorithm.key_len() - 1]; + let result = Key::new(&algorithm, &key_bytes); + assert!(result.is_err()); + + let key_bytes = vec![0u8; algorithm.key_len() + 1]; + let result = Key::new(&algorithm, &key_bytes); + assert!(result.is_err()); + } + } + + #[test] + fn test_cant_use_incorrectly_sized_nonce() { + for algorithm in ALL_ALGORITHMS { + let nonce_bytes = vec![0u8; algorithm.nonce_len() - 1]; + let result = Nonce::try_assume_unique_for_key(&algorithm, &nonce_bytes); + assert!(result.is_err()); + + let nonce_bytes = vec![0u8; algorithm.nonce_len() + 1]; + let result = Nonce::try_assume_unique_for_key(&algorithm, &nonce_bytes); + assert!(result.is_err()); + } + } +} diff --git a/third_party/rust/rc_crypto/src/aead/aes_cbc.rs b/third_party/rust/rc_crypto/src/aead/aes_cbc.rs new file mode 100644 index 0000000000..93e3d8a1d7 --- /dev/null +++ b/third_party/rust/rc_crypto/src/aead/aes_cbc.rs @@ -0,0 +1,265 @@ +/* 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 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<Vec<u8>> { + let ciphertext_len = ciphertext_and_tag + .len() + .checked_sub(key.algorithm().tag_len()) + .ok_or_else(|| 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, + base64::encode(ciphertext).as_bytes(), + hmac_signature, + )?; + // 2. Decryption. + Ok(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, base64::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 = base64::decode(KEY_B64).unwrap(); + let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap(); + let ciphertext_and_tag = base64::decode(&CIPHERTEXT_AND_TAG_B64).unwrap(); + + let iv = base64::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 = base64::decode(&CLEARTEXT_B64).unwrap(); + assert_eq!(&expected_cleartext_bytes, &cleartext_bytes); + } + + #[test] + fn test_encrypt() { + let key_bytes = base64::decode(KEY_B64).unwrap(); + let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap(); + let cleartext = base64::decode(&CLEARTEXT_B64).unwrap(); + + let iv = base64::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 = base64::decode(&CIPHERTEXT_AND_TAG_B64).unwrap(); + assert_eq!(&expected_ciphertext_bytes, &ciphertext_bytes); + } + + #[test] + fn test_roundtrip() { + let key_bytes = base64::decode(KEY_B64).unwrap(); + let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap(); + let cleartext = base64::decode(&CLEARTEXT_B64).unwrap(); + + let iv = base64::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 = base64::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 = base64::decode(&CIPHERTEXT_AND_TAG_B64).unwrap(); + let iv = base64::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 = base64::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 = base64::decode(&CIPHERTEXT_AND_TAG_B64).unwrap(); + let iv = base64::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 = base64::decode(KEY_B64).unwrap(); + let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap(); + let iv = base64::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 = base64::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 = base64::decode(KEY_B64).unwrap(); + let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap(); + let iv = base64::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 = base64::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"), + } + } +} diff --git a/third_party/rust/rc_crypto/src/aead/aes_gcm.rs b/third_party/rust/rc_crypto/src/aead/aes_gcm.rs new file mode 100644 index 0000000000..8bcdbe6803 --- /dev/null +++ b/third_party/rust/rc_crypto/src/aead/aes_gcm.rs @@ -0,0 +1,146 @@ +/* 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, error::*}; +use nss::aes; + +/// AES-128 in GCM mode with 128-bit tags and 96 bit nonces. +pub static AES_128_GCM: aead::Algorithm = aead::Algorithm { + key_len: 16, + tag_len: 16, + nonce_len: 96 / 8, + open, + seal, +}; + +/// AES-256 in GCM mode with 128-bit tags and 96 bit nonces. +pub static AES_256_GCM: aead::Algorithm = aead::Algorithm { + key_len: 32, + tag_len: 16, + nonce_len: 96 / 8, + open, + seal, +}; + +pub(crate) fn open( + key: &aead::Key, + nonce: aead::Nonce, + aad: &aead::Aad<'_>, + ciphertext_and_tag: &[u8], +) -> Result<Vec<u8>> { + aes_gcm( + key, + nonce, + aad, + ciphertext_and_tag, + aead::Direction::Opening, + ) +} + +pub(crate) fn seal( + key: &aead::Key, + nonce: aead::Nonce, + aad: &aead::Aad<'_>, + plaintext: &[u8], +) -> Result<Vec<u8>> { + aes_gcm(key, nonce, aad, plaintext, aead::Direction::Sealing) +} + +fn aes_gcm( + key: &aead::Key, + nonce: aead::Nonce, + aad: &aead::Aad<'_>, + data: &[u8], + direction: aead::Direction, +) -> Result<Vec<u8>> { + Ok(aes::aes_gcm_crypt( + &key.key_value, + &nonce.0, + &aad.0, + data, + direction.to_nss_operation(), + )?) +} + +#[cfg(test)] +mod test { + use super::*; + + // Test vector from the AES-GCM spec. + const NONCE_HEX: &str = "cafebabefacedbaddecaf888"; + const KEY_HEX: &str = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308"; + const AAD_HEX: &str = "feedfacedeadbeeffeedfacedeadbeefabaddad2"; + const TAG_HEX: &str = "76fc6ece0f4e1768cddf8853bb2d551b"; + const CIPHERTEXT_HEX: &str = + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662"; + const CLEARTEXT_HEX: &str = + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"; + + #[test] + fn test_decrypt() { + let key_bytes = hex::decode(KEY_HEX).unwrap(); + let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap(); + let mut ciphertext_and_tag = hex::decode(&CIPHERTEXT_HEX).unwrap(); + let tag = hex::decode(&TAG_HEX).unwrap(); + ciphertext_and_tag.extend(&tag); + + let iv = hex::decode(NONCE_HEX).unwrap(); + let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap(); + let aad_bytes = hex::decode(AAD_HEX).unwrap(); + let aad = aead::Aad::from(&aad_bytes); + let cleartext_bytes = open(&key, nonce, &aad, &ciphertext_and_tag).unwrap(); + let encoded_cleartext = hex::encode(cleartext_bytes); + assert_eq!(&CLEARTEXT_HEX, &encoded_cleartext); + } + + #[test] + fn test_encrypt() { + let key_bytes = hex::decode(KEY_HEX).unwrap(); + let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap(); + let cleartext = hex::decode(&CLEARTEXT_HEX).unwrap(); + + let iv = hex::decode(NONCE_HEX).unwrap(); + let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap(); + let aad_bytes = hex::decode(AAD_HEX).unwrap(); + let aad = aead::Aad::from(&aad_bytes); + let ciphertext_bytes = seal(&key, nonce, &aad, &cleartext).unwrap(); + + let expected_tag = hex::decode(&TAG_HEX).unwrap(); + let mut expected_ciphertext = hex::decode(&CIPHERTEXT_HEX).unwrap(); + expected_ciphertext.extend(&expected_tag); + assert_eq!(&expected_ciphertext, &ciphertext_bytes); + } + + #[test] + fn test_roundtrip() { + let key_bytes = hex::decode(KEY_HEX).unwrap(); + let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap(); + let cleartext = hex::decode(&CLEARTEXT_HEX).unwrap(); + + let iv = hex::decode(NONCE_HEX).unwrap(); + let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap(); + let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap(); + let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap(); + let roundtriped_cleartext_bytes = + open(&key, nonce, &aead::Aad::empty(), &ciphertext_bytes).unwrap(); + assert_eq!(roundtriped_cleartext_bytes, cleartext); + } +} diff --git a/third_party/rust/rc_crypto/src/agreement.rs b/third_party/rust/rc_crypto/src/agreement.rs new file mode 100644 index 0000000000..ae609b9c9c --- /dev/null +++ b/third_party/rust/rc_crypto/src/agreement.rs @@ -0,0 +1,414 @@ +/* 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::error::*; +use core::marker::PhantomData; +pub use ec::{Curve, EcKey}; +use nss::{ec, ecdh}; + +pub type EphemeralKeyPair = KeyPair<Ephemeral>; + +/// A key agreement algorithm. +#[derive(PartialEq)] +pub struct Algorithm { + pub(crate) curve_id: ec::Curve, +} + +pub static ECDH_P256: Algorithm = Algorithm { + curve_id: ec::Curve::P256, +}; + +pub static ECDH_P384: Algorithm = Algorithm { + curve_id: ec::Curve::P384, +}; + +/// How many times the key may be used. +pub trait Lifetime {} + +/// The key may be used at most once. +pub struct Ephemeral {} +impl Lifetime for Ephemeral {} + +/// The key may be used more than once. +pub struct Static {} +impl Lifetime for Static {} + +/// A key pair for key agreement. +pub struct KeyPair<U: Lifetime> { + private_key: PrivateKey<U>, + public_key: PublicKey, +} + +impl<U: Lifetime> KeyPair<U> { + /// Generate a new key pair for the given algorithm. + pub fn generate(alg: &'static Algorithm) -> Result<Self> { + let (prv_key, pub_key) = ec::generate_keypair(alg.curve_id)?; + Ok(Self { + private_key: PrivateKey { + alg, + wrapped: prv_key, + usage: PhantomData, + }, + public_key: PublicKey { + alg, + wrapped: pub_key, + }, + }) + } + + pub fn from_private_key(private_key: PrivateKey<U>) -> Result<Self> { + let public_key = private_key + .compute_public_key() + .map_err(|_| ErrorKind::InternalError)?; + Ok(Self { + private_key, + public_key, + }) + } + + /// The private key. + pub fn private_key(&self) -> &PrivateKey<U> { + &self.private_key + } + + /// The public key. + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + /// Split the key pair apart. + pub fn split(self) -> (PrivateKey<U>, PublicKey) { + (self.private_key, self.public_key) + } +} + +impl KeyPair<Static> { + pub fn from(private_key: PrivateKey<Static>) -> Result<Self> { + Self::from_private_key(private_key) + } +} + +/// A public key for key agreement. +pub struct PublicKey { + wrapped: ec::PublicKey, + alg: &'static Algorithm, +} + +impl PublicKey { + #[inline] + pub fn to_bytes(&self) -> Result<Vec<u8>> { + Ok(self.wrapped.to_bytes()?) + } + + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.alg + } +} + +/// An unparsed public key for key agreement. +pub struct UnparsedPublicKey<'a> { + alg: &'static Algorithm, + bytes: &'a [u8], +} + +impl<'a> UnparsedPublicKey<'a> { + pub fn new(algorithm: &'static Algorithm, bytes: &'a [u8]) -> Self { + Self { + alg: algorithm, + bytes, + } + } + + pub fn algorithm(&self) -> &'static Algorithm { + self.alg + } + + pub fn bytes(&self) -> &'a [u8] { + &self.bytes + } +} + +/// A private key for key agreement. +pub struct PrivateKey<U: Lifetime> { + wrapped: ec::PrivateKey, + alg: &'static Algorithm, + usage: PhantomData<U>, +} + +impl<U: Lifetime> PrivateKey<U> { + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.alg + } + + pub fn compute_public_key(&self) -> Result<PublicKey> { + let pub_key = self.wrapped.convert_to_public_key()?; + Ok(PublicKey { + wrapped: pub_key, + alg: self.alg, + }) + } + + /// Ephemeral agreement. + /// This consumes `self`, ensuring that the private key can + /// only be used for a single agreement operation. + pub fn agree(self, peer_public_key: &UnparsedPublicKey<'_>) -> Result<InputKeyMaterial> { + agree_(&self.wrapped, self.alg, peer_public_key) + } +} + +impl PrivateKey<Static> { + /// Static agreement. + /// This borrows `self`, allowing the private key to + /// be used for a multiple agreement operations. + pub fn agree_static( + &self, + peer_public_key: &UnparsedPublicKey<'_>, + ) -> Result<InputKeyMaterial> { + agree_(&self.wrapped, self.alg, peer_public_key) + } + + pub fn import(ec_key: &EcKey) -> Result<Self> { + // XXX: we should just let ec::PrivateKey own alg. + let alg = match ec_key.curve() { + Curve::P256 => &ECDH_P256, + Curve::P384 => &ECDH_P384, + }; + let private_key = ec::PrivateKey::import(ec_key)?; + Ok(Self { + wrapped: private_key, + alg, + usage: PhantomData, + }) + } + + pub fn export(&self) -> Result<EcKey> { + Ok(self.wrapped.export()?) + } + + /// The whole point of having `Ephemeral` and `Static` lifetimes is to use the type + /// system to avoid re-using the same ephemeral key. However for tests we might need + /// to create a "static" ephemeral key. + pub fn _tests_only_dangerously_convert_to_ephemeral(self) -> PrivateKey<Ephemeral> { + PrivateKey::<Ephemeral> { + wrapped: self.wrapped, + alg: self.alg, + usage: PhantomData, + } + } +} + +fn agree_( + my_private_key: &ec::PrivateKey, + my_alg: &Algorithm, + peer_public_key: &UnparsedPublicKey<'_>, +) -> Result<InputKeyMaterial> { + let alg = &my_alg; + if peer_public_key.algorithm() != *alg { + return Err(ErrorKind::InternalError.into()); + } + let pub_key = ec::PublicKey::from_bytes(my_private_key.curve(), peer_public_key.bytes())?; + let value = ecdh::ecdh_agreement(my_private_key, &pub_key)?; + Ok(InputKeyMaterial { value }) +} + +/// The result of a key agreement operation, to be fed into a KDF. +#[must_use] +pub struct InputKeyMaterial { + value: Vec<u8>, +} + +impl InputKeyMaterial { + /// Calls `kdf` with the raw key material and then returns what `kdf` + /// returns, consuming `Self` so that the key material can only be used + /// once. + pub fn derive<F, R>(self, kdf: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + kdf(&self.value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Test vectors copied from: + // https://chromium.googlesource.com/chromium/src/+/56f1232/components/test/data/webcrypto/ecdh.json#5 + + const PUB_KEY_1_B64: &str = + "BLunVoWkR67xRdAohVblFBWn1Oosb3kH_baxw1yfIYFfthSm4LIY35vDD-5LE454eB7TShn919DVVGZ_7tWdjTE"; + const PRIV_KEY_1_JWK_D: &str = "CQ8uF_-zB1NftLO6ytwKM3Cnuol64PQw5qOuCzQJeFU"; + const PRIV_KEY_1_JWK_X: &str = "u6dWhaRHrvFF0CiFVuUUFafU6ixveQf9trHDXJ8hgV8"; + const PRIV_KEY_1_JWK_Y: &str = "thSm4LIY35vDD-5LE454eB7TShn919DVVGZ_7tWdjTE"; + + const PRIV_KEY_2_JWK_D: &str = "uN2YSQvxuxhQQ9Y1XXjYi1vr2ZTdzuoDX18PYu4LU-0"; + const PRIV_KEY_2_JWK_X: &str = "S2S3tjygMB0DkM-N9jYUgGLt_9_H6km5P9V6V_KS4_4"; + const PRIV_KEY_2_JWK_Y: &str = "03j8Tyqgrc4R4FAUV2C7-im96yMmfmO_5Om6Kr8YP3o"; + + const SHARED_SECRET_HEX: &str = + "163FAA3FC4815D47345C8E959F707B2F1D3537E7B2EA1DAEC23CA8D0A242CFF3"; + + fn load_priv_key_1() -> PrivateKey<Static> { + let private_key = base64::decode_config(PRIV_KEY_1_JWK_D, base64::URL_SAFE_NO_PAD).unwrap(); + let x = base64::decode_config(PRIV_KEY_1_JWK_X, base64::URL_SAFE_NO_PAD).unwrap(); + let y = base64::decode_config(PRIV_KEY_1_JWK_Y, base64::URL_SAFE_NO_PAD).unwrap(); + PrivateKey::<Static>::import( + &EcKey::from_coordinates(Curve::P256, &private_key, &x, &y).unwrap(), + ) + .unwrap() + } + + fn load_priv_key_2() -> PrivateKey<Static> { + let private_key = base64::decode_config(PRIV_KEY_2_JWK_D, base64::URL_SAFE_NO_PAD).unwrap(); + let x = base64::decode_config(PRIV_KEY_2_JWK_X, base64::URL_SAFE_NO_PAD).unwrap(); + let y = base64::decode_config(PRIV_KEY_2_JWK_Y, base64::URL_SAFE_NO_PAD).unwrap(); + PrivateKey::<Static>::import( + &EcKey::from_coordinates(Curve::P256, &private_key, &x, &y).unwrap(), + ) + .unwrap() + } + + #[test] + fn test_static_agreement() { + let pub_key_raw = base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD).unwrap(); + let peer_pub_key = UnparsedPublicKey::new(&ECDH_P256, &pub_key_raw); + let prv_key = load_priv_key_2(); + let ikm = prv_key.agree_static(&peer_pub_key).unwrap(); + let secret = ikm + .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) }) + .unwrap(); + let secret_b64 = hex::encode_upper(&secret); + assert_eq!(secret_b64, *SHARED_SECRET_HEX); + } + + #[test] + fn test_ephemeral_agreement_roundtrip() { + let (our_prv_key, our_pub_key) = + KeyPair::<Ephemeral>::generate(&ECDH_P256).unwrap().split(); + let (their_prv_key, their_pub_key) = + KeyPair::<Ephemeral>::generate(&ECDH_P256).unwrap().split(); + let their_pub_key_raw = their_pub_key.to_bytes().unwrap(); + let peer_public_key_1 = UnparsedPublicKey::new(&ECDH_P256, &their_pub_key_raw); + let ikm_1 = our_prv_key.agree(&peer_public_key_1).unwrap(); + let secret_1 = ikm_1 + .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) }) + .unwrap(); + let our_pub_key_raw = our_pub_key.to_bytes().unwrap(); + let peer_public_key_2 = UnparsedPublicKey::new(&ECDH_P256, &our_pub_key_raw); + let ikm_2 = their_prv_key.agree(&peer_public_key_2).unwrap(); + let secret_2 = ikm_2 + .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) }) + .unwrap(); + assert_eq!(secret_1, secret_2); + } + + #[test] + fn test_compute_public_key() { + let (prv_key, pub_key) = KeyPair::<Static>::generate(&ECDH_P256).unwrap().split(); + let computed_pub_key = prv_key.compute_public_key().unwrap(); + assert_eq!( + computed_pub_key.to_bytes().unwrap(), + pub_key.to_bytes().unwrap() + ); + } + + #[test] + fn test_compute_public_key_known_values() { + let prv_key = load_priv_key_1(); + let pub_key = base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD).unwrap(); + let computed_pub_key = prv_key.compute_public_key().unwrap(); + assert_eq!(computed_pub_key.to_bytes().unwrap(), pub_key.as_slice()); + + let prv_key = load_priv_key_2(); + let computed_pub_key = prv_key.compute_public_key().unwrap(); + assert_ne!(computed_pub_key.to_bytes().unwrap(), pub_key.as_slice()); + } + + #[test] + fn test_keys_byte_representations_roundtrip() { + let key_pair = KeyPair::<Static>::generate(&ECDH_P256).unwrap(); + let prv_key = key_pair.private_key; + let extracted_pub_key = prv_key.compute_public_key().unwrap(); + let ec_key = prv_key.export().unwrap(); + let prv_key_reconstructed = PrivateKey::<Static>::import(&ec_key).unwrap(); + let extracted_pub_key_reconstructed = prv_key.compute_public_key().unwrap(); + let ec_key_reconstructed = prv_key_reconstructed.export().unwrap(); + assert_eq!(ec_key.curve(), ec_key_reconstructed.curve()); + assert_eq!(ec_key.public_key(), ec_key_reconstructed.public_key()); + assert_eq!(ec_key.private_key(), ec_key_reconstructed.private_key()); + assert_eq!( + extracted_pub_key.to_bytes().unwrap(), + extracted_pub_key_reconstructed.to_bytes().unwrap() + ); + } + + #[test] + fn test_agreement_rejects_invalid_pubkeys() { + let prv_key = load_priv_key_2(); + + let mut invalid_pub_key = + base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD).unwrap(); + invalid_pub_key[0] = invalid_pub_key[0].wrapping_add(1); + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + + let mut invalid_pub_key = + base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD).unwrap(); + invalid_pub_key[0] = 0x02; + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + + let mut invalid_pub_key = + base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD).unwrap(); + invalid_pub_key[64] = invalid_pub_key[0].wrapping_add(1); + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + + let mut invalid_pub_key = [0u8; 65]; + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + invalid_pub_key[0] = 0x04; + + let mut invalid_pub_key = base64::decode_config(PUB_KEY_1_B64, base64::URL_SAFE_NO_PAD) + .unwrap() + .to_vec(); + invalid_pub_key = invalid_pub_key[0..64].to_vec(); + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + + // From FxA tests at https://github.com/mozilla/fxa-crypto-relier/blob/04f61dc/test/deriver/DeriverUtils.js#L78 + // We trust that NSS will do the right thing here, but it seems worthwhile to confirm for completeness. + let invalid_pub_key_b64 = "BEogZ-rnm44oJkKsOE6Tc7NwFMgmntf7Btm_Rc4atxcqq99Xq1RWNTFpk99pdQOSjUvwELss51PkmAGCXhLfMV0"; + let invalid_pub_key = + base64::decode_config(invalid_pub_key_b64, base64::URL_SAFE_NO_PAD).unwrap(); + assert!(prv_key + .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key)) + .is_err()); + } +} diff --git a/third_party/rust/rc_crypto/src/constant_time.rs b/third_party/rust/rc_crypto/src/constant_time.rs new file mode 100644 index 0000000000..64dfe568b8 --- /dev/null +++ b/third_party/rust/rc_crypto/src/constant_time.rs @@ -0,0 +1,45 @@ +/* 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::error::*; + +/// Returns `Ok(())` if `a == b` and `Error` otherwise. +/// The comparison of `a` and `b` is done in constant time with respect to the +/// contents of each, but NOT in constant time with respect to the lengths of +/// `a` and `b`. +pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> { + if nss::secport::secure_memcmp(a, b) { + Ok(()) + } else { + Err(ErrorKind::InternalError.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn does_compare() { + assert!(verify_slices_are_equal(b"bobo", b"bobo").is_ok()); + assert!(verify_slices_are_equal(b"bobo", b"obob").is_err()); + assert!(verify_slices_are_equal(b"bobo", b"notbobo").is_err()); + } +} diff --git a/third_party/rust/rc_crypto/src/digest.rs b/third_party/rust/rc_crypto/src/digest.rs new file mode 100644 index 0000000000..4dc451a55c --- /dev/null +++ b/third_party/rust/rc_crypto/src/digest.rs @@ -0,0 +1,75 @@ +/* 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::error::*; + +pub use nss::pk11::context::HashAlgorithm::{self as Algorithm, *}; + +/// A calculated digest value. +#[derive(Clone)] +pub struct Digest { + pub(crate) value: Vec<u8>, + pub(crate) algorithm: Algorithm, +} + +impl Digest { + pub fn algorithm(&self) -> &Algorithm { + &self.algorithm + } +} + +impl AsRef<[u8]> for Digest { + fn as_ref(&self) -> &[u8] { + self.value.as_ref() + } +} + +/// Returns the digest of data using the given digest algorithm. +pub fn digest(algorithm: &Algorithm, data: &[u8]) -> Result<Digest> { + let value = nss::pk11::context::hash_buf(algorithm, data)?; + Ok(Digest { + value, + algorithm: *algorithm, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + const MESSAGE: &[u8] = b"bobo"; + const DIGEST_HEX: &str = "bf0c97708b849de696e7373508b13c5ea92bafa972fc941d694443e494a4b84d"; + + #[test] + fn sha256_digest() { + assert_eq!(hex::encode(&digest(&SHA256, MESSAGE).unwrap()), DIGEST_HEX); + assert_ne!( + hex::encode(&digest(&SHA256, b"notbobo").unwrap()), + DIGEST_HEX + ); + } + + #[test] + fn digest_cleanly_rejects_gigantic_messages() { + let message = vec![0; (std::i32::MAX as usize) + 1]; + assert!(digest(&SHA256, &message).is_err()); + } +} diff --git a/third_party/rust/rc_crypto/src/ece_crypto.rs b/third_party/rust/rc_crypto/src/ece_crypto.rs new file mode 100644 index 0000000000..44cf59d231 --- /dev/null +++ b/third_party/rust/rc_crypto/src/ece_crypto.rs @@ -0,0 +1,437 @@ +/* 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/. */ + +use crate::{ + aead, + agreement::{self, Curve, EcKey, UnparsedPublicKey}, + digest, hkdf, hmac, rand, +}; +use ece::crypto::{Cryptographer, EcKeyComponents, LocalKeyPair, RemotePublicKey}; + +impl From<crate::Error> for ece::Error { + fn from(_: crate::Error) -> Self { + ece::Error::CryptoError + } +} + +pub struct RcCryptoLocalKeyPair { + wrapped: agreement::KeyPair<agreement::Static>, +} +// SECKEYPrivateKeyStr and SECKEYPublicKeyStr are Sync. +unsafe impl Sync for RcCryptoLocalKeyPair {} + +impl RcCryptoLocalKeyPair { + pub fn from_raw_components(components: &EcKeyComponents) -> Result<Self, ece::Error> { + let ec_key = EcKey::new( + Curve::P256, + components.private_key(), + components.public_key(), + ); + let priv_key = agreement::PrivateKey::<agreement::Static>::import(&ec_key)?; + let wrapped = agreement::KeyPair::<agreement::Static>::from_private_key(priv_key)?; + Ok(RcCryptoLocalKeyPair { wrapped }) + } + + pub fn generate_random() -> Result<Self, ece::Error> { + let wrapped = agreement::KeyPair::<agreement::Static>::generate(&agreement::ECDH_P256)?; + Ok(RcCryptoLocalKeyPair { wrapped }) + } + + fn agree(&self, peer: &RcCryptoRemotePublicKey) -> Result<Vec<u8>, ece::Error> { + let peer_public_key_raw_bytes = &peer.as_raw()?; + let peer_public_key = + UnparsedPublicKey::new(&agreement::ECDH_P256, &peer_public_key_raw_bytes); + self.wrapped + .private_key() + .agree_static(&peer_public_key)? + .derive(|z| Ok(z.to_vec())) + } +} + +impl LocalKeyPair for RcCryptoLocalKeyPair { + fn raw_components(&self) -> Result<EcKeyComponents, ece::Error> { + let ec_key = self.wrapped.private_key().export()?; + Ok(EcKeyComponents::new( + ec_key.private_key(), + ec_key.public_key(), + )) + } + + fn pub_as_raw(&self) -> Result<Vec<u8>, ece::Error> { + let bytes = self.wrapped.public_key().to_bytes()?; + Ok(bytes.to_vec()) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} +pub struct RcCryptoRemotePublicKey { + raw: Vec<u8>, +} + +impl RcCryptoRemotePublicKey { + pub fn from_raw(bytes: &[u8]) -> Result<RcCryptoRemotePublicKey, ece::Error> { + Ok(RcCryptoRemotePublicKey { + raw: bytes.to_owned(), + }) + } +} + +impl RemotePublicKey for RcCryptoRemotePublicKey { + fn as_raw(&self) -> Result<Vec<u8>, ece::Error> { + Ok(self.raw.to_vec()) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub(crate) struct RcCryptoCryptographer; + +impl Cryptographer for RcCryptoCryptographer { + fn generate_ephemeral_keypair(&self) -> Result<Box<dyn LocalKeyPair>, ece::Error> { + Ok(Box::new(RcCryptoLocalKeyPair::generate_random()?)) + } + + fn import_key_pair( + &self, + components: &EcKeyComponents, + ) -> Result<Box<dyn LocalKeyPair>, ece::Error> { + Ok(Box::new(RcCryptoLocalKeyPair::from_raw_components( + components, + )?)) + } + + fn import_public_key(&self, raw: &[u8]) -> Result<Box<dyn RemotePublicKey>, ece::Error> { + Ok(Box::new(RcCryptoRemotePublicKey::from_raw(raw)?)) + } + + fn compute_ecdh_secret( + &self, + remote: &dyn RemotePublicKey, + local: &dyn LocalKeyPair, + ) -> Result<Vec<u8>, ece::Error> { + let local_any = local.as_any(); + let local = local_any.downcast_ref::<RcCryptoLocalKeyPair>().unwrap(); + let remote_any = remote.as_any(); + let remote = remote_any + .downcast_ref::<RcCryptoRemotePublicKey>() + .unwrap(); + Ok(local.agree(&remote)?) + } + + fn hkdf_sha256( + &self, + salt: &[u8], + secret: &[u8], + info: &[u8], + len: usize, + ) -> Result<Vec<u8>, ece::Error> { + let salt = hmac::SigningKey::new(&digest::SHA256, &salt); + let mut out = vec![0u8; len]; + hkdf::extract_and_expand(&salt, &secret, &info, &mut out)?; + Ok(out) + } + + fn aes_gcm_128_encrypt( + &self, + key: &[u8], + iv: &[u8], + data: &[u8], + ) -> Result<Vec<u8>, ece::Error> { + let key = aead::SealingKey::new(&aead::AES_128_GCM, key)?; + let nonce = aead::Nonce::try_assume_unique_for_key(&aead::AES_128_GCM, iv)?; + Ok(aead::seal(&key, nonce, aead::Aad::empty(), data)?) + } + + fn aes_gcm_128_decrypt( + &self, + key: &[u8], + iv: &[u8], + ciphertext_and_tag: &[u8], + ) -> Result<Vec<u8>, ece::Error> { + let key = aead::OpeningKey::new(&aead::AES_128_GCM, key)?; + let nonce = aead::Nonce::try_assume_unique_for_key(&aead::AES_128_GCM, iv)?; + Ok(aead::open( + &key, + nonce, + aead::Aad::empty(), + &ciphertext_and_tag, + )?) + } + + fn random_bytes(&self, dest: &mut [u8]) -> Result<(), ece::Error> { + Ok(rand::fill(dest)?) + } +} + +// Please call `rc_crypto::ensure_initialized()` instead of calling +// this function directly. +pub(crate) fn init() { + ece::crypto::set_cryptographer(&crate::ece_crypto::RcCryptoCryptographer) + .expect("Failed to initialize `ece` cryptographer!") +} + +#[cfg(test)] +mod tests { + use super::*; + use ece::*; + + // Copy-pasta from the tests in the ECE crate. + fn generate_keys() -> Result<(Box<dyn LocalKeyPair>, Box<dyn LocalKeyPair>)> { + let local_key = RcCryptoLocalKeyPair::generate_random()?; + let remote_key = RcCryptoLocalKeyPair::generate_random()?; + Ok((Box::new(local_key), Box::new(remote_key))) + } + + #[allow(clippy::too_many_arguments)] + fn try_encrypt( + private_key: &str, + public_key: &str, + remote_pub_key: &str, + auth_secret: &str, + salt: &str, + pad_length: usize, + rs: u32, + plaintext: &str, + ) -> Result<String> { + let private_key = hex::decode(private_key).unwrap(); + let public_key = hex::decode(public_key).unwrap(); + let ec_key = EcKeyComponents::new(private_key, public_key); + let local_key_pair = RcCryptoLocalKeyPair::from_raw_components(&ec_key)?; + let remote_pub_key = hex::decode(remote_pub_key).unwrap(); + let remote_pub_key = RcCryptoRemotePublicKey::from_raw(&remote_pub_key).unwrap(); + let auth_secret = hex::decode(auth_secret).unwrap(); + let salt = hex::decode(salt).unwrap(); + let plaintext = plaintext.as_bytes(); + let params = WebPushParams::new(rs, pad_length, salt); + let ciphertext = Aes128GcmEceWebPush::encrypt_with_keys( + &local_key_pair, + &remote_pub_key, + &auth_secret, + &plaintext, + params, + )?; + Ok(hex::encode(ciphertext)) + } + + fn try_decrypt( + private_key: &str, + public_key: &str, + auth_secret: &str, + payload: &str, + ) -> Result<String> { + let private_key = hex::decode(private_key).unwrap(); + let public_key = hex::decode(public_key).unwrap(); + let ec_key = EcKeyComponents::new(private_key, public_key); + let plaintext = decrypt( + &ec_key, + &hex::decode(auth_secret).unwrap(), + &hex::decode(payload).unwrap(), + )?; + Ok(String::from_utf8(plaintext).unwrap()) + } + + #[test] + fn test_e2e() { + crate::ensure_initialized(); + let (local_key, remote_key) = generate_keys().unwrap(); + let plaintext = b"When I grow up, I want to be a watermelon"; + let mut auth_secret = vec![0u8; 16]; + RcCryptoCryptographer + .random_bytes(&mut auth_secret) + .unwrap(); + let remote_public = RcCryptoCryptographer + .import_public_key(&remote_key.pub_as_raw().unwrap()) + .unwrap(); + let params = WebPushParams::default(); + let ciphertext = Aes128GcmEceWebPush::encrypt_with_keys( + &*local_key, + &*remote_public, + &auth_secret, + plaintext, + params, + ) + .unwrap(); + let decrypted = + Aes128GcmEceWebPush::decrypt(&*remote_key, &auth_secret, &ciphertext).unwrap(); + assert_eq!(decrypted, plaintext.to_vec()); + } + + #[test] + fn test_conv_fn() -> Result<()> { + crate::ensure_initialized(); + let (local_key, auth) = generate_keypair_and_auth_secret()?; + let plaintext = b"Mary had a little lamb, with some nice mint jelly"; + let mut salt = vec![0u8; 16]; + RcCryptoCryptographer.random_bytes(&mut salt)?; + let encoded = encrypt(&local_key.pub_as_raw()?, &auth, &salt, plaintext).unwrap(); + let decoded = decrypt(&local_key.raw_components()?, &auth, &encoded)?; + assert_eq!(decoded, plaintext.to_vec()); + Ok(()) + } + + #[test] + fn try_encrypt_ietf_rfc() { + crate::ensure_initialized(); + let ciphertext = try_encrypt( + "c9f58f89813e9f8e872e71f42aa64e1757c9254dcc62b72ddc010bb4043ea11c", + "04fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0f", + "042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e", + "05305932a1c7eabe13b6cec9fda48882", + "0c6bfaadad67958803092d454676f397", + 0, + 4096, + "When I grow up, I want to be a watermelon", + ).unwrap(); + assert_eq!(ciphertext, "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd"); + } + + #[test] + fn try_encrypt_rs_24_pad_6() { + crate::ensure_initialized(); + let ciphertext = try_encrypt( + "0f28beaf7e27793c03638dc2973a15b0016e1b367cbffda8861ab175f31bce02", + "0430efcb1eb043b805e4e44bab35f82513c33fedb28700f7e568ac8b61e8d835665a51eb6679b2db228a10c0c3fe5077062848d9bb3d60279f93ce35484728aa1f", + "04c0d1a812b291291dd7beee358713c126c589f3633c26d1a201311de036dc10931e4ee142f61921a3ea5864e872a93841a52944e5b3f6accecce8c828fb04a4cd", + "9d7735d8de1962b98394b07ffe287e20", + "ff805030a108e114e6c17fad6186a1a6", + 6, + 24, + "I am the very model of a modern Major-General, I've information vegetable, animal, and mineral", + ).unwrap(); + assert_eq!(ciphertext, "ff805030a108e114e6c17fad6186a1a600000018410430efcb1eb043b805e4e44bab35f82513c33fedb28700f7e568ac8b61e8d835665a51eb6679b2db228a10c0c3fe5077062848d9bb3d60279f93ce35484728aa1fd2c1713949aec98f05096c7298fd3f51c4f818fafa1fe615d8447b3a05406031f6401ac24f2a775ca52456a921b83b9e0042c3a63e1afa1ae012774d9d775be8d19419451d37ff59ff592e84f07440a63fc17f5cabcb9a50eddaf75370db647f94447d3f166269d8711df0f57e56049576e1130a5a5e1f94ba8a5d0b0007c6c0fd2998429e7d63d4ef919798f46ecf5f0b28fb80f5b2439de26b8a52200bc7d6af7a4840721fe8be8524a691b6ef0edae90bb6f5927894819b831b45b53f8401fe022dbb64ed7565350904ac0b517135d7f8abbc98127fb163864d4d4a307425b2cd43db22af267d71c37146994a8c4805adc341bfba27af09fd80bd5eff51d877282a2fbfbfeb10199e7879e4b9d13a46d57fb7d786824853e1cc89cafbaf14de1e924c944feb8b626ce0207d6f9fa9d849eecac69b42d6e7a23bd5124d49622b44b35c5b15fb0e6a7781a503f1a4e062e015d557d95d44d9d8b0799b3aafce83d5d4"); + } + + #[test] + fn try_encrypt_rs_18_pad_31() { + crate::ensure_initialized(); + // This test is also interesting because the data length (54) is a + // multiple of rs (18). We'll allocate memory to hold 4 records, but only + // write 3. + let ciphertext = try_encrypt( + "7830577bafcfc45828da0c40aab09fb227bfeae068aab8c064222acbe6effd34", + "0400b833e481a99aa330dcb277922d5f84af2e9ce611ad2ad3ed0f5b431912d35ea72fc5bf76b769d9526778f5abfa058650988da5e531ff82d1a7043794c71706", + "04c3d714cb42e2b0a1d6f98599e2f186b8c2ba6f6fab5e09a2abca865c0805892b2c3729330ef83dc9df4b44362b039a0609d36beb9321a431ec123506ddd90f24", + "e4d7b79decdede12c3e9d90d3e05730f", + "e49888d2b28f277f847bc5de96f0f81b", + 31, + 18, + "Push the button, Frank!", + ).unwrap(); + assert_eq!(ciphertext, "e49888d2b28f277f847bc5de96f0f81b00000012410400b833e481a99aa330dcb277922d5f84af2e9ce611ad2ad3ed0f5b431912d35ea72fc5bf76b769d9526778f5abfa058650988da5e531ff82d1a7043794c717063aeb958bf116bccf50742fd4d69bd0ea7e3f611c709bf2cdf5cd47c6426cb8323b5398c43c0d0b92cc982da1c24ce5fee2b203f7ad78ca44f0490f3407f5fee883266ee47035195de0fe6d8a75e487df256db597a75e45ae4fb55b8259cb0b2d19e7b05714267eb560ae072b7a665951917a068732df309be256f90f2adda32f05feaa5e9b0695bca2ccf22aaefc7da9ceebc5d40c12d32adb5c84cb320af944016095362febba4ffa4a99830e4958ea2bba508cb683a58d2027d4b74726a853b24b47ccba751abe9d9ab2da9ec2ba9c7ccf0cf17305bae314d38a687618b0772fcb71d4419027a4bf435cb721aad74efc179981b7169604bf97ecac41e73884456933734818132923b56c152d6c9e59aef995aca59de0bf2c803a07180889670a08e64a20d2bfa853e0112872947baaaffb510cc9e75d6310ed6aacbd2e0ba3a29be42c6532ea4e3346e1f0571646371c71665e3fac9d76faee1f122e64d490dd2a3e31816eab583f172841a075d205f318714a8c70ce0f327f4d92b8c9dcb813e6d24fe85633f1a9c7c1e4a1fb314dd5fe3e280e3908f36c8cbfb80b7d9243abaffa65c216cf1aa8b8d626a630dfe8186ce977a5b8f3649d3753b9176c367e4e07f220a175806138e88825a2f3498420582b96209658bbfa8f2ba6933a83c25edb269187796542e2ac49b8078636bddc268e11625e8bff9f0a343d3a4c06080ef0803b8dcd8e841d0e2759e483ea19b903324d9ec4d52f491acef3eeff441c37881c7593eac31621337a5e8659f93e20079b0e26ebfe56c10455d10971130bd2a2c159c74f48b2e526530a76f64cca2efb246e793d11fb75a668018e70c3107100f81ba3b16ae40a838f18d4c47f1d7132f174688ec5382394e0119921731a16879b858ff38f72851ea3d9f5263fec5a606d1271a89b84cca53ed73c5254e245bf8f2f27c2c1c87f39eea78c7017c8c6b5ab01663032b58da31057285e56c203f4e48d6789c66b2695a900e00482bd846559ecddd40264b38e279647d1ec0fccdc1881838bbe0c835e2690ef058b8f6a03e29cd9eb9584e97fbc309773c3688e5e03f9d38e3e4548738a5f569c59147d3e823cccac71d5e8825d5134ce9813cd0b8f9627a3dbfa45b83a59c83d2b4d3ad437778a3cb1bc77ba16c92306f4261a2a1f0d5c7edaecf926f92d7c9dfcae87513a68b8c7ef7c63264b858767c11aaa41d27c636f52e28551e93a969cdc96d43867b7cbd68fe0357bd33415faf22aaeebc957f4b5737a04ab7277b4ed4008f09edaff5a6db69f6cb06f3d0b76688906b2f53b27e63f3728ba2eda505fb1b32f81dddc6d305fd5949edd05490cb1618f0ce1430e9f5edf50012dc3"); + } + + #[test] + fn test_decrypt_rs_24_pad_0() { + crate::ensure_initialized(); + let plaintext = try_decrypt( + "c899d11d32e2b7e6fe7498786f50f23b98ace5397ad261de39ba6449ecc12cad", + "04b3fc72e4365cbeb5c78862396eb5e66fd905b483a1b3eac04695f4b802e5b493c5e3b70eb427b6c728b2b204fc255fa218cb45f34d235242705e0d1ea87236e0", + "996fad8b50aa2d02b83f26412b2e2aee", + "495ce6c8de93a4539e862e8634993cbb0000001841043c3378a2c0ab954e1498718e85f08bb723fb7d25e135a663fe385884eb8192336bf90a54ed720f1c045c0b405e9bbc3a2142b16c89086734c374ebaf7099e6427e2d32c8ada5018703c54b10b481e1027d7209d8c6b43553fa133afa597f2ddc45a5ba8140944e6490bb8d6d99ba1d02e60d95f48ce644477c17231d95b97a4f95dd" + ).unwrap(); + assert_eq!(plaintext, "I am the walrus"); + } + + #[test] + fn test_decrypt_rs_49_pad_84_ciphertext_len_falls_on_record_boundary() { + crate::ensure_initialized(); + let plaintext = try_decrypt( + "67004a4ea820deed8e49db5e9480e63d3ea3cce1ae8e1a60609713d527d001ef", + "04014e8f14b92da07ce083b93f96367e87b217a47f7ef2ee93a9d343aa063e575a9f30d59c690c6a39b3fc815b150ca7dd149601741337b53507a51f41b173a721", + "95f17570e508ef6a2b2ad1b4f5cade33", + "fb2883cec1c4fcadd6d1371f6ea491e00000003141042d441ee7f9ff6a0329a64927d0524fdbe7b22c6fb65e10ab4fdc038f94420a0ca3fa28dad36c84ec91a162eae078faad2c1ced78de8113e19602b20e894f4976b973e2fcf682fa0c8ccd9af3d5bff1ede16fad5a31ce19d38b5e1fe1f78a4fad842bbc10254c2c6cdd96a2b55284d972c53cad8c3bacb10f5f57eb0d4a4333b604102ba117cae29108fbd9f629a8ba6960dd01945b39ed37ba706c434a10fd2bd2094ff9249bcdad45135f5fe45fcd38071f8b2d3941afda439810d77aacaf7ce50b54325bf58c9503337d073785a323dfa343" + ).unwrap(); + assert_eq!(plaintext, "Hello, world"); + } + + #[test] + fn test_decrypt_ietf_rfc() { + crate::ensure_initialized(); + let plaintext = try_decrypt( + "ab5757a70dd4a53e553a6bbf71ffefea2874ec07a6b379e3c48f895a02dc33de", + "042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e", + "05305932a1c7eabe13b6cec9fda48882", + "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd" + ).unwrap(); + assert_eq!(plaintext, "When I grow up, I want to be a watermelon"); + } + + #[test] + fn test_decrypt_rs_18_pad_0() { + crate::ensure_initialized(); + let plaintext = try_decrypt( + "27433fab8970b3cb5284b61183efb46286562cd2a7330d8cae960911a5571d0c", + "04515d4326355652399da24b2be9241e633b5cf14faf0cf3a6fd60317b954c0a2f4848548004b27b0cf7480bc810c6bec03a8fb79c8ea00fc8b05e00f8834563ef", + "d65a04df95f2db5e604839f717dcde79", + "7caebdbc20938ee340a946f1bd4f68f100000012410437cfdb5223d9f95eaa02f6ed940ff22eaf05b3622e949dc3ce9f335e6ef9b26aeaacca0f74080a8b364592f2ccc6d5eddd43004b70b91887d144d9fa93f16c3bc7ea68f4fd547a94eca84b16e138a6080177" + ).unwrap(); + assert_eq!(plaintext, "1"); + } + + #[test] + fn test_decrypt_missing_header_block() { + crate::ensure_initialized(); + let err = try_decrypt( + "1be83f38332ef09681faf3f307b1ff2e10cab78cc7cdab683ac0ee92ac3f6ee1", + "04dba991ca215343f36bdd3e857cafde3d18bf57f1835b2833bad414f0884162051ac96a0b24490037d07cf528e4e18e100a1a64eb744748544bf1e220dabacf2c", + "3471bb98481e02533bf39542bcf3dba4", + "45b74d2b69be9b074de3b35aa87e7c15611d", + ) + .unwrap_err(); + match err { + Error::HeaderTooShort => {} + _ => panic!("Unexpected error type!"), + }; + } + + #[test] + fn test_decrypt_truncated_sender_key() { + crate::ensure_initialized(); + let err = try_decrypt( + "ce88e8e0b3057a4752eb4c8fa931eb621c302da5ad03b81af459cf6735560cae", + "04a325d99084c40de0ce722a042c448d94a32691721ca79e3cf745e78c69886194b02cea19224176795a9d4dbbb2073af2ccd6fa6f0a4c7c4968556be502a3ba81", + "5c31e0d96d9a139899ac0969d359f740", + "de5b696b87f1a15cb6adebdd79d6f99e000000120100b6bc1826c37c9f73dd6b4859c2b505181952", + ) + .unwrap_err(); + match err { + Error::InvalidKeyLength => {} + _ => panic!("Unexpected error type!"), + }; + } + + #[test] + fn test_decrypt_truncated_auth_secret() { + crate::ensure_initialized(); + let err = try_decrypt( + "60c7636a517de7039a0ac2d0e3064400794c78e7e049398129a227cee0f9a801", + "04fdd04128a85c05896d7f81fe118bdcb887b9f3c1ff4183adc4c824d128607300e986b2dfb5a610e5af43e408a00730584f93e3dfddfc44737d5f08fb2d6f8916", + "355a38cd6d9bef15990e2d3308dbd600", + "8115f4988b8c392a7bacb43c8f1ac5650000001241041994483c541e9bc39a6af03ff713aa7745c284e138a42a2435b797b20c4b698cf5118b4f8555317c190eabebfab749c164d3f6bdebe0d441719131a357d8890a13c4dbd4b16ff3dd5a83f7c91ad6e040ac42730a7f0b3cd3245e9f8d6ff31c751d410cfd" + ).unwrap_err(); + match err { + Error::CryptoError => {} + _ => panic!("Unexpected error type!"), + }; + } + + #[test] + fn test_decrypt_early_final_record() { + crate::ensure_initialized(); + let err = try_decrypt( + "5dda1d918bc407ba3cda12cb8014d49aa7e0269002820304466bc80034ca9240", + "04c95c6520dad11e8f6a1bf8031a40c2a4ee1045c1903be06a1dfa7f829cceb2de02481ae6bd0476121b12c5532d0b231788077efa0683a5bfe0d62339b251cb35", + "40c241fde4269ee1e6d725592d982718", + "dbe215507d1ad3d2eaeabeae6e874d8f0000001241047bc4343f34a8348cdc4e462ffc7c40aa6a8c61a739c4c41d45125505f70e9fc5f9efa86852dd488dcf8e8ea2cafb75e07abd5ee7c9d5c038bafef079571b0bda294411ce98c76dd031c0e580577a4980a375e45ed30429be0e2ee9da7e6df8696d01b8ec" + ).unwrap_err(); + match err { + Error::DecryptPadding => {} + _ => panic!("Unexpected error type!"), + }; + } +} diff --git a/third_party/rust/rc_crypto/src/error.rs b/third_party/rust/rc_crypto/src/error.rs new file mode 100644 index 0000000000..0edc97bef4 --- /dev/null +++ b/third_party/rust/rc_crypto/src/error.rs @@ -0,0 +1,20 @@ +/* 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/. */ + +#[derive(Debug, thiserror::Error)] +pub enum ErrorKind { + #[error("NSS error: {0}")] + NSSError(#[from] nss::Error), + #[error("Internal crypto error")] + InternalError, + #[error("Conversion error: {0}")] + ConversionError(#[from] std::num::TryFromIntError), +} + +error_support::define_error! { + ErrorKind { + (ConversionError, std::num::TryFromIntError), + (NSSError, nss::Error), + } +} diff --git a/third_party/rust/rc_crypto/src/hawk_crypto.rs b/third_party/rust/rc_crypto/src/hawk_crypto.rs new file mode 100644 index 0000000000..92f4725ca8 --- /dev/null +++ b/third_party/rust/rc_crypto/src/hawk_crypto.rs @@ -0,0 +1,157 @@ +/* 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/. */ + +use crate::{digest, hmac, rand}; +use hawk::crypto as hc; + +impl From<crate::Error> for hc::CryptoError { + // Our errors impl `Fail`, so we can do this. + fn from(e: crate::Error) -> Self { + hc::CryptoError::Other(e.into()) + } +} + +pub(crate) struct RcCryptoCryptographer; + +impl hc::HmacKey for crate::hmac::SigningKey { + fn sign(&self, data: &[u8]) -> Result<Vec<u8>, hc::CryptoError> { + let digest = hmac::sign(&self, data)?; + Ok(digest.as_ref().into()) + } +} + +// I don't really see a reason to bother doing incremental hashing here. A +// one-shot is going to be faster in many cases anyway, and the higher memory +// usage probably doesn't matter given our usage. +struct NssHasher { + buffer: Vec<u8>, + algorithm: &'static digest::Algorithm, +} + +impl hc::Hasher for NssHasher { + fn update(&mut self, data: &[u8]) -> Result<(), hc::CryptoError> { + self.buffer.extend_from_slice(data); + Ok(()) + } + + fn finish(&mut self) -> Result<Vec<u8>, hc::CryptoError> { + let digest = digest::digest(self.algorithm, &self.buffer)?; + let bytes: &[u8] = digest.as_ref(); + Ok(bytes.to_owned()) + } +} + +impl hc::Cryptographer for RcCryptoCryptographer { + fn rand_bytes(&self, output: &mut [u8]) -> Result<(), hc::CryptoError> { + rand::fill(output)?; + Ok(()) + } + + fn new_key( + &self, + algorithm: hawk::DigestAlgorithm, + key: &[u8], + ) -> Result<Box<dyn hc::HmacKey>, hc::CryptoError> { + let k = hmac::SigningKey::new(to_rc_crypto_algorithm(algorithm)?, key); + Ok(Box::new(k)) + } + + fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool { + crate::constant_time::verify_slices_are_equal(a, b).is_ok() + } + + fn new_hasher( + &self, + algorithm: hawk::DigestAlgorithm, + ) -> Result<Box<dyn hc::Hasher>, hc::CryptoError> { + Ok(Box::new(NssHasher { + algorithm: to_rc_crypto_algorithm(algorithm)?, + buffer: vec![], + })) + } +} + +fn to_rc_crypto_algorithm( + algorithm: hawk::DigestAlgorithm, +) -> Result<&'static digest::Algorithm, hc::CryptoError> { + match algorithm { + hawk::DigestAlgorithm::Sha256 => Ok(&digest::SHA256), + algo => Err(hc::CryptoError::UnsupportedDigest(algo)), + } +} + +// Note: this doesn't initialize NSS! +pub(crate) fn init() { + hawk::crypto::set_cryptographer(&crate::hawk_crypto::RcCryptoCryptographer) + .expect("Failed to initialize `hawk` cryptographer!") +} + +#[cfg(test)] +mod test { + + // Based on rust-hawk's hash_consistency. This fails if we've messed up the hashing. + #[test] + fn test_hawk_hashing() { + crate::ensure_initialized(); + let mut hasher1 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap(); + hasher1.update("pày").unwrap(); + hasher1.update("load").unwrap(); + let hash1 = hasher1.finish().unwrap(); + + let mut hasher2 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap(); + hasher2.update("pàyload").unwrap(); + let hash2 = hasher2.finish().unwrap(); + + let hash3 = hawk::PayloadHasher::hash("text/plain", hawk::SHA256, "pàyload").unwrap(); + + let hash4 = // "pàyload" as utf-8 bytes + hawk::PayloadHasher::hash("text/plain", hawk::SHA256, &[112, 195, 160, 121, 108, 111, 97, 100]).unwrap(); + + assert_eq!( + hash1, + &[ + 228, 238, 241, 224, 235, 114, 158, 112, 211, 254, 118, 89, 25, 236, 87, 176, 181, + 54, 61, 135, 42, 223, 188, 103, 194, 59, 83, 36, 136, 31, 198, 50 + ] + ); + assert_eq!(hash2, hash1); + assert_eq!(hash3, hash1); + assert_eq!(hash4, hash1); + } + + // Based on rust-hawk's test_make_mac. This fails if we've messed up the signing. + #[test] + fn test_hawk_signing() { + crate::ensure_initialized(); + let key = hawk::Key::new( + &[ + 11u8, 19, 228, 209, 79, 189, 200, 59, 166, 47, 86, 254, 235, 184, 120, 197, 75, + 152, 201, 79, 115, 61, 111, 242, 219, 187, 173, 14, 227, 108, 60, 232, + ], + hawk::SHA256, + ) + .unwrap(); + + let mac = hawk::mac::Mac::new( + hawk::mac::MacType::Header, + &key, + std::time::UNIX_EPOCH + std::time::Duration::new(1000, 100), + "nonny", + "POST", + "mysite.com", + 443, + "/v1/api", + None, + None, + ) + .unwrap(); + assert_eq!( + mac.as_ref(), + &[ + 192, 227, 235, 121, 157, 185, 197, 79, 189, 214, 235, 139, 9, 232, 99, 55, 67, 30, + 68, 0, 150, 187, 192, 238, 21, 200, 209, 107, 245, 159, 243, 178 + ] + ); + } +} diff --git a/third_party/rust/rc_crypto/src/hkdf.rs b/third_party/rust/rc_crypto/src/hkdf.rs new file mode 100644 index 0000000000..a87a652d28 --- /dev/null +++ b/third_party/rust/rc_crypto/src/hkdf.rs @@ -0,0 +1,127 @@ +/* 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::{error::*, hmac}; + +pub fn extract_and_expand( + salt: &hmac::SigningKey, + secret: &[u8], + info: &[u8], + out: &mut [u8], +) -> Result<()> { + let prk = extract(salt, secret)?; + expand(&prk, info, out)?; + Ok(()) +} + +pub fn extract(salt: &hmac::SigningKey, secret: &[u8]) -> Result<hmac::SigningKey> { + let prk = hmac::sign(salt, secret)?; + Ok(hmac::SigningKey::new(salt.digest_algorithm(), prk.as_ref())) +} + +pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> { + let mut derived = + nss::pk11::sym_key::hkdf_expand(prk.digest_alg, &prk.key_value, info, out.len())?; + out.swap_with_slice(&mut derived[0..out.len()]); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::digest; + + #[test] + fn hkdf_produces_correct_result() { + let secret = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); + let salt = hex::decode("000102030405060708090a0b0c").unwrap(); + let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); + let expected_out = hex::decode( + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", + ) + .unwrap(); + let salt = hmac::SigningKey::new(&digest::SHA256, &salt); + let mut out = vec![0u8; expected_out.len()]; + extract_and_expand(&salt, &secret, &info, &mut out).unwrap(); + assert_eq!(out, expected_out); + } + + #[test] + fn hkdf_rejects_gigantic_salt() { + if (std::u32::MAX as usize) < std::usize::MAX { + let salt_bytes = vec![0; (std::u32::MAX as usize) + 1]; + let salt = hmac::SigningKey { + digest_alg: &digest::SHA256, + key_value: salt_bytes, + }; + let mut out = vec![0u8; 8]; + assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err()); + } + } + + #[test] + fn hkdf_rejects_gigantic_secret() { + if (std::u32::MAX as usize) < std::usize::MAX { + let salt = hmac::SigningKey::new(&digest::SHA256, b"salt"); + let secret = vec![0; (std::u32::MAX as usize) + 1]; + let mut out = vec![0u8; 8]; + assert!(extract_and_expand(&salt, secret.as_slice(), b"info", &mut out).is_err()); + } + } + + // N.B. the `info `parameter is a `c_ulong`, and I can't figure out how to check whether + // `c_ulong` is smaller than `usize` in order to write a `hkdf_rejects_gigantic_info` test. + + #[test] + fn hkdf_rejects_gigantic_output_buffers() { + let salt = hmac::SigningKey::new(&digest::SHA256, b"salt"); + let mut out = vec![0u8; 8160 + 1]; // RFC maximum (hashlen * 255) + 1 + assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err()); + } + + #[test] + fn hkdf_rejects_zero_length_output_buffer() { + let salt = hmac::SigningKey::new(&digest::SHA256, b"salt"); + let mut out = vec![0u8; 0]; + assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err()); + } + + #[test] + fn hkdf_can_produce_small_output() { + let salt = hmac::SigningKey::new(&digest::SHA256, b"salt"); + let mut out = vec![0u8; 1]; + assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_ok()); + } + + #[test] + fn hkdf_accepts_zero_length_info() { + let salt = hmac::SigningKey::new(&digest::SHA256, b"salt"); + let mut out = vec![0u8; 32]; + assert!(extract_and_expand(&salt, b"secret", b"", &mut out).is_ok()); + } + + #[test] + fn hkdf_expand_rejects_short_prk() { + let prk = hmac::SigningKey::new(&digest::SHA256, b"too short"); // must be >= HashLen + let mut out = vec![0u8; 8]; + assert!(expand(&prk, b"info", &mut out).is_ok()); + } +} diff --git a/third_party/rust/rc_crypto/src/hmac.rs b/third_party/rust/rc_crypto/src/hmac.rs new file mode 100644 index 0000000000..cc3decb128 --- /dev/null +++ b/third_party/rust/rc_crypto/src/hmac.rs @@ -0,0 +1,199 @@ +/* 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::{constant_time, digest, error::*}; + +/// A calculated signature value. +/// This is a type-safe wrappper that discourages attempts at comparing signatures +/// for equality, which might naively be done using a non-constant-time comparison. +#[derive(Clone)] +pub struct Signature(pub(crate) digest::Digest); + +impl AsRef<[u8]> for Signature { + #[inline] + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +/// A key to use for HMAC signing. +pub struct SigningKey { + pub(crate) digest_alg: &'static digest::Algorithm, + pub(crate) key_value: Vec<u8>, +} + +impl SigningKey { + pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self { + SigningKey { + digest_alg, + key_value: key_value.to_vec(), + } + } + + #[inline] + pub fn digest_algorithm(&self) -> &'static digest::Algorithm { + self.digest_alg + } +} + +/// A key to use for HMAC authentication. +pub struct VerificationKey { + wrapped: SigningKey, +} + +impl VerificationKey { + pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self { + VerificationKey { + wrapped: SigningKey::new(digest_alg, key_value), + } + } + + #[inline] + pub fn digest_algorithm(&self) -> &'static digest::Algorithm { + self.wrapped.digest_algorithm() + } +} + +/// Calculate the HMAC of `data` using `key` and verify it corresponds to the provided signature. +pub fn verify(key: &VerificationKey, data: &[u8], signature: &[u8]) -> Result<()> { + verify_with_own_key(&key.wrapped, data, signature) +} + +/// Equivalent to `verify` but allows the consumer to pass a `SigningKey`. +pub fn verify_with_own_key(key: &SigningKey, data: &[u8], signature: &[u8]) -> Result<()> { + constant_time::verify_slices_are_equal(sign(key, data)?.as_ref(), signature) +} + +/// Calculate the HMAC of `data` using `key`. +pub fn sign(key: &SigningKey, data: &[u8]) -> Result<Signature> { + let value = nss::pk11::context::hmac_sign(key.digest_alg, &key.key_value, data)?; + Ok(Signature(digest::Digest { + value, + algorithm: *key.digest_alg, + })) +} + +#[cfg(test)] +mod tests { + use super::*; + + const KEY: &[u8] = b"key"; + const MESSAGE: &[u8] = b"The quick brown fox jumps over the lazy dog"; + const SIGNATURE_HEX: &str = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"; + + #[test] + fn hmac_sign() { + let key = SigningKey::new(&digest::SHA256, KEY); + let signature = sign(&key, MESSAGE).unwrap(); + let expected_signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert_eq!(signature.as_ref(), expected_signature.as_slice()); + assert!(verify_with_own_key(&key, MESSAGE, &expected_signature).is_ok()); + } + + #[test] + fn hmac_sign_gives_different_signatures_for_different_keys() { + let key = SigningKey::new(&digest::SHA256, b"another key"); + let signature = sign(&key, MESSAGE).unwrap(); + let expected_signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert_ne!(signature.as_ref(), expected_signature.as_slice()); + } + + #[test] + fn hmac_sign_gives_different_signatures_for_different_messages() { + let key = SigningKey::new(&digest::SHA256, KEY); + let signature = sign(&key, b"a different message").unwrap(); + let expected_signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert_ne!(signature.as_ref(), expected_signature.as_slice()); + } + + #[test] + fn hmac_verify() { + let key = VerificationKey::new(&digest::SHA256, KEY); + let expected_signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert!(verify(&key, MESSAGE, &expected_signature).is_ok()); + } + + #[test] + fn hmac_verify_fails_with_incorrect_signature() { + let key = VerificationKey::new(&digest::SHA256, KEY); + let signature = hex::decode(SIGNATURE_HEX).unwrap(); + for i in 0..signature.len() { + let mut wrong_signature = signature.clone(); + wrong_signature[i] = wrong_signature[i].wrapping_add(1); + assert!(verify(&key, MESSAGE, &wrong_signature).is_err()); + } + } + + #[test] + fn hmac_verify_fails_with_incorrect_key() { + let key = VerificationKey::new(&digest::SHA256, b"wrong key"); + let signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert!(verify(&key, MESSAGE, &signature).is_err()); + } + + #[test] + fn hmac_sign_cleanly_rejects_gigantic_keys() { + if (std::u32::MAX as usize) < std::usize::MAX { + let key_bytes = vec![0; (std::u32::MAX as usize) + 1]; + // Direct construction of SigningKey to avoid instantiating the array. + let key = SigningKey { + digest_alg: &digest::SHA256, + key_value: key_bytes, + }; + assert!(sign(&key, MESSAGE).is_err()); + } + } + + #[test] + fn hmac_verify_cleanly_rejects_gigantic_keys() { + if (std::u32::MAX as usize) < std::usize::MAX { + let key_bytes = vec![0; (std::u32::MAX as usize) + 1]; + // Direct construction of VerificationKey to avoid instantiating the array. + let key = VerificationKey { + wrapped: SigningKey { + digest_alg: &digest::SHA256, + key_value: key_bytes, + }, + }; + let signature = hex::decode(SIGNATURE_HEX).unwrap(); + assert!(verify(&key, MESSAGE, &signature).is_err()); + } + } + + #[test] + fn hmac_sign_cleanly_rejects_gigantic_messages() { + if (std::u32::MAX as usize) < std::usize::MAX { + let key = SigningKey::new(&digest::SHA256, KEY); + let message = vec![0; (std::u32::MAX as usize) + 1]; + assert!(sign(&key, &message).is_err()); + } + } + + #[test] + fn hmac_verify_cleanly_rejects_gigantic_messages() { + if (std::u32::MAX as usize) < std::usize::MAX { + let key = VerificationKey::new(&digest::SHA256, KEY); + let signature = hex::decode(SIGNATURE_HEX).unwrap(); + let message = vec![0; (std::u32::MAX as usize) + 1]; + assert!(verify(&key, &message, &signature).is_err()); + } + } +} diff --git a/third_party/rust/rc_crypto/src/lib.rs b/third_party/rust/rc_crypto/src/lib.rs new file mode 100644 index 0000000000..923817423f --- /dev/null +++ b/third_party/rust/rc_crypto/src/lib.rs @@ -0,0 +1,68 @@ +/* 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. + +#![allow(unknown_lints)] +#![warn(rust_2018_idioms)] +/// This crate provides all the cryptographic primitives required by +/// this workspace, backed by the NSS library. +/// The exposed API is pretty much the same as the `ring` crate. +pub mod aead; +pub mod agreement; +pub mod constant_time; +pub mod digest; +#[cfg(feature = "ece")] +pub mod ece_crypto; +mod error; +#[cfg(feature = "hawk")] +mod hawk_crypto; +pub mod hkdf; +pub mod hmac; +pub mod pbkdf2; +pub mod rand; +pub mod signature; + +// Expose `hawk` if the hawk feature is on. This avoids consumers needing to +// configure this separately, which is more or less trivial to do incorrectly. +#[cfg(feature = "hawk")] +pub use hawk; + +// Expose `ece` if the ece feature is on. This avoids consumers needing to +// configure this separately, which is more or less trivial to do incorrectly. +#[cfg(feature = "ece")] +pub use ece; + +pub use crate::error::{Error, ErrorKind, Result}; + +/// Only required to be called if you intend to use this library in conjunction +/// with the `hawk` or the `ece` crate. +pub fn ensure_initialized() { + nss::ensure_initialized(); + #[cfg(any(feature = "hawk", feature = "ece"))] + { + static INIT_ONCE: std::sync::Once = std::sync::Once::new(); + INIT_ONCE.call_once(|| { + #[cfg(feature = "hawk")] + crate::hawk_crypto::init(); + #[cfg(feature = "ece")] + crate::ece_crypto::init(); + }); + } +} diff --git a/third_party/rust/rc_crypto/src/pbkdf2.rs b/third_party/rust/rc_crypto/src/pbkdf2.rs new file mode 100644 index 0000000000..37d8ea8497 --- /dev/null +++ b/third_party/rust/rc_crypto/src/pbkdf2.rs @@ -0,0 +1,196 @@ +/* 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/. */ + +use crate::error::*; +use nss::pbkdf2::pbkdf2_key_derive; +pub use nss::pbkdf2::HashAlgorithm; +/// Extend passwords using pbkdf2, based on the following [rfc](https://www.ietf.org/rfc/rfc2898.txt) it runs the NSS implementation +/// # Arguments +/// +/// * `passphrase` - The password to stretch +/// * `salt` - A salt to use in the generation process +/// * `iterations` - The number of iterations the hashing algorithm will run on each section of the key +/// * `hash_algorithm` - The hash algorithm to use +/// * `out` - The slice the algorithm will populate +/// +/// # Examples +/// +/// ``` +/// use rc_crypto::pbkdf2; +/// let password = b"password"; +/// let salt = b"salt"; +/// let mut out = vec![0u8; 32]; +/// let iterations = 2; // Real code should have a MUCH higher number of iterations (Think 1000+) +/// pbkdf2::derive(password, salt, iterations, pbkdf2::HashAlgorithm::SHA256, &mut out).unwrap(); // Oh oh should handle the error! +/// assert_eq!(hex::encode(out), "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"); +// +///``` +/// +/// # Errors +/// +/// Could possibly return an error if the HMAC algorithm fails, or if the NSS algorithm returns an error +pub fn derive( + passphrase: &[u8], + salt: &[u8], + iterations: u32, + hash_algorithm: HashAlgorithm, + out: &mut [u8], +) -> Result<()> { + pbkdf2_key_derive(passphrase, salt, iterations, hash_algorithm, out)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_generate_correct_out() { + let expected = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"; + let mut out = vec![0u8; 32]; + let password = b"password"; + let salt = b"salt"; + derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_longer_key() { + let expected = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b4dbf3a2f3dad3377264bb7b8e8330d4efc7451418617dabef683735361cdc18c"; + let password = b"password"; + let salt = b"salt"; + let mut out = vec![0u8; 64]; + derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_more_iterations() { + let expected = "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"; + let password = b"password"; + let salt = b"salt"; + let mut out = vec![0u8; 32]; + derive(password, salt, 2, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_odd_length() { + let expected = "ad35240ac683febfaf3cd49d845473fbbbaa2437f5f82d5a415ae00ac76c6bfccf"; + let password = b"password"; + let salt = b"salt"; + let mut out = vec![0u8; 33]; + derive(password, salt, 3, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_nulls() { + let expected = "e25d526987819f966e324faa4a"; + let password = b"passw\x00rd"; + let salt = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + let mut out = vec![0u8; 13]; + derive(password, salt, 5, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_password_null() { + let expected = "62384466264daadc4144018c6bd864648272b34da8980d31521ffcce92ae003b"; + let password = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + let salt = b"salt"; + let mut out = vec![0u8; 32]; + derive(password, salt, 2, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_empty_password() { + let expected = "f135c27993baf98773c5cdb40a5706ce6a345cde61b000a67858650cd6a324d7"; + let mut out = vec![0u8; 32]; + let password = b""; + let salt = b"salt"; + derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_empty_salt() { + let expected = "c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab"; + let mut out = vec![0u8; 32]; + let password = b"password"; + let salt = b""; + derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_tiny_out() { + let expected = "12"; + let mut out = vec![0u8; 1]; + let password = b"password"; + let salt = b"salt"; + derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap(); + assert_eq!(expected, hex::encode(out)); + } + + #[test] + fn test_rejects_zero_iterations() { + let mut out = vec![0u8; 32]; + let password = b"password"; + let salt = b"salt"; + assert!(derive(password, salt, 0, HashAlgorithm::SHA256, &mut out).is_err()); + } + + #[test] + fn test_rejects_empty_out() { + let mut out = vec![0u8; 0]; + let password = b"password"; + let salt = b"salt"; + assert!(derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err()); + } + + #[test] + fn test_rejects_gaigantic_salt() { + if (std::u32::MAX as usize) < std::usize::MAX { + let salt = vec![0; (std::u32::MAX as usize) + 1]; + let mut out = vec![0u8; 1]; + let password = b"password"; + assert!(derive(password, &salt, 1, HashAlgorithm::SHA256, &mut out).is_err()); + } + } + #[test] + fn test_rejects_gaigantic_password() { + if (std::u32::MAX as usize) < std::usize::MAX { + let password = vec![0; (std::u32::MAX as usize) + 1]; + let mut out = vec![0u8; 1]; + let salt = b"salt"; + assert!(derive(&password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err()); + } + } + + #[test] + fn test_rejects_gaigantic_out() { + if (std::u32::MAX as usize) < std::usize::MAX { + let password = b"password"; + let mut out = vec![0; (std::u32::MAX as usize) + 1]; + let salt = b"salt"; + assert!(derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err()); + } + } + + #[test] + fn test_rejects_gaigantic_iterations() { + let password = b"password"; + let mut out = vec![0; 32]; + let salt = b"salt"; + assert!(derive( + password, + salt, + std::u32::MAX, + HashAlgorithm::SHA256, + &mut out + ) + .is_err()); + } +} diff --git a/third_party/rust/rc_crypto/src/rand.rs b/third_party/rust/rc_crypto/src/rand.rs new file mode 100644 index 0000000000..4d3dc5df4c --- /dev/null +++ b/third_party/rust/rc_crypto/src/rand.rs @@ -0,0 +1,70 @@ +/* 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::error::*; + +/// Fill a buffer with cryptographically secure pseudo-random data. +pub fn fill(dest: &mut [u8]) -> Result<()> { + Ok(nss::pk11::slot::generate_random(dest)?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn random_fill() { + let mut out = vec![0u8; 64]; + assert!(fill(&mut out).is_ok()); + // This check could *in theory* fail if we randomly generate all zeroes + // but we're treating that probability as negligible in practice. + assert_ne!(out, vec![0u8; 64]); + + let mut out2 = vec![0u8; 64]; + assert!(fill(&mut out2).is_ok()); + assert_ne!(out, vec![0u8; 64]); + assert_ne!(out2, out); + } + + #[test] + fn random_fill_empty() { + let mut out = vec![0u8; 0]; + assert!(fill(&mut out).is_ok()); + assert_eq!(out, vec![0u8; 0]); + } + + #[test] + fn random_fill_oddly_sized_arrays() { + let sizes: [usize; 4] = [61, 63, 65, 67]; + for size in &sizes { + let mut out = vec![0u8; *size]; + assert!(fill(&mut out).is_ok()); + assert_ne!(out, vec![0u8; *size]); + } + } + + #[test] + fn random_fill_rejects_attempts_to_fill_gigantic_arrays() { + let max_size: usize = std::i32::MAX as usize; + let mut out = vec![0u8; max_size + 1]; + assert!(fill(&mut out).is_err()); + } +} diff --git a/third_party/rust/rc_crypto/src/signature.rs b/third_party/rust/rc_crypto/src/signature.rs new file mode 100644 index 0000000000..8ad15fb0f0 --- /dev/null +++ b/third_party/rust/rc_crypto/src/signature.rs @@ -0,0 +1,111 @@ +/* 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::Result; +use nss::{ec::Curve, ec::PublicKey, pbkdf2::HashAlgorithm}; + +/// A signature verification algorithm. +pub struct VerificationAlgorithm { + curve: Curve, + digest_alg: HashAlgorithm, +} + +pub static ECDSA_P256_SHA256: VerificationAlgorithm = VerificationAlgorithm { + curve: Curve::P256, + digest_alg: HashAlgorithm::SHA256, +}; + +pub static ECDSA_P384_SHA384: VerificationAlgorithm = VerificationAlgorithm { + curve: Curve::P384, + digest_alg: HashAlgorithm::SHA384, +}; + +/// An unparsed public key for signature operations. +pub struct UnparsedPublicKey<'a> { + alg: &'static VerificationAlgorithm, + bytes: &'a [u8], +} + +impl<'a> UnparsedPublicKey<'a> { + pub fn new(algorithm: &'static VerificationAlgorithm, bytes: &'a [u8]) -> Self { + Self { + alg: algorithm, + bytes, + } + } + + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + let pub_key = PublicKey::from_bytes(self.alg.curve, self.bytes)?; + Ok(pub_key.verify(message, signature, self.alg.digest_alg)?) + } + + pub fn algorithm(&self) -> &'static VerificationAlgorithm { + self.alg + } + + pub fn bytes(&self) -> &'a [u8] { + &self.bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ecdsa_p384_sha384_verify() { + // Test generated with JS DOM's WebCrypto. + let pub_key_bytes = base64::decode_config( + "BMZj_xHOfLQn5DIEQcYUkyASDWo8O30gWdkWXHHHWN5owKhGWplYHEb4PLf3DkFTg_smprr-ApdULy3NV10x8IZ0EfVaUZdXvTquH1kiw2PxD7fhqiozMXUaSuZI5KBE6w", + base64::URL_SAFE_NO_PAD + ).unwrap(); + let message = base64::decode_config( + "F9MQDmEEdvOfm-NkCRrXqG-aVA9kq0xqtjvtWLndmmt6bO2gfLE2CVDDLzJYds0n88uz27c5JkzdsLpm5HP3aLFgD8bgnGm-EgdBpm99CRiIm7mAMbb0-NRAyUxeoGmdgJPVQLWFNoHRwzKV2wZ0Bk-Bq7jkeDHmDfnx-CJKVMQ", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(); + let signature = base64::decode_config( + "XLZmtJweW4qx0u0l6EpfmB5z-S-CNj4mrl9d7U0MuftdNPhmlNacV4AKR-i4uNn0TUIycU7GsfIjIqxuiL9WdAnfq_KH_SJ95mduqXgWNKlyt8JgMLd4h-jKOllh4erh", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(); + let public_key = + crate::signature::UnparsedPublicKey::new(&ECDSA_P384_SHA384, &pub_key_bytes); + + // Failure case: Wrong key algorithm. + let public_key_wrong_alg = + crate::signature::UnparsedPublicKey::new(&ECDSA_P256_SHA256, &pub_key_bytes); + assert!(public_key_wrong_alg.verify(&message, &signature).is_err()); + + // Failure case: Add garbage to signature. + let mut garbage_signature = signature.clone(); + garbage_signature.push(42); + assert!(public_key.verify(&message, &garbage_signature).is_err()); + + // Failure case: Flip a bit in message. + let mut garbage_message = message.clone(); + garbage_message[42] = 42; + assert!(public_key.verify(&garbage_message, &signature).is_err()); + + // Happy case. + assert!(public_key.verify(&message, &signature).is_ok()); + } +} |