Skip to content
Open
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ bouncycastle-mlkem = { path = "./crypto/mlkem" }
bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory" }
bouncycastle-mldsa = { path = "./crypto/mldsa" }
bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory" }
bouncycastle-padding = { path = "./crypto/padding" }
bouncycastle-rng = { path = "./crypto/rng" }
bouncycastle-sha2 = { path = "./crypto/sha2" }
bouncycastle-sha3 = { path = "./crypto/sha3" }
Expand Down Expand Up @@ -51,6 +52,7 @@ bouncycastle-mldsa.workspace = true
bouncycastle-mldsa-lowmemory.workspace = true
bouncycastle-mlkem.workspace = true
bouncycastle-mlkem-lowmemory.workspace = true
bouncycastle-padding.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
104 changes: 75 additions & 29 deletions crypto/core-test-framework/src/symmetric_ciphers.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! Generic behaviour tests for the symmetric cipher traits.

use crate::DUMMY_SEED;
use bouncycastle_core::errors::SymmetricCipherError;
use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError};
use bouncycastle_core::key_material::{
KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
};
use bouncycastle_core::traits::{
AEADCipher, BlockCipher, SecurityStrength, StreamCipher, SymmetricCipher,
AEADCipher, BlockCipherDecryptor, BlockCipherEncryptor, SecurityStrength, StreamCipher,
SymmetricCipher,
};

/// Instance of the test framework.
Expand Down Expand Up @@ -85,9 +86,13 @@ impl TestFrameworkSymmetricCipher {
];
for ss in security_strengths.iter() {
// Tag the key at an arbitrary strength for the purpose of this test. Inside a
// do_hazardous_operations() closure, set_security_strength() raises the strength
// (and bypasses the key-length guard) without complaining.
do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap();
// do_hazardous_operations() closure, set_security_strength() may raise the strength,
// but it still refuses a strength the key length cannot support; skip those.
match do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())) {
Ok(()) => {}
Err(KeyMaterialError::SecurityStrength(_)) => continue,
Err(e) => panic!("unexpected error tagging key strength: {e:?}"),
}

