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
15 changes: 12 additions & 3 deletions QUALITY_AND_STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ Where the behaviour of a function is critical to test but cannot be tested from
private function, in-line tests in the source file should be used.

All traits in `bouncycastle-core` must have corresponding tests in `bouncycastle-core-test-framework` that exercise all
behaviours and error conditions that are comment to all implementations of that trait.

All crypto algorithms must have tests against the bc-test-data repo and against wycheproof.
behaviours and error conditions that are comment to all implementations of that trait.All crypto algorithms must have tests against the bc-test-data repo and against wycheproof.

## Performance Benchmarks

Expand All @@ -57,6 +55,17 @@ yourself whether this function would take 6-months-from-now-you more than 10 min
there comments you could add that would help future you get back up to speed faster about what this code is doing and
which parts were done for a very specific reason and should not be changed on a whim.

## Naming Conventions

All normal rust naming convensions from clippy apply. In addition, some library-specific naming conventions:

* In constants, "LEN" is the length of a value in bytes (typically used for sizing arrays), whereas "SIZE" is a value in
bits (typically used as a security parameter). For example SHA256 could have constants `HASH_SIZE = 256` and
`HASH_LEN = 32`.
* Functions that are part of a stateful streaming api should be named `do_*()`.
* We use "pk" for public key and "sk" for secret key / private key. (some other libraries use "pub" and "priv", but "
pub" is a keyword in rust, and "pubkey / privkey" is verbose :P )

## APIs

Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member
Expand Down
1 change: 1 addition & 0 deletions crypto/core-test-framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod kdf;
pub mod kem;
pub mod mac;
pub mod signature;
pub mod symmetric_ciphers;

pub const DUMMY_SEED_512: &[u8; 512] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";

Expand Down
361 changes: 361 additions & 0 deletions crypto/core-test-framework/src/symmetric_ciphers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
use crate::{DUMMY_SEED_512, DUMMY_SEED_1024};
use bouncycastle_core::errors::SymmetricCipherError;
use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType};
use bouncycastle_core::traits::{
AEADCipher, BlockCipher, SecurityStrength, StreamCipher, SymmetricCipher,
};

pub struct TestFrameworkSymmetricCipher {
// Put any config options here
}

impl TestFrameworkSymmetricCipher {
pub fn new() -> Self {
Self {}
}

/// Test all the members of trait Hash against the given input-output pair.
/// This gives good baseline test coverage, but is not exhaustive.
pub fn test<
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
C: SymmetricCipher<KEY_LEN, INIT_DATA_LEN>,
>(
&self,
) {
let msg = b"The quick brown fox jumps over the lazy dog";

let key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.unwrap();

// one-shot API
let mut ct = [0u8; 1024];
let (iv, ct_bytes_written) = C::encrypt_out(&key, msg, &mut ct).unwrap();
assert_ne!(ct_bytes_written, 0);

let mut pt = [0u8; 1024];
let pt_bytes_written = C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt).unwrap();
assert_ne!(pt_bytes_written, 0);
assert_eq!(msg, &pt[..pt_bytes_written]);

// todo -- add tests for encrypt() / decrypt() wrapped in a #[cfg(std)]

// messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok)
ct[17] ^= 0xFF;
match C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt) {
Ok(bytes_written) => {
// so it decrypted something, but it had better not match the original plaintext
assert_eq!(bytes_written, pt_bytes_written);
assert_ne!(&pt[..bytes_written], msg);
}
Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ }
_ => panic!("Unexpected error"),
};

// error case: KeyMaterial of wrong type
let mac_key =
KeyMaterial::<KEY_LEN>::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey)
.unwrap();
match C::encrypt_out(&mac_key, msg, &mut ct) {
Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ }
_ => panic!("Unexpected error"),
};

// error case: security strengths too weak and too strong
let mut key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.unwrap();
key.allow_hazardous_operations();

let security_strengths = [
SecurityStrength::None,
SecurityStrength::_112bit,
SecurityStrength::_128bit,
SecurityStrength::_192bit,
SecurityStrength::_256bit,
];
for ss in security_strengths.iter() {
key.set_security_strength(ss.clone()).unwrap();

match C::encrypt_out(&key, msg, &mut ct) {
Ok(_) => {
if ss >= &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should have been a strong enough key");
}
}
Err(SymmetricCipherError::KeyMaterialError(_)) => {
if ss < &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should not have accepted a key weaker than algorithm");
}
}
_ => panic!("Unexpected error"),
};
}
}
}

pub struct TestFrameworkBlockCipher {
// Put any config options here
}

