From 1858813d474ad71e875c08765cf3f7969ab08fd8 Mon Sep 17 00:00:00 2001 From: boxdot Date: Wed, 3 Jan 2024 17:39:00 +0100 Subject: [PATCH] Upgrade aes 0.7 -> 0.8 (#247) * Port attachment cipher to aes 8 * Port envelope decryption to aes 8 * Port provisioning cipher to aes 8 * Use Iso7816 padding from aes8 * Port account management cipher to aes 8 * Port profile cipher to aes gcm 0.10 * Undo renaming of crates * Replace try_into by GenericArray::from_slice * replace GenericArray::from_slice by just into() * remove alloc from hmac calc --- libsignal-service/Cargo.toml | 7 ++-- libsignal-service/src/account_manager.rs | 43 +++++++++++--------- libsignal-service/src/attachment_cipher.rs | 17 ++++---- libsignal-service/src/cipher.rs | 10 ++--- libsignal-service/src/envelope.rs | 37 ++++++++++++++--- libsignal-service/src/profile_cipher.rs | 32 ++++++--------- libsignal-service/src/provisioning/cipher.rs | 19 ++++----- libsignal-service/src/push_service.rs | 2 +- 8 files changed, 92 insertions(+), 75 deletions(-) diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 77451cca1..6c6a4b6b6 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -10,12 +10,13 @@ readme = "../README.md" libsignal-protocol = { git = "https://github.com/signalapp/libsignal", tag = "v0.32.0" } zkgroup = { git = "https://github.com/signalapp/libsignal", tag = "v0.32.0" } -aes = { version = "0.7", features = ["ctr"] } -aes-gcm = "0.9" +aes = "0.8" +aes-gcm = "0.10" +cbc = "0.1" +ctr = "0.9" async-trait = "0.1" base64 = "0.13" bincode = "1.3" -block-modes = "0.8" bytes = "1" chrono = { version = "0.4", features = ["serde", "clock"], default-features = false } derivative = "2.2" diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 35dc382dc..5dac2b1b5 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; -use aes::cipher::generic_array::GenericArray; -use aes::cipher::{NewCipher, StreamCipher}; -use aes::Aes256Ctr; +use aes::cipher::{KeyIvInit, StreamCipher as _}; +use hmac::digest::Output; use hmac::{Hmac, Mac}; use libsignal_protocol::{ kem, GenericSignedPreKey, IdentityKeyStore, KeyPair, KyberPreKeyRecord, @@ -33,6 +32,8 @@ use crate::{ utils::serde_base64, }; +type Aes256Ctr128BE = ctr::Ctr128BE; + pub struct AccountManager { service: Service, profile_key: Option, @@ -524,11 +525,11 @@ impl AccountManager { fn calculate_hmac256( mac_key: &[u8], ciphertext: &[u8], -) -> Result, ServiceError> { +) -> Result>, ServiceError> { let mut mac = Hmac::::new_from_slice(mac_key) .map_err(|_| ServiceError::MacError)?; mac.update(ciphertext); - Ok(mac.finalize().into_bytes().to_vec()) + Ok(mac.finalize().into_bytes()) } pub fn encrypt_device_name( @@ -544,17 +545,16 @@ pub fn encrypt_device_name( .calculate_agreement(identity_public)?; let key1 = calculate_hmac256(&master_secret, b"auth")?; - let mut synthetic_iv = calculate_hmac256(&key1, &plaintext)?; - synthetic_iv.truncate(16); + let synthetic_iv = calculate_hmac256(&key1, &plaintext)?; + let synthetic_iv = &synthetic_iv[..16]; let key2 = calculate_hmac256(&master_secret, b"cipher")?; - let cipher_key = calculate_hmac256(&key2, &synthetic_iv)?; + let cipher_key = calculate_hmac256(&key2, synthetic_iv)?; let mut ciphertext = plaintext; - let mut cipher = Aes256Ctr::new( - GenericArray::from_slice(&cipher_key), - GenericArray::from_slice(&[0u8; 16]), - ); + + const IV: [u8; 16] = [0; 16]; + let mut cipher = Aes256Ctr128BE::new(&cipher_key, &IV.into()); cipher.apply_keystream(&mut ciphertext); let device_name = DeviceName { @@ -581,24 +581,27 @@ pub fn decrypt_device_name( return Err(ServiceError::InvalidDeviceName); }; + let synthetic_iv: [u8; 16] = synthetic_iv[..synthetic_iv.len().min(16)] + .try_into() + .map_err(|_| ServiceError::MacError)?; + let ephemeral_public = PublicKey::deserialize(ephemeral_public)?; let master_secret = private_key.calculate_agreement(&ephemeral_public)?; let key2 = calculate_hmac256(&master_secret, b"cipher")?; - let cipher_key = calculate_hmac256(&key2, synthetic_iv)?; + let cipher_key = calculate_hmac256(&key2, &synthetic_iv)?; let mut plaintext = ciphertext.to_vec(); - let mut cipher = Aes256Ctr::new( - GenericArray::from_slice(&cipher_key), - GenericArray::from_slice(&[0u8; 16]), - ); + const IV: [u8; 16] = [0; 16]; + let mut cipher = + Aes256Ctr128BE::new(cipher_key.as_slice().into(), &IV.into()); cipher.apply_keystream(&mut plaintext); let key1 = calculate_hmac256(&master_secret, b"auth")?; - let mut our_synthetic_iv = calculate_hmac256(&key1, &plaintext)?; - our_synthetic_iv.truncate(16); + let our_synthetic_iv = calculate_hmac256(&key1, &plaintext)?; + let our_synthetic_iv = &our_synthetic_iv[..16]; - if synthetic_iv != &our_synthetic_iv { + if synthetic_iv != our_synthetic_iv { Err(ServiceError::MacError) } else { Ok(String::from_utf8_lossy(&plaintext).to_string()) diff --git a/libsignal-service/src/attachment_cipher.rs b/libsignal-service/src/attachment_cipher.rs index 65026538e..06eeb2c6e 100644 --- a/libsignal-service/src/attachment_cipher.rs +++ b/libsignal-service/src/attachment_cipher.rs @@ -1,8 +1,11 @@ -use aes::Aes256; -use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; +use aes::cipher::block_padding::Pkcs7; +use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use hmac::{Hmac, Mac}; use sha2::Sha256; +type Aes256CbcEnc = cbc::Encryptor; +type Aes256CbcDec = cbc::Decryptor; + #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum AttachmentCipherError { #[error("MAC verification error")] @@ -30,12 +33,11 @@ pub fn encrypt_in_place(iv: [u8; 16], key: [u8; 64], plaintext: &mut Vec) { // Pad with zeroes for padding plaintext.extend(&[0u8; 16]); - let cipher = Cbc::::new_from_slices(aes_half, &iv) - .expect("fixed length key material"); + let cipher = Aes256CbcEnc::new(aes_half.into(), &iv.into()); let buffer = plaintext; let ciphertext_slice = cipher - .encrypt(&mut buffer[16..], plaintext_len) + .encrypt_padded_mut::(&mut buffer[16..], plaintext_len) .expect("encrypted ciphertext"); let ciphertext_len = ciphertext_slice.len(); // Correct length for padding @@ -71,11 +73,10 @@ pub fn decrypt_in_place( let (iv, buffer) = buffer.split_at_mut(16); - let cipher = Cbc::::new_from_slices(aes_half, iv) - .expect("fixed length key material"); + let cipher = Aes256CbcDec::new(aes_half.into(), (&*iv).into()); let plaintext_slice = cipher - .decrypt(buffer) + .decrypt_padded_mut::(buffer) .map_err(|_| AttachmentCipherError::PaddingError)?; let plaintext_len = plaintext_slice.len(); diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 8154cad54..aa01385df 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -1,6 +1,6 @@ use std::{convert::TryFrom, time::SystemTime}; -use block_modes::block_padding::{Iso7816, Padding}; +use aes::cipher::block_padding::{Iso7816, RawPadding}; use libsignal_protocol::{ group_decrypt, message_decrypt_prekey, message_decrypt_signal, message_encrypt, process_sender_key_distribution_message, @@ -358,11 +358,7 @@ fn add_padding(version: u32, contents: &[u8]) -> Result, ServiceError> { let mut buffer = vec![0u8; message_length_with_padding]; buffer[..message_length].copy_from_slice(contents); - Iso7816::pad_block(&mut buffer, message_length).map_err(|e| { - ServiceError::InvalidFrameError { - reason: format!("Invalid message padding: {:?}", e), - } - })?; + Iso7816::raw_pad(&mut buffer, message_length); Ok(buffer) } } @@ -386,7 +382,7 @@ fn strip_padding_version( #[allow(clippy::comparison_chain)] fn strip_padding(contents: &mut Vec) -> Result<(), ServiceError> { - let new_length = Iso7816::unpad(contents) + let new_length = Iso7816::raw_unpad(contents) .map_err(|e| ServiceError::InvalidFrameError { reason: format!("Invalid message padding: {:?}", e), })? diff --git a/libsignal-service/src/envelope.rs b/libsignal-service/src/envelope.rs index 8e9d7be00..c90a36330 100644 --- a/libsignal-service/src/envelope.rs +++ b/libsignal-service/src/envelope.rs @@ -1,5 +1,7 @@ use std::convert::{TryFrom, TryInto}; +use aes::cipher::block_padding::Pkcs7; +use aes::cipher::{BlockDecryptMut, KeyIvInit}; use prost::Message; use uuid::Uuid; @@ -65,15 +67,15 @@ impl Envelope { return Err(ServiceError::MacError); } - use aes::Aes256; // libsignal-service-java uses Pkcs5, // but that should not matter. // https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding - use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; - let cipher = Cbc::::new_from_slices(aes_key, iv) - .expect("initalization of CBC/AES/PKCS7"); + let cipher = + cbc::Decryptor::::new(aes_key.into(), iv.into()); let input = &input[CIPHERTEXT_OFFSET..(input.len() - MAC_SIZE)]; - let input = cipher.decrypt_vec(input).expect("decryption"); + let input = cipher + .decrypt_padded_vec_mut::(input) + .expect("decryption"); log::trace!("Envelope::decrypt: decrypted, decoding"); @@ -213,6 +215,29 @@ mod tests { ]; let signaling_key = [0u8; 52]; - let _ = Envelope::decrypt(&body, &signaling_key, true).unwrap(); + let envelope = Envelope::decrypt(&body, &signaling_key, true).unwrap(); + assert_eq!(envelope.server_timestamp(), 1594373582421); + assert_eq!(envelope.timestamp(), 1594373580977); + assert_eq!( + envelope.content(), + [ + 51, 10, 33, 5, 239, 254, 183, 191, 204, 223, 85, 150, 43, 192, + 240, 57, 46, 189, 153, 7, 48, 17, 9, 166, 185, 157, 205, 181, + 66, 235, 99, 221, 114, 58, 187, 117, 16, 76, 24, 0, 34, 160, 1, + 85, 61, 73, 83, 99, 213, 160, 109, 122, 125, 204, 137, 178, + 237, 146, 87, 183, 107, 33, 213, 234, 64, 152, 132, 122, 173, + 25, 33, 4, 65, 20, 134, 117, 62, 116, 80, 151, 18, 132, 187, + 101, 235, 208, 74, 78, 214, 66, 59, 71, 171, 124, 167, 217, + 157, 36, 194, 156, 12, 50, 239, 185, 230, 253, 38, 107, 106, + 149, 194, 39, 214, 35, 245, 58, 216, 250, 225, 150, 170, 26, + 241, 153, 133, 173, 197, 194, 27, 127, 56, 77, 119, 242, 26, + 252, 168, 61, 221, 44, 76, 128, 69, 27, 203, 6, 173, 193, 179, + 69, 27, 243, 36, 185, 181, 157, 41, 23, 72, 113, 40, 209, 46, + 189, 63, 167, 156, 148, 118, 76, 153, 91, 40, 179, 180, 245, + 193, 123, 180, 47, 115, 220, 191, 148, 245, 116, 32, 194, 232, + 55, 13, 0, 217, 52, 116, 21, 48, 244, 17, 222, 26, 240, 31, + 236, 199, 237, 94, 255, 93, 137, 192, + ] + ); } } diff --git a/libsignal-service/src/profile_cipher.rs b/libsignal-service/src/profile_cipher.rs index 1f9381f60..a568b09d6 100644 --- a/libsignal-service/src/profile_cipher.rs +++ b/libsignal-service/src/profile_cipher.rs @@ -1,11 +1,6 @@ -use aes_gcm::{ - aead::{ - generic_array::typenum::U32, generic_array::GenericArray, Aead, - AeadInPlace, - }, - Aes256Gcm, NewAead, -}; -use rand::Rng; +use std::convert::TryInto; + +use aes_gcm::{aead::Aead, AeadCore, AeadInPlace, Aes256Gcm, KeyInit}; use zkgroup::profiles::ProfileKey; use crate::profile_name::ProfileName; @@ -87,10 +82,6 @@ impl ProfileCipher { self.profile_key } - fn get_key(&self) -> GenericArray { - GenericArray::from(self.profile_key.get_bytes()) - } - fn pad_and_encrypt( &self, mut bytes: Vec, @@ -98,16 +89,15 @@ impl ProfileCipher { ) -> Result, ProfileCipherError> { let _len = pad_plaintext(&mut bytes, padding_brackets)?; - let cipher = Aes256Gcm::new(&self.get_key()); - let nonce: [u8; 12] = rand::thread_rng().gen(); - let nonce = GenericArray::from_slice(&nonce); + let cipher = Aes256Gcm::new(&self.profile_key.get_bytes().into()); + let nonce = Aes256Gcm::generate_nonce(rand::thread_rng()); cipher - .encrypt_in_place(nonce, b"", &mut bytes) + .encrypt_in_place(&nonce, b"", &mut bytes) .map_err(|_| ProfileCipherError::EncryptionError)?; let mut concat = Vec::with_capacity(nonce.len() + bytes.len()); - concat.extend_from_slice(nonce); + concat.extend_from_slice(&nonce); concat.extend_from_slice(&bytes); Ok(concat) } @@ -117,11 +107,13 @@ impl ProfileCipher { bytes: impl AsRef<[u8]>, ) -> Result, ProfileCipherError> { let bytes = bytes.as_ref(); - let nonce = GenericArray::from_slice(&bytes[0..12]); - let cipher = Aes256Gcm::new(&self.get_key()); + let nonce: [u8; 12] = bytes[0..12] + .try_into() + .expect("fixed length nonce material"); + let cipher = Aes256Gcm::new(&self.profile_key.get_bytes().into()); let mut plaintext = cipher - .decrypt(nonce, &bytes[12..]) + .decrypt(&nonce.into(), &bytes[12..]) .map_err(|_| ProfileCipherError::EncryptionError)?; // Unpad diff --git a/libsignal-service/src/provisioning/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index 0e9e054d7..1200c9d98 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -1,7 +1,8 @@ use std::fmt::{self, Debug}; +use aes::cipher::block_padding::Pkcs7; +use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use aes::Aes256; -use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; use bytes::Bytes; use hmac::{Hmac, Mac}; use libsignal_protocol::{KeyPair, PublicKey}; @@ -101,9 +102,8 @@ impl ProvisioningCipher { let mac_key = &shared_secrets[32..]; let iv: [u8; IV_LENGTH] = rng.gen(); - let cipher = Cbc::::new_from_slices(aes_key, &iv) - .expect("initalization of CBC/AES/PKCS7"); - let ciphertext = cipher.encrypt_vec(&msg); + let cipher = cbc::Encryptor::::new(aes_key.into(), &iv.into()); + let ciphertext = cipher.encrypt_padded_vec_mut::(&msg); let mut mac = Hmac::::new_from_slice(mac_key) .expect("HMAC can take any size key"); mac.update(&[VERSION]); @@ -176,13 +176,12 @@ impl ProvisioningCipher { // libsignal-service-java uses Pkcs5, // but that should not matter. // https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding - let cipher = Cbc::::new_from_slices(parts1, iv) - .expect("initalization of CBC/AES/PKCS7"); - let input = cipher.decrypt_vec(cipher_text).map_err(|e| { - ProvisioningError::InvalidData { + let cipher = cbc::Decryptor::::new(parts1.into(), iv.into()); + let input = cipher + .decrypt_padded_vec_mut::(cipher_text) + .map_err(|e| ProvisioningError::InvalidData { reason: format!("CBC/Padding error: {:?}", e).into(), - } - })?; + })?; Ok(prost::Message::decode(Bytes::from(input))?) } diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 41cd0fe83..155eb2919 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -1001,7 +1001,7 @@ pub trait PushService: MaybeSend { "/v1/devices/link", &[], HttpAuthOverride::Identified(http_auth), - &link_request, + link_request, ) .await }