Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wacore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ js = ["getrandom/wasm_js"]

[dependencies]
aes = { workspace = true }
aes-gcm = { workspace = true }
anyhow = { workspace = true }
async-channel = { workspace = true }
async-lock = { workspace = true }
Expand Down Expand Up @@ -55,6 +54,7 @@ wacore-noise = { workspace = true }
waproto = { workspace = true }

[dev-dependencies]
aes-gcm = { workspace = true }
futures = { workspace = true, features = ["executor"] }
iai-callgrind = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt"] }
Expand Down
1 change: 0 additions & 1 deletion wacore/libsignal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ description = "Signal Protocol implementation for the WhatsApp platform"

[dependencies]
aes = { workspace = true }
aes-gcm = { workspace = true }
arrayref = "0.3.9"
async-trait = { workspace = true }
cbc = { version = "0.2", features = ["alloc"] }
Expand Down
64 changes: 11 additions & 53 deletions wacore/libsignal/src/crypto/aes_cbc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

use std::result::Result;

use aes::Aes256;
use aes::cipher::block_padding::Pkcs7;
use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
use crate::crypto::provider::provider;

#[derive(Debug, displaydoc::Display, thiserror::Error)]
pub enum EncryptionError {
Expand All @@ -31,35 +29,11 @@ pub fn aes_256_cbc_encrypt_into(
iv: &[u8],
output: &mut Vec<u8>,
) -> Result<(), EncryptionError> {
// Calculate the space needed for encryption + PKCS7 padding
// PKCS7 padding can add 1-16 bytes (always adds at least 1 byte)
let padding_needed = 16 - (ptext.len() % 16);
let encrypted_size = ptext.len() + padding_needed;

let start_pos = output.len();

// Reserve space for the encrypted data
output.resize(start_pos + encrypted_size, 0);

// Copy plaintext to the buffer
output[start_pos..start_pos + ptext.len()].copy_from_slice(ptext);

// Create encryptor and encrypt in place
let encryptor = cbc::Encryptor::<Aes256>::new_from_slices(key, iv)
.map_err(|_| EncryptionError::BadKeyOrIv)?;

// Encrypt the data in place with proper padding
let encrypted_len = {
let encrypted_slice = encryptor
.encrypt_padded::<Pkcs7>(&mut output[start_pos..], ptext.len())
.map_err(|_| EncryptionError::BadPadding)?;
encrypted_slice.len()
};

// Resize to actual encrypted length
output.truncate(start_pos + encrypted_len);

Ok(())
let key: &[u8; 32] = key.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;
let iv: &[u8; 16] = iv.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;
provider()
.aes_256_cbc_encrypt(key, iv, ptext, output)
.map_err(|_| EncryptionError::BadPadding)
}

/// The output buffer is cleared and filled with the decrypted plaintext.
Expand All @@ -69,27 +43,11 @@ pub fn aes_256_cbc_decrypt_into(
iv: &[u8],
output: &mut Vec<u8>,
) -> Result<(), DecryptionError> {
if ctext.is_empty() || !ctext.len().is_multiple_of(16) {
return Err(DecryptionError::BadCiphertext(
"ciphertext length must be a non-zero multiple of 16",
));
}

output.clear();
output.reserve(ctext.len());
output.extend_from_slice(ctext);

let decryptor = cbc::Decryptor::<Aes256>::new_from_slices(key, iv)
.map_err(|_| DecryptionError::BadKeyOrIv)?;

let decrypted = decryptor
.decrypt_padded::<Pkcs7>(output)
.map_err(|_| DecryptionError::BadCiphertext("failed to decrypt"))?;

let decrypted_len = decrypted.len();
output.truncate(decrypted_len);

Ok(())
let key: &[u8; 32] = key.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
let iv: &[u8; 16] = iv.try_into().map_err(|_| DecryptionError::BadKeyOrIv)?;
provider()
.aes_256_cbc_decrypt(key, iv, ctext, output)
.map_err(|_| DecryptionError::BadCiphertext("failed to decrypt"))
Comment on lines +48 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve backend failures in CBC decrypt error mapping

When a custom SignalCryptoProvider returns CryptoProviderError::BackendFailed from CBC decrypt, this wrapper collapses it into BadCiphertext("failed to decrypt"), making provider/runtime outages indistinguishable from corrupted ciphertext. In downstream paths (for example protocol/session_cipher.rs), BadCiphertext is handled as an invalid incoming message, so a transient backend failure can cause valid messages to be dropped instead of surfacing an operational crypto-provider error that can be retried or failed fast.

Useful? React with 👍 / 👎.

}

#[cfg(test)]
Expand Down
38 changes: 37 additions & 1 deletion wacore/libsignal/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ mod hash;

mod aes_cbc;
mod aes_ctr;
mod aes_gcm;
pub(crate) mod aes_gcm;
mod provider;

pub use aes_cbc::{
DecryptionError, EncryptionError, aes_256_cbc_decrypt_into, aes_256_cbc_encrypt_into,
Expand All @@ -19,3 +20,38 @@ pub use error::{Error, Result};
pub use hash::{
CryptographicHash, CryptographicMac, SHA1_OUTPUT_SIZE, SHA256_OUTPUT_SIZE, SHA512_OUTPUT_SIZE,
};
pub use provider::{
CryptoProviderError, RustCryptoProvider, SignalCryptoProvider, set_crypto_provider,
};

/// AES-256-GCM seal. Appends `ciphertext || tag(16)` to `out`.
/// Delegates to the active [`SignalCryptoProvider`].
#[inline]
pub fn aes_256_gcm_encrypt(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
out: &mut Vec<u8>,
) -> std::result::Result<(), CryptoProviderError> {
provider::provider().aes_256_gcm_encrypt(key, nonce, aad, plaintext, out)
}

/// AES-256-GCM open. `ciphertext_with_tag` must end with the 16-byte tag.
/// Appends plaintext to `out` on success.
#[inline]
pub fn aes_256_gcm_decrypt(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
out: &mut Vec<u8>,
) -> std::result::Result<(), CryptoProviderError> {
provider::provider().aes_256_gcm_decrypt(key, nonce, aad, ciphertext_with_tag, out)
}

/// HMAC-SHA256 one-shot. Delegates to the active [`SignalCryptoProvider`].
#[inline]
pub fn hmac_sha256(key: &[u8], input: &[u8]) -> [u8; 32] {
provider::provider().hmac_sha256(key, input)
}
Loading
Loading