Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 1 addition & 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
2 changes: 1 addition & 1 deletion wacore/libsignal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ description = "Signal Protocol implementation for the WhatsApp platform"

[dependencies]
aes = { workspace = true }
aes-gcm = { workspace = true }
arrayref = "0.3.9"
async-trait = { workspace = true }
bytes = { workspace = true }
cbc = { version = "0.2", features = ["alloc"] }
chrono = { workspace = true, features = ["now"] }
ctr = { workspace = true }
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
71 changes: 70 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,71 @@ pub use error::{Error, Result};
pub use hash::{
CryptographicHash, CryptographicMac, SHA1_OUTPUT_SIZE, SHA256_OUTPUT_SIZE, SHA512_OUTPUT_SIZE,
};
pub use provider::{
CryptoProviderError, GcmInPlaceBuffer, 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)
}

/// In-place AES-256-GCM seal. On entry `buffer` holds the plaintext; on return
/// it holds `ciphertext || tag` (length grown by 16). Zero allocations with
/// the default [`RustCryptoProvider`].
#[inline]
pub fn aes_256_gcm_encrypt_in_place<B: GcmInPlaceBuffer>(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
buffer: &mut B,
) -> std::result::Result<(), CryptoProviderError> {
provider::provider().aes_256_gcm_encrypt_in_place(key, nonce, aad, buffer)
}

/// In-place AES-256-GCM open. On entry `buffer` holds `ciphertext || tag`; on
/// success it holds plaintext (length shrunk by 16).
///
/// On authentication failure ([`CryptoProviderError::AuthFailed`]) the buffer
/// is left in an **indeterminate** state: its length is unchanged but the
/// first `buffer.len() - 16` bytes contain the CTR-XOR output (pseudo-
/// plaintext derived from forged ciphertext) rather than the original
/// ciphertext. Callers **must not** reuse the buffer contents — discard or
/// reinitialize it, and treat the session as compromised.
#[inline]
pub fn aes_256_gcm_decrypt_in_place<B: GcmInPlaceBuffer>(
key: &[u8; 32],
nonce: &[u8; 12],
aad: &[u8],
buffer: &mut B,
) -> std::result::Result<(), CryptoProviderError> {
provider::provider().aes_256_gcm_decrypt_in_place(key, nonce, aad, buffer)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading
Loading