impl TestFrameworkBlockCipher {
pub fn new() -> Self {
Self {}
}

pub fn test<
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
const BLOCK_LEN: usize,
C: BlockCipher<KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>,
>(
&self,
) {
let key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.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();

for msg_chunk in DUMMY_SEED_512.as_chunks::<BLOCK_LEN>().0.iter() {
let ct = encryptor.do_encrypt_block(msg_chunk).unwrap();
let pt = decryptor.do_decrypt_block(&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 ct = [0u8; BLOCK_LEN];
let mut pt = [0u8; BLOCK_LEN];
for msg_chunk in DUMMY_SEED_1024.as_chunks::<BLOCK_LEN>().0.iter() {
let ct_bytes_written = encryptor.do_encrypt_block_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();
assert_eq!(pt_bytes_written, BLOCK_LEN);

assert_eq!(msg_chunk, &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();
assert_ne!(iv1, iv2);

// error case: KeyMaterial of wrong type
let mac_key =
KeyMaterial::<KEY_LEN>::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey)
.unwrap();
match C::do_encrypt_init(&mac_key) {
Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ }
_ => panic!("Unexpected error"),
};

// error case: security strengths too weak and too strong
let mut key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.unwrap();
key.allow_hazardous_operations();

let security_strengths = [
SecurityStrength::None,
SecurityStrength::_112bit,
SecurityStrength::_128bit,
SecurityStrength::_192bit,
SecurityStrength::_256bit,
];
for ss in security_strengths.iter() {
key.set_security_strength(ss.clone()).unwrap();

match C::do_encrypt_init(&key) {
Ok(_) => {
if ss >= &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should have been a strong enough key");
}
}
Err(SymmetricCipherError::KeyMaterialError(_)) => {
if ss < &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should not have accepted a key weaker than algorithm");
}
}
_ => panic!("Unexpected error"),
};
}
}
}

pub struct TestFrameworkAEADCipher {
// Put any config options here
}

impl TestFrameworkAEADCipher {
pub fn new() -> Self {
Self {}
}

/// Test all the members of trait Hash against the given input-output pair.
/// This gives good baseline test coverage, but is not exhaustive.
pub fn test<
const KEY_LEN: usize,
const NONCE_LEN: usize,
const TAG_LEN: usize,
C: AEADCipher<KEY_LEN, NONCE_LEN, TAG_LEN>,
>(
&self,
) {
let msg = b"The quick brown fox jumps over the lazy dog";
let aad = b"some associated data";

let key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.unwrap();

// one-shot API
let mut ct = [0u8; 1024];
let (nonce, ct_bytes_written, tag) = C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap();
if nonce.len() != 0 {
assert_ne!(nonce, [0u8; NONCE_LEN]);
}
assert_ne!(ct_bytes_written, 0);
assert_ne!(tag, [0u8; TAG_LEN]);

let mut pt = [0u8; 1024];
let pt_bytes_written =
C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt).unwrap();
assert_ne!(pt_bytes_written, 0);
assert_eq!(msg, &pt[..pt_bytes_written]);

// todo -- add tests for aead_encrypt() / aead_decrypt() wrapped in a #[cfg(std)]

// messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok)
ct[17] ^= 0xFF;
match C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt) {
Ok(bytes_written) => {
// so it decrypted something, but it had better not match the original plaintext
assert_eq!(bytes_written, pt_bytes_written);
assert_ne!(&pt[..bytes_written], msg);
}
Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ }
_ => panic!("Unexpected error"),
};

// messing with the aad causes the aead_decrypt to fail
match C::aead_decrypt_out(
&key,
&nonce,
b"not the right associated data",
&ct[..ct_bytes_written],
&tag,
&mut pt,
) {
Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ }
_ => panic!("Expected TagCheckFailed error"),
};

// messing with the tag causes the aead_decrypt to fail
match C::aead_decrypt_out(
&key,
&nonce,
aad,
&ct[..ct_bytes_written],
&[3u8; TAG_LEN],
&mut pt,
) {
Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ }
_ => panic!("Expected TagCheckFailed error"),
};

// multiple invocations give different nonces
let (nonce1, _ct_bytes_written, _tag) =
C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap();
let (nonce2, _ct_bytes_written, _tag) =
C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap();
assert_ne!(nonce1, nonce2);

// error case: KeyMaterial of wrong type
let mac_key =
KeyMaterial::<KEY_LEN>::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey)
.unwrap();
match C::encrypt_out(&mac_key, msg, &mut ct) {
Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ }
_ => panic!("Unexpected error"),
};

// error case: security strengths too weak and too strong
let mut key = KeyMaterial::<KEY_LEN>::from_bytes_as_type(
&DUMMY_SEED_512[..KEY_LEN],
KeyType::SymmetricCipherKey,
)
.unwrap();
key.allow_hazardous_operations();

let security_strengths = [
SecurityStrength::None,
SecurityStrength::_112bit,
SecurityStrength::_128bit,
SecurityStrength::_192bit,
SecurityStrength::_256bit,
];
for ss in security_strengths.iter() {
key.set_security_strength(ss.clone()).unwrap();

match C::encrypt_out(&mac_key, msg, &mut ct) {
Ok(_) => {
if ss >= &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should have been a strong enough key");
}
}
Err(SymmetricCipherError::KeyMaterialError(_)) => {
if ss < &C::MAX_SECURITY_STRENGTH { /* good */
} else {
panic!("Should not have accepted a key weaker than algorithm");
}
}
_ => panic!("Unexpected error"),
};
}
}
}

pub struct TestFrameworkStreamCipher {
// Put any config options here
}

impl TestFrameworkStreamCipher {
pub fn new() -> Self {
Self {}
}

/// Test all the members of trait Hash against the given input-output pair.
/// This gives good baseline test coverage, but is not exhaustive.
pub fn test<
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
C: StreamCipher<KEY_LEN, INIT_DATA_LEN>,
>(
&self,
) {
todo!()
}
}
Loading
Loading