match C::encrypt_out(&key, msg, &mut ct) {
Ok(_) => {
Expand Down Expand Up @@ -124,7 +129,8 @@ impl TestFrameworkBlockCipher {
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
const BLOCK_LEN: usize,
C: BlockCipher<KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>,
E: BlockCipherEncryptor<KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>,
D: BlockCipherDecryptor<KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>,
>(
&self,
) {
Expand All @@ -135,42 +141,74 @@ impl TestFrameworkBlockCipher {
.unwrap();

// to test blocks, we'll chunk our dummy seed
let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap();
let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap();
let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();

// one block at a time (N = 1)
for msg_chunk in DUMMY_SEED.as_chunks::<BLOCK_LEN>().0.iter() {
let ct = encryptor.do_encrypt_block(msg_chunk).unwrap();
let pt = decryptor.do_decrypt_block(&ct).unwrap();
let ct = encryptor.do_encrypt_blocks(&[*msg_chunk]).unwrap();
let [pt] = decryptor.do_decrypt_blocks(&ct).unwrap();
assert_eq!(msg_chunk, &pt);
}

// do it again using the _out versions

let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap();
let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap();
let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();

let mut ct = [0u8; BLOCK_LEN];
let mut pt = [0u8; BLOCK_LEN];
let mut ct = [[0u8; BLOCK_LEN]; 1];
let mut pt = [[0u8; BLOCK_LEN]; 1];
for msg_chunk in DUMMY_SEED.as_chunks::<BLOCK_LEN>().0.iter() {
let ct_bytes_written = encryptor.do_encrypt_block_out(msg_chunk, &mut ct).unwrap();
let ct_bytes_written = encryptor.do_encrypt_blocks_out(&[*msg_chunk], &mut ct).unwrap();
assert_eq!(ct_bytes_written, BLOCK_LEN);

let pt_bytes_written = decryptor.do_decrypt_block_out(&ct, &mut pt).unwrap();
let pt_bytes_written = decryptor.do_decrypt_blocks_out(&ct, &mut pt).unwrap();
assert_eq!(pt_bytes_written, BLOCK_LEN);

assert_eq!(msg_chunk, &pt);
assert_eq!(msg_chunk, &pt[0]);
}

// multi-block (N = 2): blocks encrypted together must decrypt both together and one at a time,
// and blocks encrypted one at a time must decrypt together.
let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();

let mut ct = [[0u8; BLOCK_LEN]; 2];
let mut pt = [[0u8; BLOCK_LEN]; 2];
for msg_pair in DUMMY_SEED.as_chunks::<BLOCK_LEN>().0.as_chunks::<2>().0.iter() {
// encrypt together, decrypt together (by value)
let ct_by_value = encryptor.do_encrypt_blocks(msg_pair).unwrap();
let pt_by_value = decryptor.do_decrypt_blocks(&ct_by_value).unwrap();
assert_eq!(msg_pair, &pt_by_value);

// encrypt together (_out), decrypt one at a time
let ct_bytes_written = encryptor.do_encrypt_blocks_out(msg_pair, &mut ct).unwrap();
assert_eq!(ct_bytes_written, 2 * BLOCK_LEN);
for (msg_chunk, ct_chunk) in msg_pair.iter().zip(ct.iter()) {
let [pt] = decryptor.do_decrypt_blocks(&[*ct_chunk]).unwrap();
assert_eq!(msg_chunk, &pt);
}

// encrypt one at a time, decrypt together (_out)
for (msg_chunk, ct_chunk) in msg_pair.iter().zip(ct.iter_mut()) {
let [c] = encryptor.do_encrypt_blocks(&[*msg_chunk]).unwrap();
*ct_chunk = c;
}
let pt_bytes_written = decryptor.do_decrypt_blocks_out(&ct, &mut pt).unwrap();
assert_eq!(pt_bytes_written, 2 * BLOCK_LEN);
assert_eq!(msg_pair, &pt);
}

// test that the iv is random (ie not the same on two runs)
let (_encryptor, iv1) = C::do_encrypt_init(&key).unwrap();
let (_encryptor, iv2) = C::do_encrypt_init(&key).unwrap();
let (_encryptor, iv1) = E::do_encrypt_init(&key).unwrap();
let (_encryptor, iv2) = E::do_encrypt_init(&key).unwrap();
assert_ne!(iv1, iv2);

// error case: KeyMaterial of wrong type
let mac_key =
KeyMaterial::<KEY_LEN>::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey)
.unwrap();
match C::do_encrypt_init(&mac_key) {
match E::do_encrypt_init(&mac_key) {
Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ }
_ => panic!("Unexpected error"),
};
Expand All @@ -190,19 +228,23 @@ impl TestFrameworkBlockCipher {
];
for ss in security_strengths.iter() {
// Tag the key at an arbitrary strength for the purpose of this test. Inside a
// do_hazardous_operations() closure, set_security_strength() raises the strength
// (and bypasses the key-length guard) without complaining.
do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap();
// do_hazardous_operations() closure, set_security_strength() may raise the strength,
// but it still refuses a strength the key length cannot support; skip those.
match do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())) {
Ok(()) => {}
Err(KeyMaterialError::SecurityStrength(_)) => continue,
Err(e) => panic!("unexpected error tagging key strength: {e:?}"),
}

match C::do_encrypt_init(&key) {
match E::do_encrypt_init(&key) {
Ok(_) => {
if ss >= &C::MAX_SECURITY_STRENGTH { /* good */
if ss >= &E::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should have been a strong enough key");
}
}
Err(SymmetricCipherError::KeyMaterialError(_)) => {
if ss < &C::MAX_SECURITY_STRENGTH { /* good */
if ss < &E::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should not have accepted a key weaker than algorithm");
}
Expand Down Expand Up @@ -328,9 +370,13 @@ impl TestFrameworkAEADCipher {
];
for ss in security_strengths.iter() {
// Tag the key at an arbitrary strength for the purpose of this test. Inside a
// do_hazardous_operations() closure, set_security_strength() raises the strength
// (and bypasses the key-length guard) without complaining.
do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap();
// do_hazardous_operations() closure, set_security_strength() may raise the strength,
// but it still refuses a strength the key length cannot support; skip those.
match do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())) {
Ok(()) => {}
Err(KeyMaterialError::SecurityStrength(_)) => continue,
Err(e) => panic!("unexpected error tagging key strength: {e:?}"),
}

// The key-strength requirement must be enforced both by the AEAD one-shot and by the
// inherited SymmetricCipher one-shot (encrypt_out), so exercise both.
Expand Down
20 changes: 20 additions & 0 deletions crypto/core/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,32 @@ pub enum SymmetricCipherError {
///
KeyMaterialError(KeyMaterialError),
///
PaddingError(PaddingError),
///
RNGError(RNGError),
///
StateError(&'static str),
}

/// Errors from a [`crate::traits::Padding`] scheme.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PaddingError {
/// `pad()` was asked to pad more data than fits in a block alongside at least one byte of padding.
/// The usize is the maximum permitted data length (`BLOCK_LEN - 1`).
DataLengthTooLong(usize),
/// `unpad()` found the block does not carry well-formed padding. Deliberately carries no detail
/// about *how* the padding was malformed.
InvalidPadding,
}

/*** Promotion functions ***/
impl From<PaddingError> for SymmetricCipherError {
fn from(e: PaddingError) -> SymmetricCipherError {
Self::PaddingError(e)
}
}

impl From<KeyMaterialError> for SymmetricCipherError {
fn from(e: KeyMaterialError) -> SymmetricCipherError {
Self::KeyMaterialError(e)
Expand Down
Loading
Loading