diff --git a/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs index 8f2d8bd2e..ceeba757a 100644 --- a/cli/src/aes_cbc_cmd.rs +++ b/cli/src/aes_cbc_cmd.rs @@ -13,9 +13,10 @@ use crate::helpers::block_mode_helpers::{ BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key, }; use bouncycastle::aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle::aes::{AES_CBC_128_Key, AES_CBC_192_Key, AES_CBC_256_Key}; use bouncycastle::cipher::padding::PKCS7; use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; pub(crate) fn aes128_cbc_cmd( @@ -26,9 +27,9 @@ pub(crate) fn aes128_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_128, 16>( + run::, AES_CBC_128, AES_CBC_128_Key, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -42,9 +43,9 @@ pub(crate) fn aes192_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_192, 24>( + run::, AES_CBC_192, AES_CBC_192_Key, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -58,30 +59,31 @@ pub(crate) fn aes256_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_256, 32>( + run::, AES_CBC_256, AES_CBC_256_Key, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); } /// Dispatches to the shared streaming loops. -fn run( +fn run( action: &CipherDirection, - key: &KeyMaterial, + key: &K, iv: Option<[u8; BLOCK_LEN]>, output_hex: bool, ) where - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + K: SymmetricCipherKey, + E: SymmetricCipherEncryptor, + D: SymmetricCipherDecryptor, { match action { CipherDirection::Encrypt => { - encrypt_stream::(key, iv, output_hex, None) + encrypt_stream::(key, iv, output_hex, None) } CipherDirection::Decrypt => { - decrypt_stream::(key, iv, output_hex, None) + decrypt_stream::(key, iv, output_hex, None) } } } diff --git a/cli/src/aes_ccm_cmd.rs b/cli/src/aes_ccm_cmd.rs index 89d3c28a5..ca6bb1b14 100644 --- a/cli/src/aes_ccm_cmd.rs +++ b/cli/src/aes_ccm_cmd.rs @@ -53,14 +53,15 @@ use std::marker::PhantomData; use std::process::exit; use bouncycastle::aes::{ - AES_CCM_128_Packet, AES_CCM_192_Packet, AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN, + AES_CCM_128_Key, AES_CCM_128_Packet, AES_CCM_192_Key, AES_CCM_192_Packet, AES_CCM_256_Key, + AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN, }; use bouncycastle::cipher::modes::CcmPacket; use bouncycastle::cipher::modes::ModeNames; use bouncycastle::cipher::{Decrypting, Encrypting}; use bouncycastle::core::errors::SymmetricCipherError; use bouncycastle::core::hazmat::ElectronicCodeBook; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::hex; use crate::helpers; @@ -84,7 +85,7 @@ pub(crate) fn aes128_ccm_cmd( run( PhantomData::>, action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), nonce, nonce_file, aad, @@ -109,7 +110,7 @@ pub(crate) fn aes192_ccm_cmd( run( PhantomData::>, action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), nonce, nonce_file, aad, @@ -134,7 +135,7 @@ pub(crate) fn aes256_ccm_cmd( run( PhantomData::>, action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), nonce, nonce_file, aad, @@ -255,11 +256,12 @@ fn load_aad(aad: &Option, aad_file: &Option) -> Aad { /// is being read, the declared length is wrong, and the tag would be computed over a length /// encoding that does not match the AAD; that is reported and the command exits rather than /// producing it. -fn feed_aad( - ccm: &mut CcmPacket, +fn feed_aad( + ccm: &mut CcmPacket, aad: &mut Aad, ) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { match aad { Aad::Bytes(bytes) => { @@ -308,10 +310,10 @@ fn read_all_stdin() -> Vec { /// conditions compile-time checks rather than runtime ones -- so a command-line value has to be /// matched into one of the permitted instantiations. The two nested matches are the price of that, /// and they are exhaustive over A.1's sets: 7 nonce lengths x 7 tag lengths. -fn run( - _aes: PhantomData>, +fn run( + _aes: PhantomData>, action: &CipherDirection, - key: &KeyMaterial, + key: &K, nonce: &Option, nonce_file: &Option, aad: &Option, @@ -319,7 +321,8 @@ fn run( tag_len: usize, output_hex: bool, ) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { // Reject this before opening nonce/AAD files or waiting for stdin. Appendix A.1: "t is an // element of {4, 6, 8, 10, 12, 14, 16}". @@ -339,25 +342,25 @@ fn run( macro_rules! with_tag_len { ($n:literal) => { match tag_len { - 4 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 6 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 8 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 10 => go::( + 4 => go::( + key, &nonce_bytes, &mut aad, input, encrypt, output_hex, + ), + 6 => go::( + key, &nonce_bytes, &mut aad, input, encrypt, output_hex, + ), + 8 => go::( + key, &nonce_bytes, &mut aad, input, encrypt, output_hex, + ), + 10 => go::( key, &nonce_bytes, &mut aad, input, encrypt, output_hex, ), - 12 => go::( + 12 => go::( key, &nonce_bytes, &mut aad, input, encrypt, output_hex, ), - 14 => go::( + 14 => go::( key, &nonce_bytes, &mut aad, input, encrypt, output_hex, ), - 16 => go::( + 16 => go::( key, &nonce_bytes, &mut aad, input, encrypt, output_hex, ), _ => unreachable!("tag length was validated before stdin was read"), @@ -387,19 +390,26 @@ fn run( /// The only [`SymmetricCipherError::GenericError`] it can return is that limit: A.1's /// `p < 2^8q`, where `q = 15 - n`. Both directions hit it -- the decrypt side on the input minus /// its tag -- so both report it here, with the numbers, since the fix is a shorter nonce. -fn payload_past_the_q_limit( +fn payload_past_the_q_limit< + P, + K, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, +>( msg: &str, payload_len: usize, ) -> ! where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { eprintln!("Error: {msg}"); eprintln!( " Payload is {payload_len} bytes; with a {NONCE_LEN}-byte nonce, q = {} and the \ limit is {} bytes.", 15 - NONCE_LEN, - CcmPacket::::MAX_PAYLOAD_LEN, + CcmPacket::::MAX_PAYLOAD_LEN, ); eprintln!(" Use a shorter nonce for a larger payload."); exit(-1) @@ -412,20 +422,21 @@ where /// the size of `input`: the declared-length constructor already has everything a one-shot needs, /// so there is no second buffer to allocate or copy into. The AAD goes in through /// [`CcmPacket::new_with_lengths`] and [`feed_aad`], so a `--aad-file` is streamed rather than loaded. -fn go( - key: &KeyMaterial, +fn go( + key: &K, nonce_bytes: &[u8], aad: &mut Aad, mut input: Vec, encrypt: bool, output_hex: bool, ) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - type Enc = - CcmPacket; - type Dec = - CcmPacket; + type Enc = + CcmPacket; + type Dec = + CcmPacket; // `run` dispatched on this exact length, so the conversion cannot fail. let Ok(nonce) = <[u8; NONCE_LEN]>::try_from(nonce_bytes) else { @@ -434,7 +445,7 @@ fn go( }; if encrypt { - match Enc::::new_with_lengths( + match Enc::::new_with_lengths( key, &nonce, aad.len(), @@ -455,7 +466,7 @@ fn go( } } Err(SymmetricCipherError::GenericError(msg)) => { - payload_past_the_q_limit::(msg, input.len()) + payload_past_the_q_limit::(msg, input.len()) } Err(e) => { eprintln!("Error: AES-CCM encryption failed: {e:?}"); @@ -473,7 +484,7 @@ fn go( ); exit(-1) }; - match Dec::::new_with_lengths( + match Dec::::new_with_lengths( key, &nonce, aad.len(), @@ -507,7 +518,7 @@ fn go( } } Err(SymmetricCipherError::GenericError(msg)) => { - payload_past_the_q_limit::(msg, data.len()) + payload_past_the_q_limit::(msg, data.len()) } Err(e) => { eprintln!("Error: AES-CCM decryption failed: {e:?}"); diff --git a/cli/src/aes_cfb8_cmd.rs b/cli/src/aes_cfb8_cmd.rs index ee6690cdb..6b7402e48 100644 --- a/cli/src/aes_cfb8_cmd.rs +++ b/cli/src/aes_cfb8_cmd.rs @@ -28,6 +28,7 @@ use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key}; use crate::helpers::stream_mode_helpers::run_stream_mode; use bouncycastle::aes::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; +use bouncycastle::aes::{AES_CFB8_128_Key, AES_CFB8_192_Key, AES_CFB8_256_Key}; use bouncycastle::cipher::{Decrypting, Encrypting}; pub(crate) fn aes128_cfb8_cmd( @@ -38,9 +39,15 @@ pub(crate) fn aes128_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_128, 16, BLOCK_LEN>( + run_stream_mode::< + AES_CFB8_128, + AES_CFB8_128, + AES_CFB8_128_Key, + 16, + BLOCK_LEN, + >( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -54,9 +61,15 @@ pub(crate) fn aes192_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_192, 24, BLOCK_LEN>( + run_stream_mode::< + AES_CFB8_192, + AES_CFB8_192, + AES_CFB8_192_Key, + 24, + BLOCK_LEN, + >( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -70,9 +83,15 @@ pub(crate) fn aes256_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_256, 32, BLOCK_LEN>( + run_stream_mode::< + AES_CFB8_256, + AES_CFB8_256, + AES_CFB8_256_Key, + 32, + BLOCK_LEN, + >( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); diff --git a/cli/src/aes_cfb_cmd.rs b/cli/src/aes_cfb_cmd.rs index 9cc88a6e2..75ff0c9d7 100644 --- a/cli/src/aes_cfb_cmd.rs +++ b/cli/src/aes_cfb_cmd.rs @@ -29,6 +29,7 @@ use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key}; use crate::helpers::stream_mode_helpers::run_stream_mode; use bouncycastle::aes::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; +use bouncycastle::aes::{AES_CFB_128_Key, AES_CFB_192_Key, AES_CFB_256_Key}; use bouncycastle::cipher::{Decrypting, Encrypting}; pub(crate) fn aes128_cfb_cmd( @@ -39,9 +40,15 @@ pub(crate) fn aes128_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_128, 16, BLOCK_LEN>( + run_stream_mode::< + AES_CFB_128, + AES_CFB_128, + AES_CFB_128_Key, + 16, + BLOCK_LEN, + >( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -55,9 +62,15 @@ pub(crate) fn aes192_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_192, 24, BLOCK_LEN>( + run_stream_mode::< + AES_CFB_192, + AES_CFB_192, + AES_CFB_192_Key, + 24, + BLOCK_LEN, + >( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -71,9 +84,15 @@ pub(crate) fn aes256_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_256, 32, BLOCK_LEN>( + run_stream_mode::< + AES_CFB_256, + AES_CFB_256, + AES_CFB_256_Key, + 32, + BLOCK_LEN, + >( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); diff --git a/cli/src/aes_ctr_cmd.rs b/cli/src/aes_ctr_cmd.rs index 742f333fc..b9f942d39 100644 --- a/cli/src/aes_ctr_cmd.rs +++ b/cli/src/aes_ctr_cmd.rs @@ -36,6 +36,7 @@ use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key}; use crate::helpers::stream_mode_helpers::run_stream_mode; use bouncycastle::aes::{AES_CTR_128, AES_CTR_192, AES_CTR_256, CTR_NONCE_LEN}; +use bouncycastle::aes::{AES_CTR_128_Key, AES_CTR_192_Key, AES_CTR_256_Key}; use bouncycastle::cipher::{Decrypting, Encrypting}; pub(crate) fn aes128_ctr_cmd( @@ -46,9 +47,15 @@ pub(crate) fn aes128_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_128, 16, CTR_NONCE_LEN>( + run_stream_mode::< + AES_CTR_128, + AES_CTR_128, + AES_CTR_128_Key, + 16, + CTR_NONCE_LEN, + >( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -62,9 +69,15 @@ pub(crate) fn aes192_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_192, 24, CTR_NONCE_LEN>( + run_stream_mode::< + AES_CTR_192, + AES_CTR_192, + AES_CTR_192_Key, + 24, + CTR_NONCE_LEN, + >( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -78,9 +91,15 @@ pub(crate) fn aes256_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_256, 32, CTR_NONCE_LEN>( + run_stream_mode::< + AES_CTR_256, + AES_CTR_256, + AES_CTR_256_Key, + 32, + CTR_NONCE_LEN, + >( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); diff --git a/cli/src/aes_ecb_cmd.rs b/cli/src/aes_ecb_cmd.rs index 63b7d43f5..01f59f6d2 100644 --- a/cli/src/aes_ecb_cmd.rs +++ b/cli/src/aes_ecb_cmd.rs @@ -19,9 +19,10 @@ use crate::helpers::block_mode_helpers::{ BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_key, }; use bouncycastle::aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; +use bouncycastle::aes::hazmat::{AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key}; use bouncycastle::cipher::padding::NoPadding; use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// Names the mode in error messages. @@ -33,11 +34,12 @@ pub(crate) fn aes128_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_128, 16>( - action, - &load_key::<16>(key, key_file, "AES-128"), - output_hex, - ); + run::< + AES_ECB_128, + AES_ECB_128, + AES_ECB_128_Key, + 16, + >(action, &load_key::(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_ecb_cmd( @@ -46,11 +48,12 @@ pub(crate) fn aes192_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_192, 24>( - action, - &load_key::<24>(key, key_file, "AES-192"), - output_hex, - ); + run::< + AES_ECB_192, + AES_ECB_192, + AES_ECB_192_Key, + 24, + >(action, &load_key::(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_ecb_cmd( @@ -59,29 +62,28 @@ pub(crate) fn aes256_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_256, 32>( - action, - &load_key::<32>(key, key_file, "AES-256"), - output_hex, - ); + run::< + AES_ECB_256, + AES_ECB_256, + AES_ECB_256_Key, + 32, + >(action, &load_key::(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops. `INIT_DATA_LEN` is 0, so the loops write and read no /// IV. -fn run( - action: &CipherDirection, - key: &KeyMaterial, - output_hex: bool, -) where - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, +fn run(action: &CipherDirection, key: &K, output_hex: bool) +where + K: SymmetricCipherKey, + E: SymmetricCipherEncryptor, + D: SymmetricCipherDecryptor, { match action { CipherDirection::Encrypt => { - encrypt_stream::(key, None, output_hex, Some(MODE)) + encrypt_stream::(key, None, output_hex, Some(MODE)) } CipherDirection::Decrypt => { - decrypt_stream::(key, None, output_hex, Some(MODE)) + decrypt_stream::(key, None, output_hex, Some(MODE)) } } } diff --git a/cli/src/aes_gcm_cmd.rs b/cli/src/aes_gcm_cmd.rs index c6907aebd..da40aa584 100644 --- a/cli/src/aes_gcm_cmd.rs +++ b/cli/src/aes_gcm_cmd.rs @@ -16,9 +16,9 @@ use crate::helpers::aead_cipher_helpers::{decrypt_gcm, encrypt_gcm, load_aad}; use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key}; use bouncycastle::aes::{AES_GCM_128, AES_GCM_192, AES_GCM_256}; +use bouncycastle::aes::{AES_GCM_128_Key, AES_GCM_192_Key, AES_GCM_256_Key}; use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; -use bouncycastle::core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle::core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherKey}; pub(crate) fn aes128_gcm_cmd( action: &CipherDirection, @@ -30,9 +30,9 @@ pub(crate) fn aes128_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_128, 16>( + run::, AES_GCM_128, AES_GCM_128_Key, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key::(key, key_file, "AES-128"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, @@ -49,9 +49,9 @@ pub(crate) fn aes192_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_192, 24>( + run::, AES_GCM_192, AES_GCM_192_Key, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key::(key, key_file, "AES-192"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, @@ -68,9 +68,9 @@ pub(crate) fn aes256_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_256, 32>( + run::, AES_GCM_256, AES_GCM_256_Key, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key::(key, key_file, "AES-256"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, @@ -78,18 +78,19 @@ pub(crate) fn aes256_gcm_cmd( } /// Dispatches to the shared AEAD streaming loops with GCM's 128-bit tag. -fn run( +fn run( action: &CipherDirection, - key: &KeyMaterial, + key: &K, nonce: Option<[u8; 12]>, aad: &[u8], output_hex: bool, ) where - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, { match action { - CipherDirection::Encrypt => encrypt_gcm::(key, nonce, aad, output_hex), - CipherDirection::Decrypt => decrypt_gcm::(key, nonce, aad, output_hex), + CipherDirection::Encrypt => encrypt_gcm::(key, nonce, aad, output_hex), + CipherDirection::Decrypt => decrypt_gcm::(key, nonce, aad, output_hex), } } diff --git a/cli/src/ascon_cmd.rs b/cli/src/ascon_cmd.rs index 7656b1f4c..ce4e15629 100644 --- a/cli/src/ascon_cmd.rs +++ b/cli/src/ascon_cmd.rs @@ -1,15 +1,16 @@ use std::io::{self, Read}; use std::process::exit; -use bouncycastle::ascon::Ascon_AEAD128; use bouncycastle::ascon::AsconCXof128; use bouncycastle::ascon::AsconHash256; use bouncycastle::ascon::AsconXof128; +use bouncycastle::ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; use bouncycastle::cipher::{Decrypting, Encrypting}; use bouncycastle::core::errors::SymmetricCipherError; use bouncycastle::core::hazmat::do_hazardous_operations; use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle::core::security_strength::SecurityStrength; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; @@ -56,9 +57,9 @@ fn require_16(bytes: Vec, label: &str) -> [u8; 16] { }) } -/// Build a `KeyMaterial<16>` for the AEAD key, warning (and forcing usable metadata) only if the +/// Build an `Ascon_AEAD128_Key` for the AEAD key, warning (and forcing usable metadata) only if the /// key turns out to be low-entropy (e.g. all-zero), the same way `helpers::parse_seed` does. -fn load_key_material(key_bytes: &[u8; 16]) -> KeyMaterial<16> { +fn load_key_material(key_bytes: &[u8; 16]) -> Ascon_AEAD128_Key { let mut key = KeyMaterial::<16>::from_bytes_as_type(key_bytes, KeyType::SymmetricCipherKey).unwrap(); if key.key_type() == KeyType::Zeroized || key.security_strength() < SecurityStrength::_128bit { @@ -71,7 +72,7 @@ fn load_key_material(key_bytes: &[u8; 16]) -> KeyMaterial<16> { }) .unwrap(); } - key + Ascon_AEAD128_Key::from_keymaterial(key).unwrap() } /// Ascon-Hash256 of stdin. Streaming update; 256-bit digest. @@ -146,7 +147,7 @@ pub(crate) fn aead128_cmd( /// [`SymmetricCipherEncryptor::do_encrypt_final_out`]). With no nonce given one is generated and /// written ahead of the stream; a caller-supplied nonce is not written, since the caller has it. fn aead128_encrypt_stream( - key: &KeyMaterial<16>, + key: &Ascon_AEAD128_Key, nonce: Option<&[u8; 16]>, ad_opt: Option<&[u8]>, output_hex: bool, @@ -197,7 +198,7 @@ fn aead128_encrypt_stream( /// before them as soon as it is known not to be part of the tag; at EOF /// [`SymmetricCipherDecryptor::do_decrypt_final`] checks what it held back as the tag. fn aead128_decrypt_stream( - key: &KeyMaterial<16>, + key: &Ascon_AEAD128_Key, nonce: Option<&[u8; 16]>, ad_opt: Option<&[u8]>, output_hex: bool, diff --git a/cli/src/helpers/aead_cipher_helpers.rs b/cli/src/helpers/aead_cipher_helpers.rs index e54b36fb8..f298d7b3d 100644 --- a/cli/src/helpers/aead_cipher_helpers.rs +++ b/cli/src/helpers/aead_cipher_helpers.rs @@ -39,7 +39,7 @@ //! other GCM implementation given the same file. use crate::helpers::{flush_stdout, read_from_file_raw, write_bytes_or_hex, write_stdout}; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; use bouncycastle::hex; use std::io; @@ -72,13 +72,14 @@ pub fn load_aad(aad: &Option, aad_file: &Option) -> Vec { /// Encrypts stdin to stdout under GCM: writes the generated IV (unless `nonce` supplies one), /// then the ciphertext as it streams, then the tag. -pub fn encrypt_gcm( - key: &KeyMaterial, +pub fn encrypt_gcm( + key: &K, nonce: Option<[u8; GCM_NONCE_LEN]>, aad: &[u8], output_hex: bool, ) where - E: AEADCipherEncryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, { let mut enc = match nonce { Some(nonce) => E::do_encrypt_init_nonce(key, &nonce), @@ -128,13 +129,14 @@ pub fn encrypt_gcm( /// first, streams the rest through the inline decryptor, and checks the tag on /// `do_decrypt_final`. See the module docs for why plaintext may already be written to stdout by /// the time a tag failure is reported. -pub fn decrypt_gcm( - key: &KeyMaterial, +pub fn decrypt_gcm( + key: &K, nonce: Option<[u8; GCM_NONCE_LEN]>, aad: &[u8], output_hex: bool, ) where - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + D: AEADCipherDecryptor, { let nonce = nonce.unwrap_or_else(|| { let mut nonce = [0u8; GCM_NONCE_LEN]; diff --git a/cli/src/helpers/block_mode_helpers.rs b/cli/src/helpers/block_mode_helpers.rs index c68dcc85b..3bbb1ddd9 100644 --- a/cli/src/helpers/block_mode_helpers.rs +++ b/cli/src/helpers/block_mode_helpers.rs @@ -62,6 +62,7 @@ use bouncycastle::core::errors::RNGError; use bouncycastle::core::hazmat::do_hazardous_operations; use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle::core::security_strength::SecurityStrength; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{RNG, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle::hex; use clap::ValueEnum; @@ -90,11 +91,11 @@ pub(crate) enum CipherDirection { /// /// `KEY_LEN` is exact: AES has three key lengths and the command selects one, so a key of the /// wrong length is a mistake rather than something to truncate or pad. -pub(crate) fn load_key( +pub(crate) fn load_key, const KEY_LEN: usize>( key: &Option, key_file: &Option, alg: &str, -) -> KeyMaterial { +) -> K { let key_bytes: Vec = if let Some(key_file) = key_file { // A file may hold raw bytes or hex; `read_from_file` tries hex first, as the other // commands do. @@ -144,7 +145,10 @@ pub(crate) fn load_key( }); } - key + K::from_keymaterial(key).unwrap_or_else(|e| { + eprintln!("Error: couldn't load the key: {e:?}"); + exit(-1); + }) } /// An [`RNG`] that yields the caller's IV once, so `--iv` can reach a mode whose only @@ -236,13 +240,14 @@ pub(crate) fn load_iv( /// `INIT_DATA_LEN` is the mode's: one block for CBC, 0 for ECB, in which case nothing is written /// ahead of the ciphertext. `aligned_mode` is `Some(mode name)` for a mode that pads nothing, whose /// input must therefore be a whole number of blocks; the name appears in the error message. -pub(crate) fn encrypt_stream( - key: &KeyMaterial, +pub(crate) fn encrypt_stream( + key: &K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, aligned_mode: Option<&str>, ) where - E: SymmetricCipherEncryptor, + K: SymmetricCipherKey, + E: SymmetricCipherEncryptor, { let init = match iv { Some(iv) => E::do_encrypt_init_rng(key, &mut FixedIv::new(iv)), @@ -284,13 +289,14 @@ pub(crate) fn encrypt_stream( - key: &KeyMaterial, +pub(crate) fn decrypt_stream( + key: &K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, aligned_mode: Option<&str>, ) where - D: SymmetricCipherDecryptor, + K: SymmetricCipherKey, + D: SymmetricCipherDecryptor, { let iv = iv.unwrap_or_else(|| { // The leading bytes are the IV, not ciphertext. (None for ECB: the read is skipped.) diff --git a/cli/src/helpers/stream_mode_helpers.rs b/cli/src/helpers/stream_mode_helpers.rs index fa0349e9f..ffdfde7c5 100644 --- a/cli/src/helpers/stream_mode_helpers.rs +++ b/cli/src/helpers/stream_mode_helpers.rs @@ -30,7 +30,7 @@ use crate::helpers::block_mode_helpers::{CHUNK_LEN, CipherDirection, FixedIv}; use crate::helpers::{flush_stdout, write_bytes_or_hex, write_stdout}; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use std::io; use std::io::Read; @@ -40,12 +40,13 @@ use std::process::exit; /// written first; with one, it is used and the output is ciphertext only. /// /// `INIT_DATA_LEN` is the mode's: one block for CFB and CFB8, 12 bytes for CTR. -pub(crate) fn encrypt_stream( - key: &KeyMaterial, +pub(crate) fn encrypt_stream( + key: &K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - E: StreamCipherEncryptor, + K: SymmetricCipherKey, + E: StreamCipherEncryptor, { let init = match iv { Some(iv) => E::do_encrypt_init_rng(key, &mut FixedIv::new(iv)), @@ -79,12 +80,13 @@ pub(crate) fn encrypt_stream( - key: &KeyMaterial, +pub(crate) fn decrypt_stream( + key: &K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - D: StreamCipherDecryptor, + K: SymmetricCipherKey, + D: StreamCipherDecryptor, { let iv = iv.unwrap_or_else(|| { // The leading bytes are the IV, not ciphertext. @@ -148,21 +150,22 @@ fn finish(output_hex: bool) { /// Runs one direction of a stream mode. The dispatchers in `aes_cfb_cmd`, `aes_cfb8_cmd` and /// `aes_ctr_cmd` differ only in which mode they name, so the match lives here. -pub(crate) fn run_stream_mode( +pub(crate) fn run_stream_mode( action: &CipherDirection, - key: &KeyMaterial, + key: &K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - E: StreamCipherEncryptor, - D: StreamCipherDecryptor, + K: SymmetricCipherKey, + E: StreamCipherEncryptor, + D: StreamCipherDecryptor, { match action { CipherDirection::Encrypt => { - encrypt_stream::(key, iv, output_hex) + encrypt_stream::(key, iv, output_hex) } CipherDirection::Decrypt => { - decrypt_stream::(key, iv, output_hex) + decrypt_stream::(key, iv, output_hex) } } } diff --git a/crypto/aes/Cargo.toml b/crypto/aes/Cargo.toml index 1bd18ab01..be9d97fb8 100644 --- a/crypto/aes/Cargo.toml +++ b/crypto/aes/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true bouncycastle-core.workspace = true bouncycastle-utils.workspace = true bouncycastle-cipher.workspace = true +bouncycastle-rng.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true @@ -14,10 +15,6 @@ bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true -[[bench]] -name = "aes_benches" -harness = false - [[bench]] name = "aes_modes_benches" harness = false diff --git a/crypto/aes/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs deleted file mode 100644 index c1f1838dc..000000000 --- a/crypto/aes/benches/aes_benches.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Criterion benchmarks for the bit-sliced AES permutation. -//! -//! The comparison that matters here is `encrypt_block` against `encrypt_2blocks` and -//! `encrypt_4blocks` over the same number of bytes. The engine runs on `u16`, `u32` or `u64` -//! bit-planes for one, two or four blocks, and a round costs about the same at every width on a -//! 64-bit machine, so the two- and four-block paths should approach twice and four times the -//! throughput of the single-block one; what they achieve in practice is what these benches record -//! (on x86-64: 1.75x and 3.0x for encryption, 1.95x and 3.7x for decryption). That multiplier is -//! the argument for modes of operation using the batched entry points wherever their blocks are -//! independent (CTR, ECB, and the decrypt direction of CBC and CFB). -//! -//! The data benches work in place on one buffer across iterations, so a `clone` never sits inside -//! the timed closure. The permutation is a bijection, so the buffer stays random whichever -//! direction ran last, and the contents never influence the timing of a constant-time cipher. - -use bouncycastle_aes::AES_BLOCK_LEN; -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::RNG; -use bouncycastle_rng as rng; -use criterion::measurement::WallTime; -use criterion::{BenchmarkGroup, Criterion, Throughput, criterion_group, criterion_main}; -use std::hint::black_box; - -/// 16 KiB of data, i.e. 1024 AES blocks. -const NUM_BLOCKS: usize = 1024; -const DATA_LEN: usize = NUM_BLOCKS * AES_BLOCK_LEN; - -fn random_blocks() -> Vec<[u8; AES_BLOCK_LEN]> { - let mut blocks = vec![[0u8; AES_BLOCK_LEN]; NUM_BLOCKS]; - let mut generator = rng::DefaultRNG::default(); - for block in blocks.iter_mut() { - generator.next_bytes_out(block).unwrap(); - } - blocks -} - -fn key() -> KeyMaterial { - let mut bytes = [0u8; N]; - rng::DefaultRNG::default().next_bytes_out(&mut bytes).unwrap(); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).unwrap() -} - -fn bench_key_expansion(c: &mut Criterion) { - let mut group = c.benchmark_group("aes::key expansion"); - - let key128 = key::<16>(); - group.throughput(Throughput::Bytes(16)); - group.bench_function("AES128Internal::new()", |b| { - b.iter(|| black_box(AES128Internal::new(black_box(&key128)).unwrap())) - }); - - let key192 = key::<24>(); - group.throughput(Throughput::Bytes(24)); - group.bench_function("AES192Internal::new()", |b| { - b.iter(|| black_box(AES192Internal::new(black_box(&key192)).unwrap())) - }); - - let key256 = key::<32>(); - group.throughput(Throughput::Bytes(32)); - group.bench_function("AES256Internal::new()", |b| { - b.iter(|| black_box(AES256Internal::new(black_box(&key256)).unwrap())) - }); - - group.finish(); -} - -/// The six data benches every key length gets: 16 KiB through the one-, two- and four-block -/// entry points, in each direction. -fn bench_data_paths>( - group: &mut BenchmarkGroup<'_, WallTime>, - aes: &C, -) { - let mut blocks = random_blocks(); - group.throughput(Throughput::Bytes(DATA_LEN as u64)); - - group.bench_function("16KiB -- .encrypt_block() x1024", |b| { - b.iter(|| { - for block in blocks.iter_mut() { - aes.encrypt_block(black_box(block)); - } - black_box(&blocks); - }) - }); - - group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { - b.iter(|| { - for pair in blocks.chunks_exact_mut(2) { - // `try_into` cannot fail: `chunks_exact_mut(2)` yields slices of length 2. - let pair: &mut [[u8; AES_BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_2blocks(black_box(pair)); - } - black_box(&blocks); - }) - }); - - group.bench_function("16KiB -- .encrypt_4blocks() x256", |b| { - b.iter(|| { - for four in blocks.chunks_exact_mut(4) { - // `try_into` cannot fail: `chunks_exact_mut(4)` yields slices of length 4. - let four: &mut [[u8; AES_BLOCK_LEN]; 4] = four.try_into().unwrap(); - aes.encrypt_4blocks(black_box(four)); - } - black_box(&blocks); - }) - }); - - group.bench_function("16KiB -- .decrypt_block() x1024", |b| { - b.iter(|| { - for block in blocks.iter_mut() { - aes.decrypt_block(black_box(block)); - } - black_box(&blocks); - }) - }); - - group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { - b.iter(|| { - for pair in blocks.chunks_exact_mut(2) { - let pair: &mut [[u8; AES_BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.decrypt_2blocks(black_box(pair)); - } - black_box(&blocks); - }) - }); - - group.bench_function("16KiB -- .decrypt_4blocks() x256", |b| { - b.iter(|| { - for four in blocks.chunks_exact_mut(4) { - let four: &mut [[u8; AES_BLOCK_LEN]; 4] = four.try_into().unwrap(); - aes.decrypt_4blocks(black_box(four)); - } - black_box(&blocks); - }) - }); -} - -fn bench_aes128(c: &mut Criterion) { - let aes = AES128Internal::new(&key::<16>()).unwrap(); - let mut group = c.benchmark_group("aes::AES128Internal"); - bench_data_paths(&mut group, &aes); - group.finish(); -} - -fn bench_aes192(c: &mut Criterion) { - let aes = AES192Internal::new(&key::<24>()).unwrap(); - let mut group = c.benchmark_group("aes::AES192Internal"); - bench_data_paths(&mut group, &aes); - group.finish(); -} - -fn bench_aes256(c: &mut Criterion) { - let aes = AES256Internal::new(&key::<32>()).unwrap(); - let mut group = c.benchmark_group("aes::AES256Internal"); - bench_data_paths(&mut group, &aes); - group.finish(); -} - -criterion_group!(benches, bench_key_expansion, bench_aes128, bench_aes192, bench_aes256); -criterion_main!(benches); diff --git a/crypto/aes/benches/aes_modes_benches.rs b/crypto/aes/benches/aes_modes_benches.rs index 90c66754d..2ec1fb8e3 100644 --- a/crypto/aes/benches/aes_modes_benches.rs +++ b/crypto/aes/benches/aes_modes_benches.rs @@ -1,8 +1,7 @@ //! Criterion benchmarks for the modes: one measurement per path that costs differently. //! -//! The block-level costs -- `encrypt_block`, the pair and four paths, key expansion, and every key -//! length -- are measured in `aes_benches`. These benches measure only what a mode adds on top, so -//! a figure that would equal an `aes_benches` one, or another one here, is not repeated. +//! These benches measure what a mode adds on top of the permutation, so a figure that would equal +//! another one here is not repeated. //! //! The number to watch is the **decrypt/encrypt throughput ratio** in CBC, CFB and CFB8. //! Encryption there is serial by construction (SP 800-38A Sec 6.2 and Sec 6.3: each forward cipher @@ -23,14 +22,17 @@ //! The cipher works in place, so each measurement runs on a fresh copy of the data made in //! criterion's untimed setup (`iter_batched`); the copy is not part of the timing. -use bouncycastle_aes::hazmat::{AES128Internal, AES256Internal}; +use bouncycastle_aes::hazmat::{AES_ECB_128_Key, AES128Internal, AES256Internal}; +use bouncycastle_aes::{ + AES_CBC_128_Key, AES_CCM_128_Key, AES_CFB_128_Key, AES_CFB8_128_Key, AES_CTR_128_Key, + AES_GCM_128_Key, AES_GCM_256_Key, +}; use bouncycastle_cipher::modes::hazmat::ecb::Ecb; use bouncycastle_cipher::modes::{Cbc, CcmPacket, Cfb, Cfb8, Ctr, GCM_NONCE_LEN, Gcm}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, - StreamCipherEncryptor, SymmetricCipherEncryptor, + StreamCipherEncryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_core_test_framework::FixedSeedRNG; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; @@ -41,26 +43,33 @@ const BLOCK_LEN: usize = 16; const NUM_BLOCKS: usize = 1024; const DATA_LEN: usize = NUM_BLOCKS * BLOCK_LEN; -type Aes128Cbc = Cbc; -type Aes128Cfb = Cfb; -type Aes128Cfb8 = Cfb8; +type Aes128Cbc = Cbc; +type Aes128Cfb = Cfb; +type Aes128Cfb8 = Cfb8; /// CCM at the parameters the ACVP vectors and most protocols use: a 12-byte nonce and a full /// 16-byte tag. const CCM_NONCE_LEN: usize = 12; const CCM_TAG_LEN: usize = 16; -type Aes128CcmEnc = - CcmPacket; +type Aes128CcmEnc = CcmPacket< + AES128Internal, + Encrypting, + AES_CCM_128_Key, + 16, + BLOCK_LEN, + CCM_NONCE_LEN, + CCM_TAG_LEN, +>; /// GCM with the full 16-byte tag, as the `AES_GCM_*` aliases fix it. const GCM_TAG_LEN: usize = 16; -type Aes128Gcm = Gcm; -type Aes256Gcm = Gcm; -type Aes128Ctr = Ctr; -type Aes128Ecb = Ecb; +type Aes128Gcm = Gcm; +type Aes256Gcm = Gcm; +type Aes128Ctr = Ctr; +type Aes128Ecb = Ecb; -fn key() -> KeyMaterial { +fn key, const N: usize>() -> K { let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).unwrap() + K::from_bytes(&bytes).expect("a valid key") } fn data() -> Vec<[u8; BLOCK_LEN]> { @@ -70,7 +79,7 @@ fn data() -> Vec<[u8; BLOCK_LEN]> { } fn bench_aes128cbc(c: &mut Criterion) { - let k = key::<16>(); + let k = key::(); let blocks = data(); let mut group = c.benchmark_group("modes::cbc::AES_128"); @@ -122,11 +131,12 @@ fn bench_aes128cbc(c: &mut Criterion) { /// Runs the 16 KiB through a stream-cipher encryptor in `call_len`-byte calls. Used by the CFB, /// CFB8 and CTR groups: it is generic over the trait, not over the mode. fn cfb_encrypt_in_calls< - E: StreamCipherEncryptor, + E: StreamCipherEncryptor, + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, >( - k: &KeyMaterial, + k: &K, scratch: &mut [u8], call_len: usize, ) { @@ -138,11 +148,12 @@ fn cfb_encrypt_in_calls< /// Runs the 16 KiB through a stream-cipher decryptor in `call_len`-byte calls. Shared as above. fn cfb_decrypt_in_calls< - D: StreamCipherDecryptor, + D: StreamCipherDecryptor, + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, >( - k: &KeyMaterial, + k: &K, iv: &[u8; INIT_DATA_LEN], scratch: &mut [u8], call_len: usize, @@ -154,7 +165,7 @@ fn cfb_decrypt_in_calls< } fn bench_cfb_aes128(c: &mut Criterion) { - let k = key::<16>(); + let k = key::(); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::cfb::AES_128"); @@ -165,7 +176,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, BLOCK_LEN>( + cfb_encrypt_in_calls::, _, 16, BLOCK_LEN>( &k, &mut scratch, 8 * BLOCK_LEN, @@ -185,7 +196,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { b.iter_batched( || ciphertext.clone(), |mut scratch| { - cfb_decrypt_in_calls::, 16, BLOCK_LEN>( + cfb_decrypt_in_calls::, _, 16, BLOCK_LEN>( &k, &iv, &mut scratch, @@ -206,7 +217,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { /// Encryption is strictly serial. Decryption builds its input blocks in series and then runs them /// through `encrypt_4blocks`, so it should still beat encryption by the four path's margin. fn bench_cfb8_aes128(c: &mut Criterion) { - let k = key::<16>(); + let k = key::(); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::cfb8::AES_128"); @@ -216,7 +227,7 @@ fn bench_cfb8_aes128(c: &mut Criterion) { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, BLOCK_LEN>( + cfb_encrypt_in_calls::, _, 16, BLOCK_LEN>( &k, &mut scratch, DATA_LEN, ); black_box(&scratch); @@ -233,7 +244,7 @@ fn bench_cfb8_aes128(c: &mut Criterion) { b.iter_batched( || ciphertext.clone(), |mut scratch| { - cfb_decrypt_in_calls::, 16, BLOCK_LEN>( + cfb_decrypt_in_calls::, _, 16, BLOCK_LEN>( &k, &iv, &mut scratch, DATA_LEN, ); black_box(&scratch); @@ -249,7 +260,7 @@ fn bench_cfb8_aes128(c: &mut Criterion) { /// keystream function, so one measurement covers it. It should match CFB decryption, not CFB /// encryption. fn bench_ctr_aes128(c: &mut Criterion) { - let k = key::<16>(); + let k = key::(); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::ctr::AES_128"); @@ -259,7 +270,7 @@ fn bench_ctr_aes128(c: &mut Criterion) { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, 12>( + cfb_encrypt_in_calls::, _, 16, 12>( &k, &mut scratch, 8 * BLOCK_LEN, @@ -273,10 +284,10 @@ fn bench_ctr_aes128(c: &mut Criterion) { group.finish(); } -/// ECB is a thin wrapper over the permutation's batch methods, so this should match `aes_benches`' -/// `encrypt_4blocks` figure; it is here to catch a regression in the wrapper. +/// ECB is a thin wrapper over the permutation's batch methods, so this measures the permutation's +/// four-block path; it is here to catch a regression in the wrapper. fn bench_ecb_aes128(c: &mut Criterion) { - let k = key::<16>(); + let k = key::(); let blocks = data(); let mut group = c.benchmark_group("modes::ecb::AES_128"); @@ -302,10 +313,9 @@ fn bench_ecb_aes128(c: &mut Criterion) { } /// `do_encrypt_init` is a key expansion plus an IV draw from the OS-backed DRBG, and for short -/// messages it dominates. The key expansion alone is `aes_benches`' `key expansion` group, so the -/// difference is the DRBG. Every mode does the same work here, so one is measured. +/// messages it dominates. Every mode does the same work here, so one is measured. fn bench_init(c: &mut Criterion) { - let k128 = key::<16>(); + let k128 = key::(); let mut group = c.benchmark_group("modes::init"); @@ -322,13 +332,12 @@ fn bench_init(c: &mut Criterion) { /// Sec 5.2 builds CCM out of CTR for confidentiality and CBC-MAC for authenticity, over the same /// key. The CBC-MAC half is serial (Sec 6.1 step 3: `Yi` is the cipher of `Bi XOR Yi-1`); the CTR /// half batches as [`Ctr`] does. So a payload block costs one unbatched call plus one batched one, -/// and the AAD-only measurement -- the CBC-MAC alone, one unbatched call per block -- should sit -/// close to `aes_benches`' `encrypt_block` figure. +/// and the AAD-only measurement is the CBC-MAC alone, one unbatched call per block. /// /// Decryption does the same work in the opposite order (Sec 6.1 and Sec 6.2), and AAD alongside a /// payload costs the sum of the two measurements here, so neither is measured separately. fn bench_ccm_aes128(c: &mut Criterion) { - let key = key::<16>(); + let key = key::(); let nonce = [0x24u8; CCM_NONCE_LEN]; let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; @@ -383,7 +392,7 @@ fn bench_ccm_aes128(c: &mut Criterion) { /// Decryption is the same work in the other order, and AAD alongside a payload costs the sum of /// the two measurements here, so neither is measured separately. fn bench_gcm_aes128(c: &mut Criterion) { - let key = key::<16>(); + let key = key::(); let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; let mut rng = FixedSeedRNG::::new([0x24u8; GCM_NONCE_LEN]); @@ -432,9 +441,9 @@ fn bench_gcm_aes128(c: &mut Criterion) { } /// AES-256 GCM: the one mode-level key-length point. The other modes scale with the round count -/// as `aes_benches` shows for the permutation itself. +/// as the permutation does. fn bench_gcm_aes256(c: &mut Criterion) { - let key = key::<32>(); + let key = key::(); let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; let mut rng = FixedSeedRNG::::new([0x24u8; GCM_NONCE_LEN]); diff --git a/crypto/aes/src/cbc.rs b/crypto/aes/src/cbc.rs index f853644e4..7e768cb14 100644 --- a/crypto/aes/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -13,9 +13,8 @@ //! Basic usage can be obtained via the [`SymmetricCipherEncryptor`] and [`SymmetricCipherDecryptor`] API: //! //! ``` -//! use bouncycastle_aes::AES_CBC_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::{AES_CBC_256, AES_CBC_256_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! use bouncycastle_cipher::padding::PKCS7; //! @@ -23,8 +22,7 @@ //! type AESEnc = AES_CBC_256; //! type AESDec = AES_CBC_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_CBC_256_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. @@ -43,9 +41,8 @@ //! For data that arrives in pieces, the following APIs can be used: //! //! ``` -//! use bouncycastle_aes::{AES_CBC_128, AES_BLOCK_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::{AES_CBC_128, AES_BLOCK_LEN, AES_CBC_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! use bouncycastle_cipher::padding::PKCS7; //! @@ -54,8 +51,7 @@ //! type AESDec = AES_CBC_128; //! //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CBC_128_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; @@ -101,16 +97,15 @@ //! error at `do_encrypt_final` rather than something silently padded: //! //! ``` -//! use bouncycastle_aes::AES_CBC_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! use bouncycastle_aes::{AES_CBC_128, AES_CBC_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::Encrypting; //! use bouncycastle_cipher::padding::NoPadding; //! //! // Define ourselves a convenience type for the encryption direction with no padding. //! type Enc = AES_CBC_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_CBC_128_Key::new_from_os().expect("a fresh key"); //! //! // A whole block is fine, and comes out the same length. //! let mut out = [0u8; 16]; @@ -126,13 +121,12 @@ //! interchanged. A value built with one will not satisfy a binding annotated with the other. //! //! ```compile_fail -//! use bouncycastle_aes::AES_CBC_128; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! use bouncycastle_aes::{AES_CBC_128, AES_CBC_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::Encrypting; //! use bouncycastle_cipher::padding::{NoPadding, PKCS7}; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_CBC_128_Key::new_from_os().expect("a fresh key"); //! //! // Built as NoPadding, annotated as PKCS7: mismatched types. //! let (enc, _iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); @@ -155,63 +149,155 @@ use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_cipher::modes::cbc; #[allow(unused_imports)] use bouncycastle_cipher::padding::{NoPadding, PKCS7}; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; #[allow(unused_imports)] use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // end of imports needed for docs /// AES-128 in CBC mode with a padding scheme. #[allow(non_camel_case_types)] pub type AES_CBC_128 = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc, Pad, + AES_CBC_128_Key, 16, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc, Pad, + AES_CBC_128_Key, 16, AES_BLOCK_LEN, AES_BLOCK_LEN, >, >; +/// An AES-CBC-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CBC_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_CBC_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} + /// AES-192 in CBC mode with a padding scheme. #[allow(non_camel_case_types)] pub type AES_CBC_192 = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc, Pad, + AES_CBC_192_Key, 24, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc, Pad, + AES_CBC_192_Key, 24, AES_BLOCK_LEN, AES_BLOCK_LEN, >, >; +/// An AES-CBC-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CBC_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_CBC_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} + /// AES-256 in CBC mode with a padding scheme. See [`AES_CBC_128`]. #[allow(non_camel_case_types)] pub type AES_CBC_256 = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc, Pad, + AES_CBC_256_Key, 32, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc, Pad, + AES_CBC_256_Key, 32, AES_BLOCK_LEN, AES_BLOCK_LEN, >, >; + +/// An AES-CBC-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CBC_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_CBC_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} diff --git a/crypto/aes/src/ccm.rs b/crypto/aes/src/ccm.rs index 4399aa43a..b9eab0702 100644 --- a/crypto/aes/src/ccm.rs +++ b/crypto/aes/src/ccm.rs @@ -63,9 +63,8 @@ //! payload; the one-shots take any length: //! //! ``` -//! use bouncycastle_aes::AES_CCM_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::{AES_CCM_128, AES_CCM_128_Key}; +//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Up to 64 bytes of AAD, and frames of exactly 2 KiB -- comfortably above an 802.11 frame, @@ -73,8 +72,7 @@ //! type AESEnc = AES_CCM_128; //! type AESDec = AES_CCM_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CCM_128_Key::new_from_os().expect("a fresh key"); //! //! let frame = [0x5Au8; 2048]; //! @@ -109,16 +107,15 @@ //! `ciphertext || tag` (Sec 6.1 step 8): //! //! ``` -//! use bouncycastle_aes::{AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_aes::{AES_CCM_256_Packet, AES_CCM_256_Key, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CCM_256_Packet; //! type AESDec = AES_CCM_256_Packet; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_CCM_256_Key::new_from_os().expect("a fresh key"); //! //! // Supplied, not generated. It is the caller's responsibility that it never repeat under this key. //! let nonce = [0x01u8; CCM_NONCE_LEN]; @@ -148,16 +145,15 @@ //! return and take it on its own: //! //! ``` -//! use bouncycastle_aes::{AES_CCM_128_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_CCM_128_Packet, AES_CCM_128_Key, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CCM_128_Packet; //! type AESDec = AES_CCM_128_Packet; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CCM_128_Key::new_from_os().expect("a fresh key"); //! let nonce = [0x02u8; CCM_NONCE_LEN]; //! let message = b"a short packet"; //! @@ -178,16 +174,15 @@ //! `do_encrypt_final`: //! //! ``` -//! use bouncycastle_aes::{AES_CCM_128_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_CCM_128_Packet, AES_CCM_128_Key, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CCM_128_Packet; //! type AESDec = AES_CCM_128_Packet; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CCM_128_Key::new_from_os().expect("a fresh key"); //! let nonce = [0x03u8; CCM_NONCE_LEN]; //! let aad = b"header"; //! let plaintext = [0x5Au8; 50]; @@ -248,6 +243,11 @@ use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::Direction; use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -273,17 +273,98 @@ pub const CCM_TAG_LEN: usize = 16; /// a compile error. Use [`CCM_NONCE_LEN`] and [`CCM_TAG_LEN`] if you have no reason to choose. #[allow(non_camel_case_types)] pub type AES_CCM_128_Packet = - CcmPacket; + CcmPacket; + +/// An AES-CCM-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CCM_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_CCM_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} /// AES-192 in CCM mode with a `NONCE_LEN`-byte nonce and a `TAG_LEN`-byte tag. See [`AES_CCM_128_Packet`]. #[allow(non_camel_case_types)] pub type AES_CCM_192_Packet = - CcmPacket; + CcmPacket; + +/// An AES-CCM-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CCM_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_CCM_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-256 in CCM mode with a `NONCE_LEN`-byte nonce and a `TAG_LEN`-byte tag. See [`AES_CCM_128_Packet`]. #[allow(non_camel_case_types)] pub type AES_CCM_256_Packet = - CcmPacket; + CcmPacket; + +/// An AES-CCM-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CCM_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_CCM_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-128 in CCM mode, as an [`AEADCipherEncryptor`] or [`AEADCipherDecryptor`] by `Dir`, for /// frames of exactly `DATA_LEN` payload bytes. @@ -302,8 +383,26 @@ pub type AES_CCM_128< const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmEncryptor< + AES128Internal, + AES_CCM_128_Key, + 16, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, + CcmDecryptor< + AES128Internal, + AES_CCM_128_Key, + 16, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, >; /// AES-192 in CCM mode, as an [`AEADCipherEncryptor`] or [`AEADCipherDecryptor`] by `Dir`. See [`AES_CCM_128`]. @@ -315,8 +414,26 @@ pub type AES_CCM_192< const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmEncryptor< + AES192Internal, + AES_CCM_192_Key, + 24, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, + CcmDecryptor< + AES192Internal, + AES_CCM_192_Key, + 24, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, >; /// AES-256 in CCM mode, as an [`AEADCipherEncryptor`] or [`AEADCipherDecryptor`] by `Dir`. See [`AES_CCM_128`]. @@ -328,6 +445,24 @@ pub type AES_CCM_256< const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmEncryptor< + AES256Internal, + AES_CCM_256_Key, + 32, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, + CcmDecryptor< + AES256Internal, + AES_CCM_256_Key, + 32, + AES_BLOCK_LEN, + NONCE_LEN, + TAG_LEN, + AAD_LEN, + DATA_LEN, + >, >; diff --git a/crypto/aes/src/cfb.rs b/crypto/aes/src/cfb.rs index c72606290..8279f698f 100644 --- a/crypto/aes/src/cfb.rs +++ b/crypto/aes/src/cfb.rs @@ -18,17 +18,15 @@ //! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: //! //! ``` -//! use bouncycastle_aes::AES_CFB_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_aes::{AES_CFB_256, AES_CFB_256_Key}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CFB_256; //! type AESDec = AES_CFB_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_CFB_256_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt. //! // Any length: a stream cipher does not need a whole number of blocks. @@ -50,11 +48,11 @@ //! length: //! //! ``` -//! use bouncycastle_aes::AES_CFB_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_CFB_128, AES_CFB_128_Key}; //! use bouncycastle_core::traits::{ //! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, //! SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! @@ -62,8 +60,7 @@ //! type AESEnc = AES_CFB_128; //! type AESDec = AES_CFB_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CFB_128_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; @@ -92,6 +89,11 @@ use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::Cfb; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -101,12 +103,93 @@ use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// AES-128 in CFB128 mode. #[allow(non_camel_case_types)] -pub type AES_CFB_128 = Cfb; +pub type AES_CFB_128 = Cfb; + +/// An AES-CFB-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_CFB_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} /// AES-192 in CFB128 mode. #[allow(non_camel_case_types)] -pub type AES_CFB_192 = Cfb; +pub type AES_CFB_192 = Cfb; + +/// An AES-CFB-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_CFB_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-256 in CFB128 mode. See [`AES_CFB_128`]. #[allow(non_camel_case_types)] -pub type AES_CFB_256 = Cfb; +pub type AES_CFB_256 = Cfb; + +/// An AES-CFB-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_CFB_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} diff --git a/crypto/aes/src/cfb8.rs b/crypto/aes/src/cfb8.rs index c4ec07579..4fa7e4db4 100644 --- a/crypto/aes/src/cfb8.rs +++ b/crypto/aes/src/cfb8.rs @@ -19,17 +19,15 @@ //! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: //! //! ``` -//! use bouncycastle_aes::AES_CFB8_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_aes::{AES_CFB8_256, AES_CFB8_256_Key}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CFB8_256; //! type AESDec = AES_CFB8_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_CFB8_256_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt. //! // 5 bytes: CFB8's segment is one byte, so any length at all is fine. @@ -51,11 +49,11 @@ //! length: //! //! ``` -//! use bouncycastle_aes::AES_CFB8_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_CFB8_128, AES_CFB8_128_Key}; //! use bouncycastle_core::traits::{ //! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, //! SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! @@ -63,8 +61,7 @@ //! type AESEnc = AES_CFB8_128; //! type AESDec = AES_CFB8_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CFB8_128_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; @@ -87,24 +84,24 @@ //! //! ## Not interoperable with CFB //! -//! CFB and CFB8 are not interchangeable: the same key and IV give a different ciphertext, so -//! a message encrypted with one does not decrypt with the other. +//! CFB and CFB8 are not interchangeable: the same key bytes and IV give a different ciphertext, +//! so a message encrypted with one does not decrypt with the other. The key types make the +//! mistake a compile error rather than a wrong answer: an [`AES_CFB8_128_Key`] is not an +//! [`AES_CFB_128_Key`](crate::AES_CFB_128_Key). //! -//! ``` -//! use bouncycastle_aes::{AES_CFB8_128, AES_CFB_128}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! ```compile_fail +//! use bouncycastle_aes::{AES_CFB8_128, AES_CFB_128, AES_CFB8_128_Key}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_CFB8_128_Key::new_from_os().expect("a fresh key"); //! let plaintext = *b"hello"; //! //! let mut data = plaintext; //! let (_, iv) = AES_CFB8_128::::encrypt_inplace(&key, &mut data).unwrap(); //! -//! // Decrypting CFB8 output as CFB128 does not recover the plaintext. +//! // Decrypting CFB8 output as CFB128 is refused: `key` is an AES-CFB8 key. //! AES_CFB_128::::decrypt_inplace(&key, &iv, &mut data).unwrap(); -//! assert_ne!(data, plaintext); //! ``` //! //! # 🚨 Security Considerations 🚨 @@ -115,6 +112,11 @@ use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::Cfb8; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -124,12 +126,93 @@ use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// AES-128 in CFB8 mode. #[allow(non_camel_case_types)] -pub type AES_CFB8_128 = Cfb8; +pub type AES_CFB8_128 = Cfb8; + +/// An AES-CFB8-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB8_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_CFB8_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} /// AES-192 in CFB8 mode. #[allow(non_camel_case_types)] -pub type AES_CFB8_192 = Cfb8; +pub type AES_CFB8_192 = Cfb8; + +/// An AES-CFB8-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB8_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_CFB8_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-256 in CFB8 mode. See [`AES_CFB8_128`]. #[allow(non_camel_case_types)] -pub type AES_CFB8_256 = Cfb8; +pub type AES_CFB8_256 = Cfb8; + +/// An AES-CFB8-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CFB8_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_CFB8_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} diff --git a/crypto/aes/src/ctr.rs b/crypto/aes/src/ctr.rs index c2456c47e..39869360a 100644 --- a/crypto/aes/src/ctr.rs +++ b/crypto/aes/src/ctr.rs @@ -23,17 +23,15 @@ //! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: //! //! ``` -//! use bouncycastle_aes::AES_CTR_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_aes::{AES_CTR_256, AES_CTR_256_Key}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_CTR_256; //! type AESDec = AES_CTR_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_CTR_256_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt. //! // Any length: a stream cipher does not need a whole number of blocks. @@ -55,11 +53,11 @@ //! length: //! //! ``` -//! use bouncycastle_aes::AES_CTR_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_CTR_128, AES_CTR_128_Key}; //! use bouncycastle_core::traits::{ //! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, //! SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! @@ -67,8 +65,7 @@ //! type AESEnc = AES_CTR_128; //! type AESDec = AES_CTR_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_CTR_128_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; @@ -97,6 +94,11 @@ use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::Ctr; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -109,12 +111,96 @@ pub const CTR_NONCE_LEN: usize = 12; /// AES-128 in CTR mode with a 12-byte nonce. #[allow(non_camel_case_types)] -pub type AES_CTR_128 = Ctr; +pub type AES_CTR_128 = + Ctr; + +/// An AES-CTR-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CTR_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_CTR_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} /// AES-192 in CTR mode with a 12-byte nonce. #[allow(non_camel_case_types)] -pub type AES_CTR_192 = Ctr; +pub type AES_CTR_192 = + Ctr; + +/// An AES-CTR-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CTR_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_CTR_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-256 in CTR mode with a 12-byte nonce. See [`AES_CTR_128`]. #[allow(non_camel_case_types)] -pub type AES_CTR_256 = Ctr; +pub type AES_CTR_256 = + Ctr; + +/// An AES-CTR-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_CTR_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_CTR_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} diff --git a/crypto/aes/src/gcm.rs b/crypto/aes/src/gcm.rs index 294dd56b5..06782757b 100644 --- a/crypto/aes/src/gcm.rs +++ b/crypto/aes/src/gcm.rs @@ -24,17 +24,15 @@ //! which returns the tag separately from the ciphertext: //! //! ``` -//! use bouncycastle_aes::AES_GCM_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +//! use bouncycastle_aes::{AES_GCM_256, AES_GCM_256_Key}; +//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_GCM_256; //! type AESDec = AES_GCM_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_GCM_256_Key::new_from_os().expect("a fresh key"); //! //! // The associated data is authenticated but not encrypted; the message is both. //! let aad = b"header, sent in the clear"; @@ -60,17 +58,15 @@ //! appended to the ciphertext, so the output is 16 bytes longer than the input: //! //! ``` -//! use bouncycastle_aes::AES_GCM_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::{AES_GCM_128, AES_GCM_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // Define ourselves convenience types. //! type AESEnc = AES_GCM_128; //! type AESDec = AES_GCM_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_GCM_128_Key::new_from_os().expect("a fresh key"); //! let message = b"a message of no particular length at all"; //! //! let mut ciphertext = vec![0u8; AESEnc::encrypt_out_len(message.len())]; @@ -89,10 +85,10 @@ //! `do_encrypt_final`: //! //! ``` -//! use bouncycastle_aes::AES_GCM_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_aes::{AES_GCM_128, AES_GCM_128_Key}; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! @@ -100,8 +96,7 @@ //! type AESEnc = AES_GCM_128; //! type AESDec = AES_GCM_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_GCM_128_Key::new_from_os().expect("a fresh key"); //! let aad = b"header"; //! let plaintext = [0x5Au8; 50]; //! @@ -143,6 +138,11 @@ use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::Gcm; +use bouncycastle_core::errors::{KeyMaterialError, RNGError}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::modes::GCM_NONCE_LEN; @@ -156,12 +156,93 @@ use bouncycastle_core::traits::{ /// AES-128 in GCM with a 128-bit tag. #[allow(non_camel_case_types)] -pub type AES_GCM_128 = Gcm; +pub type AES_GCM_128 = Gcm; + +/// An AES-GCM-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_GCM_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_GCM_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} /// AES-192 in GCM with a 128-bit tag. #[allow(non_camel_case_types)] -pub type AES_GCM_192 = Gcm; +pub type AES_GCM_192 = Gcm; + +/// An AES-GCM-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_GCM_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_GCM_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} /// AES-256 in GCM with a 128-bit tag. See [`AES_GCM_128`]. #[allow(non_camel_case_types)] -pub type AES_GCM_256 = Gcm; +pub type AES_GCM_256 = Gcm; + +/// An AES-GCM-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_GCM_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_GCM_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} diff --git a/crypto/aes/src/hazmat/aes_internal.rs b/crypto/aes/src/hazmat/aes_internal.rs index 451015a8a..cfc261f8a 100644 --- a/crypto/aes/src/hazmat/aes_internal.rs +++ b/crypto/aes/src/hazmat/aes_internal.rs @@ -7,8 +7,12 @@ //! //! ## Encrypting and decrypting a single block //! +//! The engine implements [`ElectronicCodeBook`] for every key type of its key length (the modes +//! each bring their own), so a call on a bare engine has to name one; here the ECB key, as the +//! raw permutation is what ECB is. +//! //! ``` -//! use bouncycastle_aes::hazmat::AES128Internal; +//! use bouncycastle_aes::hazmat::{AES128Internal, AES_ECB_128_Key}; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::hazmat::ElectronicCodeBook; //! @@ -18,12 +22,12 @@ //! KeyType::SymmetricCipherKey, //! ).expect("a 16-byte symmetric cipher key"); //! -//! let aes = AES128Internal::new(&key).expect("a valid AES-128 key"); +//! let aes = >::new(&key).expect("a valid AES-128 key"); //! //! // Sample plaintext from FIPS 197 Appendix B. //! let mut block: [u8; 16] = [0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, //! 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34]; -//! aes.encrypt_block(&mut block); +//! >::encrypt_block(&aes, &mut block); //! //! // `block` now contains the ciphertext. //! // Double-check it against the sample ciphertext from FIPS 197 Appdx B. @@ -31,7 +35,7 @@ //! 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32]); //! //! // The same value decrypts, from the same instantiated aes object. -//! aes.decrypt_block(&mut block); +//! >::decrypt_block(&aes, &mut block); //! //! // `block` now contains the original plaintext again. //! assert_eq!(block, [0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, @@ -50,22 +54,22 @@ //! docs have the table and the benches record the numbers: //! //! ``` -//! use bouncycastle_aes::hazmat::AES256Internal; +//! use bouncycastle_aes::hazmat::{AES256Internal, AES_ECB_256_Key}; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::hazmat::ElectronicCodeBook; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); -//! let aes = AES256Internal::new(&key).expect("a valid AES-256 key"); +//! let aes = >::new(&key).expect("a valid AES-256 key"); //! //! let mut pair = [[0u8; 16], [1u8; 16]]; -//! aes.encrypt_2blocks(&mut pair); -//! aes.decrypt_2blocks(&mut pair); +//! >::encrypt_2blocks(&aes, &mut pair); +//! >::decrypt_2blocks(&aes, &mut pair); //! assert_eq!(pair, [[0u8; 16], [1u8; 16]]); //! //! let mut four = [[0u8; 16], [1u8; 16], [2u8; 16], [3u8; 16]]; -//! aes.encrypt_4blocks(&mut four); -//! aes.decrypt_4blocks(&mut four); +//! >::encrypt_4blocks(&aes, &mut four); +//! >::decrypt_4blocks(&aes, &mut four); //! assert_eq!(four, [[0u8; 16], [1u8; 16], [2u8; 16], [3u8; 16]]); //! ``` diff --git a/crypto/aes/src/hazmat/ecb.rs b/crypto/aes/src/hazmat/ecb.rs index c2b44d912..1acc3a8d2 100644 --- a/crypto/aes/src/hazmat/ecb.rs +++ b/crypto/aes/src/hazmat/ecb.rs @@ -22,9 +22,8 @@ //! Basic usage can be obtained via the [`SymmetricCipherEncryptor`] and [`SymmetricCipherDecryptor`] API: //! //! ``` -//! use bouncycastle_aes::hazmat::AES_ECB_256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::hazmat::{AES_ECB_256, AES_ECB_256_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! use bouncycastle_cipher::padding::PKCS7; //! @@ -32,8 +31,7 @@ //! type AESEnc = AES_ECB_256; //! type AESDec = AES_ECB_256; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); +//! let key = AES_ECB_256_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. @@ -54,9 +52,8 @@ //! //! ``` //! use bouncycastle_aes::AES_BLOCK_LEN; -//! use bouncycastle_aes::hazmat::AES_ECB_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! use bouncycastle_cipher::padding::PKCS7; //! @@ -64,8 +61,7 @@ //! type AESEnc = AES_ECB_128; //! type AESDec = AES_ECB_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES_ECB_128_Key::new_from_os().expect("a fresh key"); //! //! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; @@ -111,16 +107,15 @@ //! error rather than something silently padded: //! //! ``` -//! use bouncycastle_aes::hazmat::AES_ECB_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::Encrypting; //! use bouncycastle_cipher::padding::NoPadding; //! //! // Define ourselves a convenience type for the encryption direction with no padding. //! type Enc = AES_ECB_128; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_ECB_128_Key::new_from_os().expect("a fresh key"); //! //! // A whole block is fine, and comes out the same length. //! let mut out = [0u8; 16]; @@ -136,13 +131,12 @@ //! interchanged. A value built with one will not satisfy a binding annotated with the other. //! //! ```compile_fail -//! use bouncycastle_aes::hazmat::AES_ECB_128; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::Encrypting; //! use bouncycastle_cipher::padding::{NoPadding, PKCS7}; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_ECB_128_Key::new_from_os().expect("a fresh key"); //! //! // Built as NoPadding, annotated as PKCS7: mismatched types. //! let (enc, _no_iv) = AES_ECB_128::::do_encrypt_init(&key).unwrap(); @@ -157,13 +151,12 @@ //! that in the least. It makes ECB accept any length; it does not make it safe. //! //! ``` -//! use bouncycastle_aes::hazmat::AES_ECB_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_128_Key}; +//! use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::Encrypting; //! use bouncycastle_cipher::padding::NoPadding; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AES_ECB_128_Key::new_from_os().expect("a fresh key"); //! //! // Two identical blocks in... //! let (_, ciphertext) = @@ -180,9 +173,12 @@ use bouncycastle_cipher::Direction; use bouncycastle_cipher::modes::hazmat::ecb::Ecb; use bouncycastle_cipher::padding::{PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::errors::{KeyMaterialError, RNGError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; // Imports needed for docs #[allow(unused_imports)] @@ -195,60 +191,147 @@ use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncrypt #[allow(non_camel_case_types)] pub type AES_ECB_128 = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb, Pad, + AES_ECB_128_Key, 16, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb, Pad, + AES_ECB_128_Key, 16, 0, AES_BLOCK_LEN, >, >; +/// An AES-ECB-128 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_ECB_128_Key(KeyMaterial<16>); + +impl SymmetricCipherKey<16> for AES_ECB_128_Key { + fn from_keymaterial(key: KeyMaterial<16>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 128bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<16> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} + /// AES-192 in ECB mode with a padding scheme. #[allow(non_camel_case_types)] pub type AES_ECB_192 = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb, Pad, + AES_ECB_192_Key, 24, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb, Pad, + AES_ECB_192_Key, 24, 0, AES_BLOCK_LEN, >, >; +/// An AES-ECB-192 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_ECB_192_Key(KeyMaterial<24>); + +impl SymmetricCipherKey<24> for AES_ECB_192_Key { + fn from_keymaterial(key: KeyMaterial<24>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_192bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 192bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<24> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} + /// AES-256 in ECB mode with a padding scheme. See [`AES_ECB_128`]. #[allow(non_camel_case_types)] pub type AES_ECB_256 = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb, Pad, + AES_ECB_256_Key, 32, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb, Pad, + AES_ECB_256_Key, 32, 0, AES_BLOCK_LEN, >, >; -impl ElectronicCodeBook<16, AES_BLOCK_LEN> for AES128Internal { +/// An AES-ECB-256 key. +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct AES_ECB_256_Key(KeyMaterial<32>); + +impl SymmetricCipherKey<32> for AES_ECB_256_Key { + fn from_keymaterial(key: KeyMaterial<32>) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_256bit { + return Err(KeyMaterialError::InvalidKeyType( + "Key's Security strength must be at least 256bit", + )); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial<32> { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA512::new_from_os()) + } +} + +impl> ElectronicCodeBook for AES128Internal { fn new(key: &KeyMaterial<16>) -> Result { AES128Internal::new(key) } @@ -272,7 +355,7 @@ impl ElectronicCodeBook<16, AES_BLOCK_LEN> for AES128Internal { } } -impl ElectronicCodeBook<24, AES_BLOCK_LEN> for AES192Internal { +impl> ElectronicCodeBook for AES192Internal { fn new(key: &KeyMaterial<24>) -> Result { AES192Internal::new(key) } @@ -296,7 +379,7 @@ impl ElectronicCodeBook<24, AES_BLOCK_LEN> for AES192Internal { } } -impl ElectronicCodeBook<32, AES_BLOCK_LEN> for AES256Internal { +impl> ElectronicCodeBook for AES256Internal { fn new(key: &KeyMaterial<32>) -> Result { AES256Internal::new(key) } diff --git a/crypto/aes/src/hazmat/mod.rs b/crypto/aes/src/hazmat/mod.rs index 4ba527e85..f08fcf559 100644 --- a/crypto/aes/src/hazmat/mod.rs +++ b/crypto/aes/src/hazmat/mod.rs @@ -20,4 +20,6 @@ mod aes_internal; mod ecb; pub use aes_internal::{AES128Internal, AES192Internal, AES256Internal, AESInternal}; -pub use ecb::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; +pub use ecb::{ + AES_ECB_128, AES_ECB_128_Key, AES_ECB_192, AES_ECB_192_Key, AES_ECB_256, AES_ECB_256_Key, +}; diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index 1c72cf6b6..8aa70040e 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -255,12 +255,24 @@ impl AESParams for AES256Params { type Schedule = [u32; 60]; // 4 * (14 + 1) } -pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +pub use cbc::{ + AES_CBC_128, AES_CBC_128_Key, AES_CBC_192, AES_CBC_192_Key, AES_CBC_256, AES_CBC_256_Key, +}; pub use ccm::{ - AES_CCM_128, AES_CCM_128_Packet, AES_CCM_192, AES_CCM_192_Packet, AES_CCM_256, - AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN, + AES_CCM_128, AES_CCM_128_Key, AES_CCM_128_Packet, AES_CCM_192, AES_CCM_192_Key, + AES_CCM_192_Packet, AES_CCM_256, AES_CCM_256_Key, AES_CCM_256_Packet, CCM_NONCE_LEN, + CCM_TAG_LEN, +}; +pub use cfb::{ + AES_CFB_128, AES_CFB_128_Key, AES_CFB_192, AES_CFB_192_Key, AES_CFB_256, AES_CFB_256_Key, +}; +pub use cfb8::{ + AES_CFB8_128, AES_CFB8_128_Key, AES_CFB8_192, AES_CFB8_192_Key, AES_CFB8_256, AES_CFB8_256_Key, +}; +pub use ctr::{ + AES_CTR_128, AES_CTR_128_Key, AES_CTR_192, AES_CTR_192_Key, AES_CTR_256, AES_CTR_256_Key, + CTR_NONCE_LEN, +}; +pub use gcm::{ + AES_GCM_128, AES_GCM_128_Key, AES_GCM_192, AES_GCM_192_Key, AES_GCM_256, AES_GCM_256_Key, }; -pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; -pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; -pub use ctr::{AES_CTR_128, AES_CTR_192, AES_CTR_256, CTR_NONCE_LEN}; -pub use gcm::{AES_GCM_128, AES_GCM_192, AES_GCM_256}; diff --git a/crypto/aes/tests/alias_tests.rs b/crypto/aes/tests/alias_tests.rs index d0c2f5dd7..3cf5655e2 100644 --- a/crypto/aes/tests/alias_tests.rs +++ b/crypto/aes/tests/alias_tests.rs @@ -7,12 +7,14 @@ //! report the same name. So must the `AES_CCM_*_Packet` aliases, which are a second API onto the //! same mode rather than another algorithm. -use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256, AES128Internal}; +use bouncycastle_aes::hazmat::{ + AES_ECB_128, AES_ECB_128_Key, AES_ECB_192, AES_ECB_256, AES128Internal, +}; use bouncycastle_aes::{ - AES_CBC_128, AES_CBC_192, AES_CBC_256, AES_CCM_128, AES_CCM_128_Packet, AES_CCM_192, - AES_CCM_192_Packet, AES_CCM_256, AES_CCM_256_Packet, AES_CFB_128, AES_CFB_192, AES_CFB_256, - AES_CFB8_128, AES_CFB8_192, AES_CFB8_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, AES_GCM_128, - AES_GCM_192, AES_GCM_256, CCM_NONCE_LEN, CCM_TAG_LEN, + AES_CBC_128, AES_CBC_128_Key, AES_CBC_192, AES_CBC_256, AES_CCM_128, AES_CCM_128_Packet, + AES_CCM_192, AES_CCM_192_Packet, AES_CCM_256, AES_CCM_256_Packet, AES_CFB_128, AES_CFB_192, + AES_CFB_256, AES_CFB8_128, AES_CFB8_192, AES_CFB8_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, + AES_GCM_128, AES_GCM_128_Key, AES_GCM_192, AES_GCM_256, CCM_NONCE_LEN, CCM_TAG_LEN, }; use bouncycastle_cipher::modes::hazmat::ecb::Ecb; use bouncycastle_cipher::modes::{Cbc, Gcm}; @@ -131,13 +133,27 @@ fn the_aliases_name_the_expected_types() { assert_eq!( size_of::>(), size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>, + PaddedBlockCipherEncryptor< + Cbc, + PKCS7, + AES_CBC_128_Key, + 16, + 16, + 16, + >, >() ); assert_eq!( size_of::>(), size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 16, 16>, + PaddedBlockCipherDecryptor< + Cbc, + PKCS7, + AES_CBC_128_Key, + 16, + 16, + 16, + >, >() ); // The two directions are genuinely different types, so the encryptor and the decryptor do not @@ -151,22 +167,36 @@ fn the_aliases_name_the_expected_types() { assert_eq!( size_of::>(), size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 0, 16>, + PaddedBlockCipherEncryptor< + Ecb, + PKCS7, + AES_ECB_128_Key, + 16, + 0, + 16, + >, >() ); assert_eq!( size_of::>(), size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 0, 16>, + PaddedBlockCipherDecryptor< + Ecb, + PKCS7, + AES_ECB_128_Key, + 16, + 0, + 16, + >, >() ); assert_eq!( size_of::>(), - size_of::>() + size_of::>() ); assert_eq!( size_of::>(), - size_of::>() + size_of::>() ); } diff --git a/crypto/aes/tests/cbc_bc-test-data.rs b/crypto/aes/tests/cbc_bc-test-data.rs index c3bf389bd..6f83750cd 100644 --- a/crypto/aes/tests/cbc_bc-test-data.rs +++ b/crypto/aes/tests/cbc_bc-test-data.rs @@ -5,8 +5,7 @@ //! matching the other vector suites in this crate and the ML-KEM and ML-DSA ones -- `cargo test` //! must stay green for someone who has only cloned this repository. //! -//! These are the counterpart to `ecb_bc-test-data.rs`, which consumes the -//! `ACVP-AES-ECB` file to test the raw permutation. CBC is a mode, so its vectors belong here. +//! These are the counterpart to `ecb_bc-test-data.rs`, which consumes the `ACVP-AES-ECB` file. //! //! # Joining the request and response files //! @@ -31,10 +30,12 @@ //! how many it skipped so the gap stays visible. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CBC_128_Key, AES_CBC_192_Key, AES_CBC_256_Key}; use bouncycastle_cipher::modes::Cbc; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; @@ -64,7 +65,7 @@ enum Grouping { /// Encryption is driven through `do_encrypt_init_rng` with a `FixedSeedRNG` emitting the vector's /// IV, and the returned init data is checked against that IV before any ciphertext is compared -- /// so a change that ignored the RNG could not pass silently. -fn run_case( +fn run_case( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[[u8; BLOCK_LEN]], @@ -72,13 +73,14 @@ fn run_case( grouping: Grouping, ) -> Vec<[u8; BLOCK_LEN]> where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let mut out: Vec<[u8; BLOCK_LEN]> = Vec::with_capacity(input.len()); if encrypt { - let (mut enc, got_iv) = Cbc::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cbc::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -108,8 +110,8 @@ where } } } else { - let mut dec = - Cbc::::do_decrypt_init(&key, &iv).expect("dec init"); + let mut dec = Cbc::::do_decrypt_init(&key, &iv) + .expect("dec init"); match grouping { Grouping::Single => { @@ -147,9 +149,15 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec<[u8; BLOCK_LEN]> { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } + 24 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } + 32 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/aes/tests/cbc_wycheproof.rs b/crypto/aes/tests/cbc_wycheproof.rs index 9ca9a2c27..43d821722 100644 --- a/crypto/aes/tests/cbc_wycheproof.rs +++ b/crypto/aes/tests/cbc_wycheproof.rs @@ -27,12 +27,14 @@ //! asserted to be the vector's. use bouncycastle_aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle_aes::{AES_CBC_128_Key, AES_CBC_192_Key, AES_CBC_256_Key}; use bouncycastle_cipher::padding::PKCS7; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::{PaddingError, SymmetricCipherError}; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; @@ -41,19 +43,22 @@ const BLOCK_LEN: usize = 16; /// Wraps the vector's raw key bytes, promoting them if `KeyMaterial`'s entropy heuristic declined /// to call them a cipher key. Same helper as the other vector suites in this crate. -fn cipher_key(bytes: &[u8]) -> KeyMaterial { - assert_eq!(bytes.len(), N, "key length should match the parameter set"); - let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) - .expect("wycheproof key bytes fit the buffer"); - - if key.key_type() != KeyType::SymmetricCipherKey { - do_hazardous_operations(&mut key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(SecurityStrength::from_bytes(N)) - }) - .expect("promoting a wycheproof test key"); - } - key +fn cipher_key, const N: usize>(bytes: &[u8]) -> K { + K::from_keymaterial({ + assert_eq!(bytes.len(), N, "key length should match the parameter set"); + let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) + .expect("wycheproof key bytes fit the buffer"); + + if key.key_type() != KeyType::SymmetricCipherKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::SymmetricCipherKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .expect("promoting a wycheproof test key"); + } + key + }) + .expect("a valid key") } /// What an invalid case must fail with, from its `flags`. @@ -77,7 +82,7 @@ enum Expected { /// mis-padded `ct` -- and it must fail with the variant the case's flags predict, leaving the /// output buffer zeroized: CBC writes plaintext before it reaches the padding, and /// `SymmetricCipherDecryptor::decrypt_out` must wipe it when the final block does not unpad. -fn run_case( +fn run_case( tc_id: u64, key_bytes: &[u8], iv: [u8; BLOCK_LEN], @@ -85,10 +90,11 @@ fn run_case( expected_ct: &[u8], expected: Expected, ) where - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + K: SymmetricCipherKey, + E: SymmetricCipherEncryptor, + D: SymmetricCipherDecryptor, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); if expected == Expected::Valid { let mut ct = vec![0u8; E::encrypt_out_len(msg.len())]; @@ -139,15 +145,24 @@ fn dispatch( expected: Expected, ) { match key_bytes.len() { - 16 => run_case::, AES_CBC_128, 16>( - tc_id, key_bytes, iv, msg, expected_ct, expected, - ), - 24 => run_case::, AES_CBC_192, 24>( - tc_id, key_bytes, iv, msg, expected_ct, expected, - ), - 32 => run_case::, AES_CBC_256, 32>( - tc_id, key_bytes, iv, msg, expected_ct, expected, - ), + 16 => run_case::< + AES_CBC_128, + AES_CBC_128, + AES_CBC_128_Key, + 16, + >(tc_id, key_bytes, iv, msg, expected_ct, expected), + 24 => run_case::< + AES_CBC_192, + AES_CBC_192, + AES_CBC_192_Key, + 24, + >(tc_id, key_bytes, iv, msg, expected_ct, expected), + 32 => run_case::< + AES_CBC_256, + AES_CBC_256, + AES_CBC_256_Key, + 32, + >(tc_id, key_bytes, iv, msg, expected_ct, expected), other => panic!("tcId {tc_id}: AES keys are 16, 24 or 32 bytes, got {other}"), } } diff --git a/crypto/aes/tests/ccm_bc-test-data.rs b/crypto/aes/tests/ccm_bc-test-data.rs index e10ed819d..a42cdb895 100644 --- a/crypto/aes/tests/ccm_bc-test-data.rs +++ b/crypto/aes/tests/ccm_bc-test-data.rs @@ -46,12 +46,13 @@ //! set is `testType: "AFT"`, so nothing is skipped for that reason. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CCM_128_Key, AES_CCM_192_Key, AES_CCM_256_Key}; use bouncycastle_cipher::modes::CcmPacket; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use std::collections::BTreeMap; @@ -79,17 +80,18 @@ enum Decrypted { /// Also re-runs it through the length-declared streaming API in several chunkings, since these are /// the only real vectors available for that path and the one-shot is a single call over the whole /// payload. -fn encrypt_case( - key: &KeyMaterial, +fn encrypt_case( + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { let mut inline = vec![0u8; plaintext.len() + TAG_LEN]; - let written = CcmPacket::::encrypt_out( + let written = CcmPacket::::encrypt_out( key, nonce, aad, plaintext, &mut inline, ) .expect("CCM encryption of a valid ACVP case"); @@ -97,7 +99,7 @@ where // The same answer must come out of the streaming API, in any chunking of both phases. for chunk in [1usize, 5, 16] { - let mut ccm = CcmPacket::::new( + let mut ccm = CcmPacket::::new( key, nonce, aad, @@ -117,17 +119,18 @@ where } /// Runs one decrypt case over the inline `ciphertext || tag` string the vectors carry. -fn decrypt_case( - key: &KeyMaterial, +fn decrypt_case( + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ct_and_tag: &[u8], ) -> Decrypted where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { let mut plaintext = vec![0u8; ct_and_tag.len().saturating_sub(TAG_LEN)]; - match CcmPacket::::decrypt_out( + match CcmPacket::::decrypt_out( key, nonce, aad, ct_and_tag, &mut plaintext, ) { Ok(n) => { @@ -163,12 +166,12 @@ fn run_case( input: &[u8], ) -> Result, ()> { macro_rules! dispatch { - ($k:literal, $t:literal, $p:ty) => {{ - let key = cipher_key::<$k>(key_bytes); + ($k:literal, $t:literal, $p:ty, $key:ty) => {{ + let key = cipher_key::<$key, $k>(key_bytes); if encrypt { - Ok(encrypt_case::<$k, $t, $p>(&key, nonce, aad, input)) + Ok(encrypt_case::<$k, $t, $p, $key>(&key, nonce, aad, input)) } else { - match decrypt_case::<$k, $t, $p>(&key, nonce, aad, input) { + match decrypt_case::<$k, $t, $p, $key>(&key, nonce, aad, input) { Decrypted::Plaintext(p) => Ok(p), Decrypted::TagCheckFailed => Err(()), } @@ -181,12 +184,12 @@ fn run_case( // runtime values. The body is one expression, and each arm is its own instantiation, so // `cargo mutants` still sees the code it expands to. match (key_len, tag_len) { - (128, 96) => dispatch!(16, 12, AES128Internal), - (128, 128) => dispatch!(16, 16, AES128Internal), - (192, 96) => dispatch!(24, 12, AES192Internal), - (192, 128) => dispatch!(24, 16, AES192Internal), - (256, 96) => dispatch!(32, 12, AES256Internal), - (256, 128) => dispatch!(32, 16, AES256Internal), + (128, 96) => dispatch!(16, 12, AES128Internal, AES_CCM_128_Key), + (128, 128) => dispatch!(16, 16, AES128Internal, AES_CCM_128_Key), + (192, 96) => dispatch!(24, 12, AES192Internal, AES_CCM_192_Key), + (192, 128) => dispatch!(24, 16, AES192Internal, AES_CCM_192_Key), + (256, 96) => dispatch!(32, 12, AES256Internal, AES_CCM_256_Key), + (256, 128) => dispatch!(32, 16, AES256Internal, AES_CCM_256_Key), other => panic!("tcId {tc_id}: unexpected (keyLen, tagLen) {other:?}"), } } diff --git a/crypto/aes/tests/ccm_wycheproof.rs b/crypto/aes/tests/ccm_wycheproof.rs index c84b1d88a..dd619e0ec 100644 --- a/crypto/aes/tests/ccm_wycheproof.rs +++ b/crypto/aes/tests/ccm_wycheproof.rs @@ -34,6 +34,7 @@ //! asserted at the end so a change in the vector file's shape is visible. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CCM_128_Key, AES_CCM_192_Key, AES_CCM_256_Key}; use bouncycastle_cipher::modes::CcmPacket; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -42,23 +43,27 @@ use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; /// Wraps the vector's raw key bytes, promoting them if `KeyMaterial`'s entropy heuristic declined /// to call them a cipher key. Same helper as the ACVP suite in this crate. -fn cipher_key(bytes: &[u8]) -> KeyMaterial { - assert_eq!(bytes.len(), N, "key length should match the parameter set"); - let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) - .expect("wycheproof key bytes fit the buffer"); +fn cipher_key, const N: usize>(bytes: &[u8]) -> K { + K::from_keymaterial({ + assert_eq!(bytes.len(), N, "key length should match the parameter set"); + let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) + .expect("wycheproof key bytes fit the buffer"); - if key.key_type() != KeyType::SymmetricCipherKey { - do_hazardous_operations(&mut key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(SecurityStrength::from_bytes(N)) - }) - .expect("promoting a wycheproof test key"); - } - key + if key.key_type() != KeyType::SymmetricCipherKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::SymmetricCipherKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .expect("promoting a wycheproof test key"); + } + key + }) + .expect("a valid key") } /// Runs one case at a fully-instantiated `(KEY_LEN, NONCE_LEN, TAG_LEN, P)`. @@ -70,7 +75,7 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { /// must fail the tag check rather than return a payload, leaving the output buffer zeroized as /// [`CcmPacket::decrypt_detached_out`] documents. #[allow(clippy::too_many_arguments)] -fn run_case( +fn run_case( tc_id: u64, key_bytes: &[u8], nonce_bytes: &[u8], @@ -80,9 +85,10 @@ fn run_case + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let nonce: [u8; NONCE_LEN] = nonce_bytes.try_into().unwrap_or_else(|_| panic!("tcId {tc_id}: bad nonce length")); let tag: [u8; TAG_LEN] = @@ -91,7 +97,7 @@ fn run_case::encrypt_detached_out( + CcmPacket::::encrypt_detached_out( &key, &nonce, aad, msg, &mut ct, ) .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); @@ -103,7 +109,7 @@ fn run_case::decrypt_detached_out( + match CcmPacket::::decrypt_detached_out( &key, &nonce, aad, expected_ct, &tag, &mut plaintext, ) { Ok(n) => { @@ -142,19 +148,19 @@ fn dispatch( ($n:literal, $t:literal) => { match key_len_bytes { 16 => { - run_case::<16, $n, $t, AES128Internal>( + run_case::<16, $n, $t, AES128Internal, AES_CCM_128_Key>( tc_id, key_bytes, nonce_bytes, aad, msg, expected_ct, expected_tag, valid, ); true } 24 => { - run_case::<24, $n, $t, AES192Internal>( + run_case::<24, $n, $t, AES192Internal, AES_CCM_192_Key>( tc_id, key_bytes, nonce_bytes, aad, msg, expected_ct, expected_tag, valid, ); true } 32 => { - run_case::<32, $n, $t, AES256Internal>( + run_case::<32, $n, $t, AES256Internal, AES_CCM_256_Key>( tc_id, key_bytes, nonce_bytes, aad, msg, expected_ct, expected_tag, valid, ); true diff --git a/crypto/aes/tests/cfb8_bc-test-data.rs b/crypto/aes/tests/cfb8_bc-test-data.rs index 3b2ece09f..e8ae4aae0 100644 --- a/crypto/aes/tests/cfb8_bc-test-data.rs +++ b/crypto/aes/tests/cfb8_bc-test-data.rs @@ -6,7 +6,7 @@ //! must stay green for someone who has only cloned this repository. //! //! This is the CFB8 counterpart to `cfb_bc-test-data.rs` (AES-CFB128), `cbc_bc-test-data.rs` -//! (AES-CBC) and `ecb_bc-test-data.rs` (AES-ECB, the raw permutation). `ACVP-AES-CFB1` is +//! (AES-CBC) and `ecb_bc-test-data.rs` (AES-ECB). `ACVP-AES-CFB1` is //! the one remaining segment size, which this crate does not implement, and is not read. //! //! # Joining the request and response files @@ -34,10 +34,12 @@ //! how many it skipped so the gap stays visible. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CFB8_128_Key, AES_CFB8_192_Key, AES_CFB8_256_Key}; use bouncycastle_cipher::modes::Cfb8; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -86,7 +88,7 @@ impl Grouping { /// Encryption is driven through `do_encrypt_init_rng` with a `FixedSeedRNG` emitting the vector's /// IV, and the returned init data is checked against that IV before any ciphertext is compared -- /// so a change that ignored the RNG could not pass silently. -fn run_case( +fn run_case( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[u8], @@ -94,14 +96,15 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let mut data = input.to_vec(); let chunk = grouping.chunk_len(data.len()); if encrypt { - let (mut enc, got_iv) = Cfb8::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cfb8::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -111,7 +114,7 @@ where enc.do_encrypt_inplace(piece).unwrap(); } } else { - let mut dec = Cfb8::::do_decrypt_init(&key, &iv) + let mut dec = Cfb8::::do_decrypt_init(&key, &iv) .expect("dec init"); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -130,9 +133,15 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => run_case::( + key_bytes, iv, input, encrypt, grouping, + ), + 24 => run_case::( + key_bytes, iv, input, encrypt, grouping, + ), + 32 => run_case::( + key_bytes, iv, input, encrypt, grouping, + ), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/aes/tests/cfb_bc-test-data.rs b/crypto/aes/tests/cfb_bc-test-data.rs index 52621ec3e..9b5c292d9 100644 --- a/crypto/aes/tests/cfb_bc-test-data.rs +++ b/crypto/aes/tests/cfb_bc-test-data.rs @@ -6,7 +6,7 @@ //! must stay green for someone who has only cloned this repository. //! //! This is the CFB128 counterpart to `cbc_bc-test-data.rs` (AES-CBC) and to -//! `ecb_bc-test-data.rs` (AES-ECB, the raw permutation). The `CFB128` file is +//! `ecb_bc-test-data.rs` (AES-ECB). The `CFB128` file is //! the one that matches [`Cfb`]; `ACVP-AES-CFB8` matches `Cfb8` and is read by //! `cfb8_bc-test-data.rs`. `ACVP-AES-CFB1` is the one segment size this crate does not implement, //! and is deliberately not read. @@ -38,10 +38,12 @@ //! how many it skipped so the gap stays visible. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CFB_128_Key, AES_CFB_192_Key, AES_CFB_256_Key}; use bouncycastle_cipher::modes::Cfb; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -91,7 +93,7 @@ impl Grouping { /// Encryption is driven through `do_encrypt_init_rng` with a `FixedSeedRNG` emitting the vector's /// IV, and the returned init data is checked against that IV before any ciphertext is compared -- /// so a change that ignored the RNG could not pass silently. -fn run_case( +fn run_case( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[u8], @@ -99,14 +101,15 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let mut data = input.to_vec(); let chunk = grouping.chunk_len(data.len()); if encrypt { - let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -116,8 +119,8 @@ where enc.do_encrypt_inplace(piece).unwrap(); } } else { - let mut dec = - Cfb::::do_decrypt_init(&key, &iv).expect("dec init"); + let mut dec = Cfb::::do_decrypt_init(&key, &iv) + .expect("dec init"); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); } @@ -135,9 +138,15 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } + 24 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } + 32 => { + run_case::(key_bytes, iv, input, encrypt, grouping) + } other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/aes/tests/common/acvp_gcm_helpers.rs b/crypto/aes/tests/common/acvp_gcm_helpers.rs index 4dc660446..f50f3dd68 100644 --- a/crypto/aes/tests/common/acvp_gcm_helpers.rs +++ b/crypto/aes/tests/common/acvp_gcm_helpers.rs @@ -10,11 +10,12 @@ #![allow(dead_code)] use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_GCM_128_Key, AES_GCM_192_Key, AES_GCM_256_Key}; use bouncycastle_cipher::modes::Gcm; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, }; @@ -41,46 +42,48 @@ pub fn run_encrypt_case( expected_tag: &[u8], ) { macro_rules! dispatch { - ($p:ty, $klen:literal) => {{ - let key = cipher_key::<$klen>(key_bytes); + ($p:ty, $key:ty, $klen:literal) => {{ + let key = cipher_key::<$key, $klen>(key_bytes); let mut data = pt.to_vec(); match tag_len { - 12 => run_encrypt::<$p, $klen, 12>(&key, iv, aad, &mut data, expected_tag), - 13 => run_encrypt::<$p, $klen, 13>(&key, iv, aad, &mut data, expected_tag), - 14 => run_encrypt::<$p, $klen, 14>(&key, iv, aad, &mut data, expected_tag), - 15 => run_encrypt::<$p, $klen, 15>(&key, iv, aad, &mut data, expected_tag), - 16 => run_encrypt::<$p, $klen, 16>(&key, iv, aad, &mut data, expected_tag), + 12 => run_encrypt::<$p, $key, $klen, 12>(&key, iv, aad, &mut data, expected_tag), + 13 => run_encrypt::<$p, $key, $klen, 13>(&key, iv, aad, &mut data, expected_tag), + 14 => run_encrypt::<$p, $key, $klen, 14>(&key, iv, aad, &mut data, expected_tag), + 15 => run_encrypt::<$p, $key, $klen, 15>(&key, iv, aad, &mut data, expected_tag), + 16 => run_encrypt::<$p, $key, $klen, 16>(&key, iv, aad, &mut data, expected_tag), other => panic!("unsupported ACVP tagLen {other} bytes"), } assert_eq!(data, expected_ct); }}; } match key_bytes.len() { - 16 => dispatch!(AES128Internal, 16), - 24 => dispatch!(AES192Internal, 24), - 32 => dispatch!(AES256Internal, 32), + 16 => dispatch!(AES128Internal, AES_GCM_128_Key, 16), + 24 => dispatch!(AES192Internal, AES_GCM_192_Key, 24), + 32 => dispatch!(AES256Internal, AES_GCM_256_Key, 32), other => panic!("unexpected AES key length {other}"), } } -fn run_encrypt( - key: &KeyMaterial, +fn run_encrypt( + key: &K, iv: [u8; GCM_NONCE_LEN], aad: &[u8], data: &mut [u8], expected_tag: &[u8], ) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, { let mut ct = vec![0u8; data.len()]; - let (got_iv, written, tag) = Gcm::::encrypt_detached_rng_out( - key, - &mut FixedSeedRNG::::new(iv), - aad, - data, - &mut ct, - ) - .expect("encrypt"); + let (got_iv, written, tag) = + Gcm::::encrypt_detached_rng_out( + key, + &mut FixedSeedRNG::::new(iv), + aad, + data, + &mut ct, + ) + .expect("encrypt"); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the vector's IV"); assert_eq!(written, data.len(), "GCM ciphertext is as long as the plaintext"); assert_eq!(&tag[..], expected_tag, "tag mismatch"); @@ -99,41 +102,42 @@ pub fn run_decrypt_case( expected_pt: Option<&[u8]>, ) { macro_rules! dispatch { - ($p:ty, $klen:literal) => {{ - let key = cipher_key::<$klen>(key_bytes); + ($p:ty, $key:ty, $klen:literal) => {{ + let key = cipher_key::<$key, $klen>(key_bytes); match tag.len() { - 12 => run_decrypt::<$p, $klen, 12>(&key, iv, aad, ct, tag, expected_pt), - 13 => run_decrypt::<$p, $klen, 13>(&key, iv, aad, ct, tag, expected_pt), - 14 => run_decrypt::<$p, $klen, 14>(&key, iv, aad, ct, tag, expected_pt), - 15 => run_decrypt::<$p, $klen, 15>(&key, iv, aad, ct, tag, expected_pt), - 16 => run_decrypt::<$p, $klen, 16>(&key, iv, aad, ct, tag, expected_pt), + 12 => run_decrypt::<$p, $key, $klen, 12>(&key, iv, aad, ct, tag, expected_pt), + 13 => run_decrypt::<$p, $key, $klen, 13>(&key, iv, aad, ct, tag, expected_pt), + 14 => run_decrypt::<$p, $key, $klen, 14>(&key, iv, aad, ct, tag, expected_pt), + 15 => run_decrypt::<$p, $key, $klen, 15>(&key, iv, aad, ct, tag, expected_pt), + 16 => run_decrypt::<$p, $key, $klen, 16>(&key, iv, aad, ct, tag, expected_pt), other => panic!("unsupported ACVP tagLen {other} bytes"), } }}; } match key_bytes.len() { - 16 => dispatch!(AES128Internal, 16), - 24 => dispatch!(AES192Internal, 24), - 32 => dispatch!(AES256Internal, 32), + 16 => dispatch!(AES128Internal, AES_GCM_128_Key, 16), + 24 => dispatch!(AES192Internal, AES_GCM_192_Key, 24), + 32 => dispatch!(AES256Internal, AES_GCM_256_Key, 32), other => panic!("unexpected AES key length {other}"), } } -fn run_decrypt( - key: &KeyMaterial, +fn run_decrypt( + key: &K, iv: [u8; GCM_NONCE_LEN], aad: &[u8], ct: &[u8], tag: &[u8], expected_pt: Option<&[u8]>, ) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, { let tag_arr: [u8; TAG_LEN] = tag.try_into().expect("tag length matches TAG_LEN"); // The detached one-shot: AAD-capable, and never releases plaintext before the tag checks out. let mut data = vec![0xEEu8; ct.len()]; - let one_shot_result = Gcm::::decrypt_detached_out( + let one_shot_result = Gcm::::decrypt_detached_out( key, &iv, aad, ct, &tag_arr, &mut data, ); @@ -144,7 +148,7 @@ fn run_decrypt( // layout meets AAD support, and unlike the one-shot it releases plaintext before the tag is // checked -- see `gcm_tests.rs` for that distinction pinned with empty AAD. let mut dec = - Gcm::::do_decrypt_init(key, &iv).expect("decrypt init"); + Gcm::::do_decrypt_init(key, &iv).expect("decrypt init"); dec.do_update_aad(aad).expect("aad"); let mut inline_ct = ct.to_vec(); inline_ct.extend_from_slice(tag); diff --git a/crypto/aes/tests/common/acvp_helpers.rs b/crypto/aes/tests/common/acvp_helpers.rs index 8abeb19d7..4de740bbf 100644 --- a/crypto/aes/tests/common/acvp_helpers.rs +++ b/crypto/aes/tests/common/acvp_helpers.rs @@ -7,22 +7,26 @@ use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; /// Builds a `KeyMaterial` from raw ACVP key bytes, including the all-zero keys. /// /// The ACVP sets deliberately include an all-zero key. `KeyMaterial` tags an all-zero buffer as /// `KeyType::Zeroized` and will not promote it outside a `do_hazardous_operations` closure, which /// is the right default -- so this opts in explicitly rather than the engine weakening its guard. -pub fn cipher_key(bytes: &[u8]) -> KeyMaterial { - assert_eq!(bytes.len(), N, "key length should match the parameter set"); - let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) - .expect("ACVP key bytes fit the buffer"); - if key.key_type() != KeyType::SymmetricCipherKey { - do_hazardous_operations(&mut key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(SecurityStrength::from_bytes(N)) - }) - .expect("promoting a NIST all-zero test key"); - } - key +pub fn cipher_key, const N: usize>(bytes: &[u8]) -> K { + K::from_keymaterial({ + assert_eq!(bytes.len(), N, "key length should match the parameter set"); + let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) + .expect("ACVP key bytes fit the buffer"); + if key.key_type() != KeyType::SymmetricCipherKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::SymmetricCipherKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .expect("promoting a NIST all-zero test key"); + } + key + }) + .expect("a valid key") } diff --git a/crypto/aes/tests/ctr_bc-test-data.rs b/crypto/aes/tests/ctr_bc-test-data.rs index a0cb85326..c682d724f 100644 --- a/crypto/aes/tests/ctr_bc-test-data.rs +++ b/crypto/aes/tests/ctr_bc-test-data.rs @@ -38,10 +38,12 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CTR_128_Key, AES_CTR_192_Key, AES_CTR_256_Key}; use bouncycastle_cipher::modes::Ctr; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -91,7 +93,7 @@ impl Grouping { /// Encryption is driven through `do_encrypt_init_rng` with a `FixedSeedRNG` emitting the vector's /// IV, and the returned init data is checked against that IV before any ciphertext is compared -- /// so a change that ignored the RNG could not pass silently. -fn run_case( +fn run_case( key_bytes: &[u8], nonce: [u8; NONCE_LEN], input: &[u8], @@ -99,15 +101,16 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let mut data = input.to_vec(); let chunk = grouping.chunk_len(data.len()); if encrypt { let (mut enc, got_iv) = - Ctr::::do_encrypt_init_rng( + Ctr::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(nonce), ) @@ -118,7 +121,7 @@ where } } else { let mut dec = - Ctr::::do_decrypt_init(&key, &nonce) + Ctr::::do_decrypt_init(&key, &nonce) .expect("dec init"); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -137,9 +140,15 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, nonce, input, encrypt, grouping), - 24 => run_case::(key_bytes, nonce, input, encrypt, grouping), - 32 => run_case::(key_bytes, nonce, input, encrypt, grouping), + 16 => run_case::( + key_bytes, nonce, input, encrypt, grouping, + ), + 24 => run_case::( + key_bytes, nonce, input, encrypt, grouping, + ), + 32 => run_case::( + key_bytes, nonce, input, encrypt, grouping, + ), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/aes/tests/ctr_bc_java_tests.rs b/crypto/aes/tests/ctr_bc_java_tests.rs index 2f680da89..2809c4a8e 100644 --- a/crypto/aes/tests/ctr_bc_java_tests.rs +++ b/crypto/aes/tests/ctr_bc_java_tests.rs @@ -36,11 +36,12 @@ //! three key lengths -- and it is exact. Those cases are covered there and by the ACVP suite, so //! what is pinned here is specifically the part neither of them reaches: the narrow counters. +use bouncycastle_aes::AES_CTR_128_Key; use bouncycastle_aes::hazmat::AES128Internal; use bouncycastle_cipher::Encrypting; use bouncycastle_cipher::modes::Ctr; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{StreamCipherEncryptor, SymmetricCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -48,20 +49,21 @@ use bouncycastle_hex as hex; /// The AES-128 key used for every vector in this file: SP 800-38A Appendix F's first key. const KEY: &str = "2b7e151628aed2a6abf7158809cf4f3c"; -fn key() -> KeyMaterial<16> { - let raw = hex::decode(KEY).expect("valid hex"); - KeyMaterial::<16>::from_bytes_as_type(&raw, KeyType::SymmetricCipherKey).expect("a valid key") +fn key() -> AES_CTR_128_Key { + let raw: [u8; 16] = hex::decode(KEY).expect("valid hex").try_into().expect("key length"); + AES_CTR_128_Key::from_bytes(&raw).expect("a valid key") } /// Produces `blocks` blocks of keystream by encrypting zeros under the given nonce. fn keystream(nonce_hex: &str, blocks: usize) -> Vec { let nonce: [u8; NONCE_LEN] = hex::decode(nonce_hex).expect("valid hex").try_into().expect("nonce length"); - let (mut enc, got) = Ctr::::do_encrypt_init_rng( - &key(), - &mut FixedSeedRNG::::new(nonce), - ) - .expect("encrypt init"); + let (mut enc, got) = + Ctr::::do_encrypt_init_rng( + &key(), + &mut FixedSeedRNG::::new(nonce), + ) + .expect("encrypt init"); assert_eq!(got, nonce, "the pinned RNG should reproduce the nonce"); let mut data = vec![0u8; blocks * 16]; @@ -151,11 +153,12 @@ fn three_byte_counter_matches_bc_java() { fn the_counter_limit_falls_where_bc_java_throws() { let nonce: [u8; 15] = hex::decode("5a5b5c5d5e5f606162636465666768").unwrap().try_into().unwrap(); - let (mut enc, _) = Ctr::::do_encrypt_init_rng( - &key(), - &mut FixedSeedRNG::<15>::new(nonce), - ) - .unwrap(); + let (mut enc, _) = + Ctr::::do_encrypt_init_rng( + &key(), + &mut FixedSeedRNG::<15>::new(nonce), + ) + .unwrap(); // BC Java encrypts 4096 bytes under this IV without complaint. let mut data = vec![0u8; 4096]; diff --git a/crypto/aes/tests/ctr_vector_tests.rs b/crypto/aes/tests/ctr_vector_tests.rs index d98452531..a4ecfba94 100644 --- a/crypto/aes/tests/ctr_vector_tests.rs +++ b/crypto/aes/tests/ctr_vector_tests.rs @@ -27,11 +27,12 @@ //! which is why the IV above ends in `00000000`. See the [`Ctr`] module docs. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CTR_128_Key, AES_CTR_192_Key, AES_CTR_256_Key}; use bouncycastle_cipher::modes::Ctr; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -89,22 +90,21 @@ fn unhex(s: &str) -> Vec { hex::decode(s).expect("valid hex") } -fn key_material(hex_str: &str) -> KeyMaterial { - let raw = unhex(hex_str); - assert_eq!(raw.len(), N, "key length"); - KeyMaterial::::from_bytes_as_type(&raw, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +fn key_material, const N: usize>(hex_str: &str) -> K { + let raw: [u8; N] = unhex(hex_str).try_into().expect("key length"); + K::from_bytes(&raw).expect("a valid key") } /// Chunk sizes that cut across the block and the four-block batch, so the vectors are reproduced /// through every path rather than only the batched one. const CHUNKINGS: [usize; 6] = [1, 5, 16, 17, 33, 69]; -fn check(name: &str, key_hex: &str, expected_hex: &str) +fn check(name: &str, key_hex: &str, expected_hex: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let nonce: [u8; NONCE_LEN] = unhex(NONCE).try_into().expect("a 12-byte nonce"); let plaintext = unhex(PLAINTEXT); let expected = unhex(expected_hex); @@ -114,7 +114,7 @@ where // Encryption, in one call and in every chunking. for chunk in [plaintext.len()].into_iter().chain(CHUNKINGS) { let (mut enc, got) = - Ctr::::do_encrypt_init_rng( + Ctr::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(nonce), ) @@ -131,7 +131,7 @@ where // Decryption, likewise. for chunk in [expected.len()].into_iter().chain(CHUNKINGS) { let mut dec = - Ctr::::do_decrypt_init(&key, &nonce) + Ctr::::do_decrypt_init(&key, &nonce) .expect("decrypt init"); let mut data = expected.clone(); for piece in data.chunks_mut(chunk) { @@ -142,24 +142,26 @@ where // ...and the one-shot. let mut data = expected.clone(); - Ctr::::decrypt_inplace(&key, &nonce, &mut data) - .expect("one-shot decryption"); + Ctr::::decrypt_inplace( + &key, &nonce, &mut data, + ) + .expect("one-shot decryption"); assert_eq!(data, plaintext, "{name}: one-shot"); } #[test] fn aes128_ctr_matches_openssl() { - check::("AES-128", KEY_128, CT_128); + check::("AES-128", KEY_128, CT_128); } #[test] fn aes192_ctr_matches_openssl() { - check::("AES-192", KEY_192, CT_192); + check::("AES-192", KEY_192, CT_192); } #[test] fn aes256_ctr_matches_openssl() { - check::("AES-256", KEY_256, CT_256); + check::("AES-256", KEY_256, CT_256); } /// The vectors must actually depend on the counter advancing: the second block of ciphertext must diff --git a/crypto/aes/tests/ecb_bc-test-data.rs b/crypto/aes/tests/ecb_bc-test-data.rs index e1fae0de4..8a97186e6 100644 --- a/crypto/aes/tests/ecb_bc-test-data.rs +++ b/crypto/aes/tests/ecb_bc-test-data.rs @@ -7,17 +7,15 @@ //! //! # Why ECB, and where the other ACVP AES files are used //! -//! ECB applies the raw permutation to each block independently, so an ECB test vector *is* a -//! block-permutation test vector -- which is the only reason ECB is mentioned in this crate. See -//! the crate docs on why you must never use ECB to encrypt data. +//! The vectors are driven through [`Ecb`] over the AES engines, which is the AES-ECB this crate +//! exposes under `hazmat`. See the crate docs on why you must never use ECB to encrypt data. //! //! `bc-test-data` ships sixteen ACVP AES vector sets, one per mode. This file deliberately -//! consumes only `ACVP-AES-ECB`, because that is the one that tests the permutation rather than a -//! mode. The others belong with whatever implements the mode: +//! consumes only `ACVP-AES-ECB`. The others belong with whatever implements the mode: //! //! | Vector set | Consumed by | //! |---|---| -//! | `ACVP-AES-ECB` | this file (the permutation; the `Ecb` mode's own tests are toy-driven, in `crypto/cipher/tests/modes/ecb_tests.rs`) | +//! | `ACVP-AES-ECB` | this file (the `Ecb` mode's structural tests are toy-driven, in `crypto/cipher/tests/modes/ecb_tests.rs`) | //! | `ACVP-AES-CBC` | `cbc_bc-test-data.rs` | //! | `ACVP-AES-CBC-CS1` / `-CS2` / `-CS3` | nothing yet (ciphertext stealing is unimplemented) | //! | `ACVP-AES-CCM` | `ccm_bc-test-data.rs` | @@ -47,11 +45,18 @@ //! reports how many it skipped so the gap is visible rather than silent. use bouncycastle_aes::AES_BLOCK_LEN; -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::hazmat::{ + AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key, AES128Internal, AES192Internal, + AES256Internal, +}; +use bouncycastle_cipher::modes::ModeNames; +use bouncycastle_cipher::modes::hazmat::ecb::Ecb; +use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json}; use bouncycastle_hex as hex; @@ -63,10 +68,9 @@ const RESPONSE_FILE: &str = "ACVP-AES-ECB.4014527.rsp.json"; /// The ACVP set deliberately includes an all-zero key (the GFSbox-style groups vary only the /// plaintext under a zero key). `KeyMaterial` tags an all-zero buffer as [`KeyType::Zeroized`] /// and will not promote it outside a [`do_hazardous_operations`] closure, which is the right -/// default -- an all-zero key normally means a broken RNG, and `AESInternal128::new` rejecting it is -/// tested in `fips197_tests.rs`. Here the zero key is deliberate and comes from NIST, so this -/// opts in explicitly rather than the library weakening its guard. -fn cipher_key(bytes: &[u8]) -> KeyMaterial { +/// default, since an all-zero key normally means a broken RNG. Here the zero key is deliberate and +/// comes from NIST, so this opts in explicitly rather than the library weakening its guard. +fn cipher_key, const N: usize>(bytes: &[u8]) -> K { assert_eq!(bytes.len(), N, "key length should match the parameter set"); let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) .expect("ACVP key bytes fit the buffer"); @@ -79,108 +83,82 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { .expect("promoting a NIST all-zero test key"); } - key + K::from_keymaterial(key).expect("a valid key") } -/// A single-block transformation, resolved once per test case rather than per block. -type BlockTransform = Box; +/// How the blocks are handed to the mode: one at a time, or in pairs through the two-block entry +/// point, which must agree with the single-block path on real vectors too. +#[derive(Clone, Copy)] +enum Grouping { + Single, + Pairs, +} -/// Encrypts or decrypts `data` block by block, i.e. ECB, dispatching on the key length. -fn ecb(key: &[u8], data: &[u8], encrypt: bool) -> Vec { +/// Runs `data` through [`Ecb`] over `P` in the given direction, grouped as asked. +fn run_case( + key_bytes: &[u8], + data: &[u8], + encrypt: bool, + grouping: Grouping, +) -> Vec +where + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, +{ assert_eq!(data.len() % AES_BLOCK_LEN, 0, "ACVP ECB data must be block-aligned"); - - let transform: BlockTransform = match key.len() { - 16 => { - let km = cipher_key::<16>(key); - let aes = AES128Internal::new(&km).expect("valid AES-128 key"); - if encrypt { - Box::new(move |b| aes.encrypt_block(b)) - } else { - Box::new(move |b| aes.decrypt_block(b)) + let key = cipher_key::(key_bytes); + let mut blocks: Vec<[u8; AES_BLOCK_LEN]> = data.as_chunks::().0.to_vec(); + + if encrypt { + let (mut enc, _) = Ecb::::do_encrypt_init(&key) + .expect("encrypt init"); + match grouping { + Grouping::Single => { + for block in blocks.iter_mut() { + enc.do_encrypt_blocks_inplace(core::array::from_mut(block)).unwrap(); + } } - } - 24 => { - let km = cipher_key::<24>(key); - let aes = AES192Internal::new(&km).expect("valid AES-192 key"); - if encrypt { - Box::new(move |b| aes.encrypt_block(b)) - } else { - Box::new(move |b| aes.decrypt_block(b)) + Grouping::Pairs => { + let (pairs, tail) = blocks.as_chunks_mut::<2>(); + for pair in pairs { + enc.do_encrypt_blocks_inplace(pair).unwrap(); + } + for block in tail { + enc.do_encrypt_blocks_inplace(core::array::from_mut(block)).unwrap(); + } } } - 32 => { - let km = cipher_key::<32>(key); - let aes = AES256Internal::new(&km).expect("valid AES-256 key"); - if encrypt { - Box::new(move |b| aes.encrypt_block(b)) - } else { - Box::new(move |b| aes.decrypt_block(b)) + } else { + let mut dec = Ecb::::do_decrypt_init(&key, &[]) + .expect("decrypt init"); + match grouping { + Grouping::Single => { + for block in blocks.iter_mut() { + dec.do_decrypt_blocks_inplace(core::array::from_mut(block)).unwrap(); + } + } + Grouping::Pairs => { + let (pairs, tail) = blocks.as_chunks_mut::<2>(); + for pair in pairs { + dec.do_decrypt_blocks_inplace(pair).unwrap(); + } + for block in tail { + dec.do_decrypt_blocks_inplace(core::array::from_mut(block)).unwrap(); + } } } - other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), - }; - - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(AES_BLOCK_LEN) { - // Cannot fail: the length is asserted block-aligned above. - let mut block: [u8; AES_BLOCK_LEN] = chunk.try_into().unwrap(); - transform(&mut block); - out.extend_from_slice(&block); - } - out -} - -/// The same, using the two-block entry points where a pair is available. -fn ecb_pairwise(key: &[u8], data: &[u8], encrypt: bool) -> Vec { - assert_eq!(data.len() % AES_BLOCK_LEN, 0, "ACVP ECB data must be block-aligned"); - let mut blocks: Vec<[u8; AES_BLOCK_LEN]> = - data.chunks(AES_BLOCK_LEN).map(|c| c.try_into().unwrap()).collect(); - - match key.len() { - 16 => { - let km = cipher_key::<16>(key); - let aes = AES128Internal::new(&km).unwrap(); - run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } - }); - } - 24 => { - let km = cipher_key::<24>(key); - let aes = AES192Internal::new(&km).unwrap(); - run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } - }); - } - 32 => { - let km = cipher_key::<32>(key); - let aes = AES256Internal::new(&km).unwrap(); - run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } - }); - } - other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } blocks.concat() } -/// Walks `blocks` two at a time, leaving a trailing odd block to a duplicated pair. -fn run_pairwise( - blocks: &mut [[u8; AES_BLOCK_LEN]], - encrypt: bool, - transform: impl Fn(&mut [[u8; AES_BLOCK_LEN]; 2], bool), -) { - let mut chunks = blocks.chunks_exact_mut(2); - for pair in &mut chunks { - // Cannot fail: `chunks_exact_mut(2)` yields slices of length 2. - let pair: &mut [[u8; AES_BLOCK_LEN]; 2] = pair.try_into().unwrap(); - transform(pair, encrypt); - } - // An odd trailing block still has to go through the two-block path. - if let [last] = chunks.into_remainder() { - let mut pair = [*last, *last]; - transform(&mut pair, encrypt); - *last = pair[0]; +/// Encrypts or decrypts `data` with AES-ECB, dispatching on the key length. +fn ecb(key: &[u8], data: &[u8], encrypt: bool, grouping: Grouping) -> Vec { + match key.len() { + 16 => run_case::(key, data, encrypt, grouping), + 24 => run_case::(key, data, encrypt, grouping), + 32 => run_case::(key, data, encrypt, grouping), + other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } @@ -224,20 +202,30 @@ fn acvp_aes_ecb_known_answer_tests() { assert_eq!(pt.len(), ct.len(), "tcId {tc_id}: pt and ct differ in length"); - assert_eq!(ecb(&key, &pt, true), ct, "tcId {tc_id}: AES-{} encrypt", key.len() * 8); - assert_eq!(ecb(&key, &ct, false), pt, "tcId {tc_id}: AES-{} decrypt", key.len() * 8); + assert_eq!( + ecb(&key, &pt, true, Grouping::Single), + ct, + "tcId {tc_id}: AES-{} encrypt", + key.len() * 8 + ); + assert_eq!( + ecb(&key, &ct, false, Grouping::Single), + pt, + "tcId {tc_id}: AES-{} decrypt", + key.len() * 8 + ); // The two-block path must agree with the single-block path on real vectors too. assert_eq!( - ecb_pairwise(&key, &pt, true), + ecb(&key, &pt, true, Grouping::Pairs), ct, - "tcId {tc_id}: AES-{} encrypt via encrypt_2blocks", + "tcId {tc_id}: AES-{} encrypt in pairs", key.len() * 8 ); assert_eq!( - ecb_pairwise(&key, &ct, false), + ecb(&key, &ct, false, Grouping::Pairs), pt, - "tcId {tc_id}: AES-{} decrypt via decrypt_2blocks", + "tcId {tc_id}: AES-{} decrypt in pairs", key.len() * 8 ); diff --git a/crypto/aes/tests/electronic_code_book_tests.rs b/crypto/aes/tests/electronic_code_book_tests.rs index b4f03bf42..3588bdbcb 100644 --- a/crypto/aes/tests/electronic_code_book_tests.rs +++ b/crypto/aes/tests/electronic_code_book_tests.rs @@ -8,20 +8,24 @@ //! paths, so the default implementations are not what runs. use bouncycastle_aes::AES_BLOCK_LEN; +use bouncycastle_aes::hazmat::{AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key}; use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; #[test] fn aes128_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<16, AES_BLOCK_LEN, AES128Internal>(); + TestFrameworkElectronicCodeBook::new() + .test::<16, AES_BLOCK_LEN, AES_ECB_128_Key, AES128Internal>(); } #[test] fn aes192_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<24, AES_BLOCK_LEN, AES192Internal>(); + TestFrameworkElectronicCodeBook::new() + .test::<24, AES_BLOCK_LEN, AES_ECB_192_Key, AES192Internal>(); } #[test] fn aes256_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<32, AES_BLOCK_LEN, AES256Internal>(); + TestFrameworkElectronicCodeBook::new() + .test::<32, AES_BLOCK_LEN, AES_ECB_256_Key, AES256Internal>(); } diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index df62bc68d..541933517 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -1,7 +1,9 @@ //! Known-answer tests from NIST FIPS 197 itself. //! //! Appendix B -- the worked single-block AES-128 encryption -- plus its inverse, the two- and -//! four-block paths, and key-handling behaviour. +//! four-block paths, and the key policy. Everything runs through [`Ecb`] over the AES engines, +//! which is the AES-ECB this crate exposes under `hazmat`; a single ECB block *is* the raw +//! permutation, so Appendix B's block is checked exactly as the document prints it. //! //! The Appendix A key expansions are **not** tested here. The key schedule is deliberately not //! public API (it is a `Secret` field), and a round-trip through the cipher cannot check it: a @@ -14,10 +16,19 @@ //! //! All values here are transcribed from the published FIPS 197 (Update 1) PDF. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_aes::hazmat::{ + AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key, AES128Internal, AES192Internal, + AES256Internal, +}; +use bouncycastle_cipher::modes::hazmat::ecb::Ecb; +use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; + +type Ecb128 = Ecb; +type Ecb192 = Ecb; +type Ecb256 = Ecb; /// Appendix A.1 / Appendix B key: `2b7e151628aed2a6abf7158809cf4f3c`. const KEY_128: [u8; 16] = [ @@ -37,86 +48,82 @@ const KEY_256: [u8; 32] = [ 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4, ]; -fn key_material(bytes: &[u8; N]) -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +/// Appendix B: Input = 32 43 f6 a8 88 5a 30 8d 31 31 98 a2 e0 37 07 34. +const APPENDIX_B_INPUT: [u8; 16] = [ + 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34, +]; + +/// Appendix B: the final state printed as "output" reads, column by column (Eq 3.7), +/// 39 25 84 1d 02 dc 09 fb dc 11 85 97 19 6a 0b 32. +const APPENDIX_B_OUTPUT: [u8; 16] = [ + 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32, +]; + +fn key, const N: usize>(bytes: &[u8; N]) -> K { + K::from_bytes(bytes).expect("a valid symmetric cipher key") +} + +fn enc128() -> Ecb128 { + Ecb128::::do_encrypt_init(&key::(&KEY_128)).unwrap().0 +} +fn dec128() -> Ecb128 { + Ecb128::::do_decrypt_init(&key::(&KEY_128), &[]).unwrap() +} +fn enc192() -> Ecb192 { + Ecb192::::do_encrypt_init(&key::(&KEY_192)).unwrap().0 +} +fn dec192() -> Ecb192 { + Ecb192::::do_decrypt_init(&key::(&KEY_192), &[]).unwrap() +} +fn enc256() -> Ecb256 { + Ecb256::::do_encrypt_init(&key::(&KEY_256)).unwrap().0 +} +fn dec256() -> Ecb256 { + Ecb256::::do_decrypt_init(&key::(&KEY_256), &[]).unwrap() } #[test] fn appendix_b_encrypts_the_documented_block() { - // Appendix B: Input = 32 43 f6 a8 88 5a 30 8d 31 31 98 a2 e0 37 07 34 - // Key = 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c - // The final state printed as "output" reads, column by column (Eq 3.7): - // 39 25 84 1d 02 dc 09 fb dc 11 85 97 19 6a 0b 32 - let aes = AES128Internal::new(&key_material(&KEY_128)).unwrap(); - - let mut block = [ - 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, - 0x34, - ]; - aes.encrypt_block(&mut block); - assert_eq!( - block, - [ - 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, - 0x0b, 0x32 - ] - ); + let mut block = APPENDIX_B_INPUT; + enc128().do_encrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); + assert_eq!(block, APPENDIX_B_OUTPUT); } #[test] fn appendix_b_decrypts_back_to_the_documented_input() { - let aes = AES128Internal::new(&key_material(&KEY_128)).unwrap(); - - let mut block = [ - 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, - 0x32, - ]; - aes.decrypt_block(&mut block); - assert_eq!( - block, - [ - 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, - 0x07, 0x34 - ] - ); + let mut block = APPENDIX_B_OUTPUT; + dec128().do_decrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); + assert_eq!(block, APPENDIX_B_INPUT); } +/// Batching the Appendix B block with unrelated ones must not disturb any of them. A pair goes +/// through the engine's two-block path and a quad through its four-block path, so this is what +/// pins each lane of the bit-planes to its slot. #[test] -fn appendix_b_two_block_path_agrees_with_the_single_block_path() { - let aes = AES128Internal::new(&key_material(&KEY_128)).unwrap(); - let input = [ - 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, - 0x34, - ]; - let expected = [ - 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, - 0x32, - ]; - - // Batching the Appendix B block with unrelated ones must not disturb any of them. +fn appendix_b_two_and_four_block_paths_agree_with_the_single_block_path() { + let mut enc = enc128(); let other = [0xAAu8; 16]; let mut other_alone = other; - aes.encrypt_block(&mut other_alone); + enc.do_encrypt_blocks_inplace(core::array::from_mut(&mut other_alone)).unwrap(); - let mut pair = [input, other]; - aes.encrypt_2blocks(&mut pair); - assert_eq!(pair[0], expected); + let mut pair = [APPENDIX_B_INPUT, other]; + enc.do_encrypt_blocks_inplace(&mut pair).unwrap(); + assert_eq!(pair[0], APPENDIX_B_OUTPUT); assert_eq!(pair[1], other_alone); // ...and in the other slot, which is a different lane of the bit-planes. - let mut pair = [other, input]; - aes.encrypt_2blocks(&mut pair); + let mut pair = [other, APPENDIX_B_INPUT]; + enc.do_encrypt_blocks_inplace(&mut pair).unwrap(); assert_eq!(pair[0], other_alone); - assert_eq!(pair[1], expected); + assert_eq!(pair[1], APPENDIX_B_OUTPUT); // ...and in each of the four lanes of the four-block path. for slot in 0..4 { let mut four = [other; 4]; - four[slot] = input; - aes.encrypt_4blocks(&mut four); + four[slot] = APPENDIX_B_INPUT; + enc.do_encrypt_blocks_inplace(&mut four).unwrap(); for (i, block) in four.iter().enumerate() { - let want = if i == slot { expected } else { other_alone }; + let want = if i == slot { APPENDIX_B_OUTPUT } else { other_alone }; assert_eq!(*block, want, "slot {slot}, block {i}"); } } @@ -124,32 +131,28 @@ fn appendix_b_two_block_path_agrees_with_the_single_block_path() { /// Encryption and decryption are inverses, under each Appendix A key. /// -/// This checks `decrypt_block` really inverts `encrypt_block` from the same stored schedule, -/// which is the load-bearing claim of following FIPS 197 Algorithm 3 rather than Sec 5.3.5. It +/// This checks that decryption really inverts encryption from the same stored schedule, which +/// is the load-bearing claim of following FIPS 197 Algorithm 3 rather than Sec 5.3.5. It /// deliberately makes no claim about the schedule being *correct* -- see the module docs. #[test] fn encryption_and_decryption_are_inverses_for_all_three_key_lengths() { - let aes128 = AES128Internal::new(&key_material(&KEY_128)).unwrap(); - let aes192 = AES192Internal::new(&key_material(&KEY_192)).unwrap(); - let aes256 = AES256Internal::new(&key_material(&KEY_256)).unwrap(); - for block in [[0u8; 16], [0xFFu8; 16], core::array::from_fn(|i| i as u8)] { let mut b = block; - aes128.encrypt_block(&mut b); + enc128().do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_ne!(b, block, "AES-128 must actually transform the block"); - aes128.decrypt_block(&mut b); + dec128().do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block, "AES-128 round trip with the Appendix A.1 key"); let mut b = block; - aes192.encrypt_block(&mut b); + enc192().do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_ne!(b, block, "AES-192 must actually transform the block"); - aes192.decrypt_block(&mut b); + dec192().do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block, "AES-192 round trip with the Appendix A.2 key"); let mut b = block; - aes256.encrypt_block(&mut b); + enc256().do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_ne!(b, block, "AES-256 must actually transform the block"); - aes256.decrypt_block(&mut b); + dec256().do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block, "AES-256 round trip with the Appendix A.3 key"); } } @@ -161,19 +164,24 @@ fn encryption_and_decryption_are_inverses_for_all_three_key_lengths() { fn the_three_key_lengths_are_distinct_permutations() { // A key whose first 16 bytes are shared, so only Nk/Nr and the extra key bytes differ. let shared = [0x11u8; 32]; - let aes128 = - AES128Internal::new(&key_material::<16>(&shared[..16].try_into().unwrap())).unwrap(); - let aes192 = - AES192Internal::new(&key_material::<24>(&shared[..24].try_into().unwrap())).unwrap(); - let aes256 = AES256Internal::new(&key_material(&shared)).unwrap(); + let (mut e128, _) = Ecb128::::do_encrypt_init(&key::( + shared[..16].try_into().unwrap(), + )) + .unwrap(); + let (mut e192, _) = Ecb192::::do_encrypt_init(&key::( + shared[..24].try_into().unwrap(), + )) + .unwrap(); + let (mut e256, _) = + Ecb256::::do_encrypt_init(&key::(&shared)).unwrap(); let block = [0x42u8; 16]; let mut b128 = block; let mut b192 = block; let mut b256 = block; - aes128.encrypt_block(&mut b128); - aes192.encrypt_block(&mut b192); - aes256.encrypt_block(&mut b256); + e128.do_encrypt_blocks_inplace(core::array::from_mut(&mut b128)).unwrap(); + e192.do_encrypt_blocks_inplace(core::array::from_mut(&mut b192)).unwrap(); + e256.do_encrypt_blocks_inplace(core::array::from_mut(&mut b256)).unwrap(); assert_ne!(b128, b192); assert_ne!(b192, b256); @@ -181,16 +189,20 @@ fn the_three_key_lengths_are_distinct_permutations() { } // ---- key handling ----------------------------------------------------------------------- +// +// The key policy lives in the key types: material that is not a cipher key, or not strong +// enough, never becomes an `AES_ECB_*_Key` at all. The one check the key type leaves to the +// engine is the populated length, which is tested through the mode. #[test] fn a_key_of_the_wrong_type_is_rejected() { // KeyType::Seed is not a cipher key: a seed reused directly as an AES key is a real mistake // and the type system tracks enough to catch it. let key = KeyMaterial::<16>::from_bytes_as_type(&[0x01; 16], KeyType::Seed).unwrap(); - assert!(AES128Internal::new(&key).is_err()); + assert!(AES_ECB_128_Key::from_keymaterial(key).is_err()); let key = KeyMaterial::<16>::from_bytes_as_type(&[0x01; 16], KeyType::MACKey).unwrap(); - assert!(AES128Internal::new(&key).is_err()); + assert!(AES_ECB_128_Key::from_keymaterial(key).is_err()); } #[test] @@ -199,7 +211,11 @@ fn a_key_of_the_wrong_length_is_rejected() { // parameter set. This is the one length error the const generic cannot catch by itself. let key = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 16], KeyType::SymmetricCipherKey).unwrap(); - assert!(AES256Internal::new(&key).is_err()); + let rejected = match AES_ECB_256_Key::from_keymaterial(key) { + Err(_) => true, + Ok(key) => Ecb256::::do_encrypt_init(&key).is_err(), + }; + assert!(rejected, "a 16-byte key must not be accepted as AES-256"); } #[test] @@ -214,7 +230,7 @@ fn a_key_carrying_too_low_a_security_strength_is_rejected() { key.set_security_strength(SecurityStrength::_128bit).unwrap(); assert!( - AES256Internal::new(&key).is_err(), + AES_ECB_256_Key::from_keymaterial(key).is_err(), "AES-256 must reject a 32-byte key only derived at the 128-bit strength" ); @@ -222,23 +238,12 @@ fn a_key_carrying_too_low_a_security_strength_is_rejected() { // not about anything else having gone wrong with the key. let good = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 32], KeyType::SymmetricCipherKey).unwrap(); - assert!(AES256Internal::new(&good).is_ok()); + assert!(AES_ECB_256_Key::from_keymaterial(good).is_ok()); } #[test] fn a_correctly_typed_key_of_each_length_is_accepted() { - assert!(AES128Internal::new(&key_material(&KEY_128)).is_ok()); - assert!(AES192Internal::new(&key_material(&KEY_192)).is_ok()); - assert!(AES256Internal::new(&key_material(&KEY_256)).is_ok()); -} - -#[test] -fn debug_does_not_print_the_key_schedule() { - // The schedule is secret; `Debug` must not be a way to leak it. - let aes = AES128Internal::new(&key_material(&KEY_128)).unwrap(); - let rendered = format!("{aes:?}"); - assert_eq!(rendered, "AES-128"); - // No byte of the key should appear as hex in the output. - assert!(!rendered.contains("2b")); - assert!(!rendered.contains("7e")); + assert!(Ecb128::::do_encrypt_init(&key::(&KEY_128)).is_ok()); + assert!(Ecb192::::do_encrypt_init(&key::(&KEY_192)).is_ok()); + assert!(Ecb256::::do_encrypt_init(&key::(&KEY_256)).is_ok()); } diff --git a/crypto/aes/tests/gcm_bc_java_tests.rs b/crypto/aes/tests/gcm_bc_java_tests.rs index 7c6703485..31747b6c0 100644 --- a/crypto/aes/tests/gcm_bc_java_tests.rs +++ b/crypto/aes/tests/gcm_bc_java_tests.rs @@ -10,10 +10,12 @@ //! built programmatically rather than typed out (a zero key or plaintext cannot be mistyped). use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_GCM_128_Key, AES_GCM_192_Key, AES_GCM_256_Key}; use bouncycastle_cipher::modes::Gcm; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -180,9 +182,10 @@ fn cases() -> Vec { ] } -fn run(case: &Case) +fn run(case: &Case) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, { let key_bytes = hex::decode(&case.key).expect("valid hex key"); // `KeyMaterial` tags an all-zero buffer as `KeyType::Zeroized` regardless of the type @@ -200,6 +203,7 @@ where }) .expect("promoting a known-zero test key"); } + let key = K::from_keymaterial(key).expect("a valid key"); let aad = hex::decode(case.aad).expect("valid hex aad"); let pt = hex::decode(&case.pt).expect("valid hex pt"); @@ -209,7 +213,7 @@ where let expected_tag = hex::decode(case.tag).expect("valid hex tag"); let mut data = vec![0u8; pt.len()]; - let (got_iv, _, tag) = Gcm::::encrypt_detached_rng_out( + let (got_iv, _, tag) = Gcm::::encrypt_detached_rng_out( &key, &mut FixedSeedRNG::<12>::new(iv), &aad, @@ -224,7 +228,7 @@ where let tag_arr: [u8; 16] = expected_tag.try_into().expect("16-byte tag"); let mut recovered = vec![0u8; data.len()]; - Gcm::::decrypt_detached_out( + Gcm::::decrypt_detached_out( &key, &iv, &aad, &data, &tag_arr, &mut recovered, ) .unwrap_or_else(|e| panic!("{}: decrypt should have verified, got {e:?}", case.name)); @@ -237,9 +241,9 @@ fn bc_java_test_vectors_with_a_96_bit_iv() { for case in cases() { let key_len_bytes = case.key.len() / 2; match key_len_bytes { - 16 => run::(&case), - 24 => run::(&case), - 32 => run::(&case), + 16 => run::(&case), + 24 => run::(&case), + 32 => run::(&case), other => panic!("{}: unexpected key length {other} bytes", case.name), } checked += 1; diff --git a/crypto/aes/tests/gcm_tests.rs b/crypto/aes/tests/gcm_tests.rs index b2a445611..94f511c1c 100644 --- a/crypto/aes/tests/gcm_tests.rs +++ b/crypto/aes/tests/gcm_tests.rs @@ -8,15 +8,16 @@ //! the ACVP and bc-java known-answer suites beside this file. use bouncycastle_aes::{AES_GCM_128, AES_GCM_192, AES_GCM_256}; +use bouncycastle_aes::{AES_GCM_128_Key, AES_GCM_192_Key, AES_GCM_256_Key}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; use bouncycastle_core_test_framework::aead::TestFrameworkAEADTaggedLayout; -fn key() -> KeyMaterial { +fn key, const N: usize>() -> K { let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).expect("a valid key") + K::from_bytes(&bytes).expect("a valid key") } /// All three key lengths satisfy the shared AEAD conformance suite, which includes the @@ -29,7 +30,7 @@ fn all_three_key_lengths_conform_to_the_aead_suite() { 12, 16, 16, - AES_GCM_128, + AES_GCM_128_Key, AES_GCM_128, AES_GCM_128, >(); framework.test_encryptor_decryptor::< @@ -37,7 +38,7 @@ fn all_three_key_lengths_conform_to_the_aead_suite() { 12, 16, 16, - AES_GCM_192, + AES_GCM_192_Key, AES_GCM_192, AES_GCM_192, >(); framework.test_encryptor_decryptor::< @@ -45,7 +46,7 @@ fn all_three_key_lengths_conform_to_the_aead_suite() { 12, 16, 16, - AES_GCM_256, + AES_GCM_256_Key, AES_GCM_256, AES_GCM_256, >(); } @@ -57,8 +58,12 @@ fn all_three_key_lengths_conform_to_the_aead_suite() { #[test] fn the_inline_tag_layout_conforms_at_every_edge() { let framework = TestFrameworkAEADTaggedLayout::new(); - framework.test::<16, 12, 16, 16, AES_GCM_128, AES_GCM_128>(); - framework.test::<32, 12, 16, 16, AES_GCM_256, AES_GCM_256>(); + framework + .test::<16, 12, 16, 16, AES_GCM_128_Key, AES_GCM_128, AES_GCM_128>( + ); + framework + .test::<32, 12, 16, 16, AES_GCM_256_Key, AES_GCM_256, AES_GCM_256>( + ); } /// The nonce is generated per encryption, so the same plaintext gives different ciphertext, and @@ -69,13 +74,17 @@ fn each_encryption_gets_a_fresh_nonce() { let mut seen = std::collections::BTreeSet::new(); for _ in 0..16 { let mut ct = [0u8; 46]; - let (nonce, _, tag) = - AES_GCM_128::::encrypt_detached_out(&key::<16>(), b"aad", &data, &mut ct) - .unwrap(); + let (nonce, _, tag) = AES_GCM_128::::encrypt_detached_out( + &key::(), + b"aad", + &data, + &mut ct, + ) + .unwrap(); assert!(seen.insert(nonce), "nonce repeated across encryptions"); let mut pt = [0u8; 46]; AES_GCM_128::::decrypt_detached_out( - &key::<16>(), + &key::(), &nonce, b"aad", &ct, diff --git a/crypto/aes/tests/gcm_wycheproof.rs b/crypto/aes/tests/gcm_wycheproof.rs index 2a02a1523..1f33da97c 100644 --- a/crypto/aes/tests/gcm_wycheproof.rs +++ b/crypto/aes/tests/gcm_wycheproof.rs @@ -37,6 +37,7 @@ //! case out. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_GCM_128_Key, AES_GCM_192_Key, AES_GCM_256_Key}; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::modes::{GCM_NONCE_LEN, Gcm}; use bouncycastle_cipher::{Decrypting, Encrypting}; @@ -45,25 +46,29 @@ use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; /// Wraps the vector's raw key bytes, promoting them if `KeyMaterial`'s entropy heuristic declined /// to call them a cipher key. Same helper as the ACVP and CCM suites in this crate. -fn cipher_key(bytes: &[u8]) -> KeyMaterial { - assert_eq!(bytes.len(), N, "key length should match the parameter set"); - let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) - .expect("wycheproof key bytes fit the buffer"); - - if key.key_type() != KeyType::SymmetricCipherKey { - do_hazardous_operations(&mut key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(SecurityStrength::from_bytes(N)) - }) - .expect("promoting a wycheproof test key"); - } - key +fn cipher_key, const N: usize>(bytes: &[u8]) -> K { + K::from_keymaterial({ + assert_eq!(bytes.len(), N, "key length should match the parameter set"); + let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) + .expect("wycheproof key bytes fit the buffer"); + + if key.key_type() != KeyType::SymmetricCipherKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::SymmetricCipherKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .expect("promoting a wycheproof test key"); + } + key + }) + .expect("a valid key") } /// Runs one case at a fully-instantiated `(KEY_LEN, TAG_LEN, P)`. @@ -74,7 +79,7 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { /// reason to reproduce a deliberately corrupted tag -- and it must fail the tag check rather than /// return a payload, leaving the caller's buffer zeroized as the trait contract requires. #[allow(clippy::too_many_arguments)] -fn run_case( +fn run_case( tc_id: u64, key_bytes: &[u8], iv: [u8; GCM_NONCE_LEN], @@ -84,16 +89,17 @@ fn run_case( expected_tag: &[u8], valid: bool, ) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = cipher_key::(key_bytes); + let key = cipher_key::(key_bytes); let tag: [u8; TAG_LEN] = expected_tag.try_into().unwrap_or_else(|_| panic!("tcId {tc_id}: bad tag length")); if valid { let mut ct = vec![0u8; msg.len()]; let (got_iv, written, got_tag) = - Gcm::::encrypt_detached_rng_out( + Gcm::::encrypt_detached_rng_out( &key, &mut FixedSeedRNG::::new(iv), aad, @@ -110,7 +116,7 @@ fn run_case( // Pre-filled with a non-zero byte, so a buffer the decryptor never wrote is not mistaken for // one it zeroized. let mut plaintext = vec![0xEEu8; expected_ct.len()]; - match Gcm::::decrypt_detached_out( + match Gcm::::decrypt_detached_out( &key, &iv, aad, expected_ct, &tag, &mut plaintext, ) { Ok(n) => { @@ -152,13 +158,13 @@ fn dispatch( return false; } match key_bytes.len() { - 16 => run_case::<16, 16, AES128Internal>( + 16 => run_case::<16, 16, AES128Internal, AES_GCM_128_Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), - 24 => run_case::<24, 16, AES192Internal>( + 24 => run_case::<24, 16, AES192Internal, AES_GCM_192_Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), - 32 => run_case::<32, 16, AES256Internal>( + 32 => run_case::<32, 16, AES256Internal, AES_GCM_256_Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), _ => return false, diff --git a/crypto/aes/tests/padded_alias_tests.rs b/crypto/aes/tests/padded_alias_tests.rs index 4dc7c5ffc..9a74ed1c0 100644 --- a/crypto/aes/tests/padded_alias_tests.rs +++ b/crypto/aes/tests/padded_alias_tests.rs @@ -6,30 +6,36 @@ //! crates; this checks the wiring between them. Which type each alias resolves to is pinned in //! `alias_tests.rs`. -use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; -use bouncycastle_aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle_aes::hazmat::{ + AES_ECB_128, AES_ECB_128_Key, AES_ECB_192, AES_ECB_192_Key, AES_ECB_256, AES_ECB_256_Key, +}; +use bouncycastle_aes::{ + AES_CBC_128, AES_CBC_128_Key, AES_CBC_192, AES_CBC_192_Key, AES_CBC_256, AES_CBC_256_Key, +}; use bouncycastle_cipher::padding::{NoPadding, PKCS7}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::traits::{ + SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, +}; -fn key() -> KeyMaterial { +fn key, const N: usize>() -> K { let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).expect("a valid key") + K::from_bytes(&bytes).expect("a valid key") } /// Every key length round-trips through its alias, at lengths that need padding and lengths that /// do not. #[test] fn every_key_length_round_trips() { - fn check(name: &str) + fn check(name: &str) where - Enc: SymmetricCipherEncryptor, - Dec: SymmetricCipherDecryptor, + K: SymmetricCipherKey, + Enc: SymmetricCipherEncryptor, + Dec: SymmetricCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 63, 64] { let plaintext: Vec = (0..len).map(|i| (i * 11 + 3) as u8).collect(); - let (iv, ciphertext) = Enc::encrypt(&key::(), &plaintext).expect("encryption"); + let (iv, ciphertext) = Enc::encrypt(&key::(), &plaintext).expect("encryption"); // PKCS#7 always adds at least one byte, and rounds up to a whole block. assert_eq!( @@ -38,17 +44,29 @@ fn every_key_length_round_trips() { "{name}, len {len}: PKCS7 pads up to the next whole block" ); - let recovered = Dec::decrypt(&key::(), &iv, &ciphertext).expect("decryption"); + let recovered = Dec::decrypt(&key::(), &iv, &ciphertext).expect("decryption"); assert_eq!(recovered, plaintext, "{name}, len {len}: round trip"); } } - check::<16, 16, AES_CBC_128, AES_CBC_128>("CBC-128"); - check::<24, 16, AES_CBC_192, AES_CBC_192>("CBC-192"); - check::<32, 16, AES_CBC_256, AES_CBC_256>("CBC-256"); - check::<16, 0, AES_ECB_128, AES_ECB_128>("ECB-128"); - check::<24, 0, AES_ECB_192, AES_ECB_192>("ECB-192"); - check::<32, 0, AES_ECB_256, AES_ECB_256>("ECB-256"); + check::<16, 16, AES_CBC_128_Key, AES_CBC_128, AES_CBC_128>( + "CBC-128", + ); + check::<24, 16, AES_CBC_192_Key, AES_CBC_192, AES_CBC_192>( + "CBC-192", + ); + check::<32, 16, AES_CBC_256_Key, AES_CBC_256, AES_CBC_256>( + "CBC-256", + ); + check::<16, 0, AES_ECB_128_Key, AES_ECB_128, AES_ECB_128>( + "ECB-128", + ); + check::<24, 0, AES_ECB_192_Key, AES_ECB_192, AES_ECB_192>( + "ECB-192", + ); + check::<32, 0, AES_ECB_256_Key, AES_ECB_256, AES_ECB_256>( + "ECB-256", + ); } /// The padding parameter must actually select the scheme, not merely be carried around. @@ -58,29 +76,35 @@ fn every_key_length_round_trips() { /// parameter reaches the behaviour. #[test] fn the_padding_parameter_selects_the_scheme() { - fn check(name: &str) + fn check(name: &str) where - Pkcs7Enc: SymmetricCipherEncryptor<16, IV_LEN, 16>, - NoPadEnc: SymmetricCipherEncryptor<16, IV_LEN, 16>, + K: SymmetricCipherKey<16>, + Pkcs7Enc: SymmetricCipherEncryptor, + NoPadEnc: SymmetricCipherEncryptor, { // A whole block: both schemes accept it, and they disagree about the length. let aligned = [0x5Au8; 16]; - let (_, pkcs7) = Pkcs7Enc::encrypt(&key::<16>(), &aligned).expect("PKCS7 accepts it"); - let (_, nopad) = NoPadEnc::encrypt(&key::<16>(), &aligned).expect("NoPadding accepts it"); + let (_, pkcs7) = Pkcs7Enc::encrypt(&key::(), &aligned).expect("PKCS7 accepts it"); + let (_, nopad) = + NoPadEnc::encrypt(&key::(), &aligned).expect("NoPadding accepts it"); assert_eq!(pkcs7.len(), 32, "{name}: PKCS7 adds a whole block of padding to aligned data"); assert_eq!(nopad.len(), 16, "{name}: NoPadding adds nothing"); // Five bytes: PKCS7 pads it, NoPadding refuses rather than silently padding. let unaligned = b"hello"; - assert!(Pkcs7Enc::encrypt(&key::<16>(), unaligned).is_ok(), "{name}: PKCS7 pads it"); + assert!(Pkcs7Enc::encrypt(&key::(), unaligned).is_ok(), "{name}: PKCS7 pads it"); assert!( - NoPadEnc::encrypt(&key::<16>(), unaligned).is_err(), + NoPadEnc::encrypt(&key::(), unaligned).is_err(), "{name}: NoPadding must refuse a message that is not a whole number of blocks" ); } - check::<16, AES_CBC_128, AES_CBC_128>("CBC"); - check::<0, AES_ECB_128, AES_ECB_128>("ECB"); + check::<16, AES_CBC_128_Key, AES_CBC_128, AES_CBC_128>( + "CBC", + ); + check::<0, AES_ECB_128_Key, AES_ECB_128, AES_ECB_128>( + "ECB", + ); } /// A ciphertext made under one scheme must not decrypt cleanly under the other. @@ -91,18 +115,21 @@ fn the_padding_parameter_selects_the_scheme() { fn the_two_schemes_are_not_interchangeable() { let aligned = [0x5Au8; 16]; let (iv, pkcs7) = - AES_CBC_128::::encrypt(&key::<16>(), &aligned).expect("encryption"); + AES_CBC_128::::encrypt(&key::(), &aligned) + .expect("encryption"); // NoPadding will hand back the padded block as if it were data, so it "succeeds" with the // wrong answer -- exactly the silent mismatch the type parameter is there to prevent. - let as_nopad = AES_CBC_128::::decrypt(&key::<16>(), &iv, &pkcs7) - .expect("NoPadding cannot tell that the trailing block is padding"); + let as_nopad = + AES_CBC_128::::decrypt(&key::(), &iv, &pkcs7) + .expect("NoPadding cannot tell that the trailing block is padding"); assert_ne!(as_nopad, aligned, "the recovered data must not match the original"); assert_eq!(as_nopad.len(), 32, "it keeps the padding block as data"); // ...and the matching scheme gets it right. let correct = - AES_CBC_128::::decrypt(&key::<16>(), &iv, &pkcs7).expect("decryption"); + AES_CBC_128::::decrypt(&key::(), &iv, &pkcs7) + .expect("decryption"); assert_eq!(correct, aligned); } @@ -112,9 +139,13 @@ fn each_cbc_encryption_gets_a_fresh_iv() { let plaintext = [0x77u8; 32]; let mut seen = std::collections::BTreeSet::new(); for _ in 0..16 { - let (iv, ct) = AES_CBC_128::::encrypt(&key::<16>(), &plaintext).unwrap(); + let (iv, ct) = + AES_CBC_128::::encrypt(&key::(), &plaintext) + .unwrap(); assert!(seen.insert(iv), "IV repeated across encryptions"); - let back = AES_CBC_128::::decrypt(&key::<16>(), &iv, &ct).unwrap(); + let back = + AES_CBC_128::::decrypt(&key::(), &iv, &ct) + .unwrap(); assert_eq!(back, plaintext); } } @@ -125,12 +156,17 @@ fn each_cbc_encryption_gets_a_fresh_iv() { #[test] fn ecb_has_no_iv() { let (no_iv, ciphertext) = - AES_ECB_128::::encrypt(&key::<16>(), b"hello").expect("encryption"); + AES_ECB_128::::encrypt(&key::(), b"hello") + .expect("encryption"); assert_eq!(no_iv, [0u8; 0], "ECB has no IV, so the init data is empty"); assert_eq!(ciphertext.len(), 16, "five bytes padded to one block, nothing prepended"); - let recovered = - AES_ECB_128::::decrypt(&key::<16>(), &no_iv, &ciphertext).unwrap(); + let recovered = AES_ECB_128::::decrypt( + &key::(), + &no_iv, + &ciphertext, + ) + .unwrap(); assert_eq!(recovered, b"hello"); } @@ -141,12 +177,15 @@ fn ecb_has_no_iv() { fn padding_does_not_hide_the_ecb_codebook_property() { // Two identical blocks give two identical ciphertext blocks. let (_, ciphertext) = - AES_ECB_128::::encrypt(&key::<16>(), &[0x5Au8; 32]).unwrap(); + AES_ECB_128::::encrypt(&key::(), &[0x5Au8; 32]) + .unwrap(); assert_eq!(ciphertext[..16], ciphertext[16..], "ECB is a codebook, padded or not"); // ...and encryption is deterministic, there being no IV to vary. - let (_, a) = AES_ECB_128::::encrypt(&key::<16>(), b"hello").unwrap(); - let (_, b) = AES_ECB_128::::encrypt(&key::<16>(), b"hello").unwrap(); + let (_, a) = + AES_ECB_128::::encrypt(&key::(), b"hello").unwrap(); + let (_, b) = + AES_ECB_128::::encrypt(&key::(), b"hello").unwrap(); assert_eq!(a, b, "the same message encrypts the same way every time"); } @@ -164,7 +203,7 @@ fn the_allocating_streaming_wrappers_match_the_out_versions() { let pieces = [0..5, 5..16, 16..17, 17..37, 37..37, 37..40]; let expected_released = [0, 16, 0, 16, 0, 0]; - let (mut enc, iv) = Enc::do_encrypt_init(&key::<16>()).unwrap(); + let (mut enc, iv) = Enc::do_encrypt_init(&key::()).unwrap(); let mut ciphertext = Vec::new(); for (range, expected) in pieces.into_iter().zip(expected_released) { let piece = &plaintext[range]; @@ -180,11 +219,11 @@ fn the_allocating_streaming_wrappers_match_the_out_versions() { let (last, last_len) = enc.do_encrypt_final().unwrap(); ciphertext.extend_from_slice(&last[..last_len]); assert_eq!(ciphertext.len(), 48); - assert_eq!(Dec::decrypt(&key::<16>(), &iv, &ciphertext).unwrap(), plaintext); + assert_eq!(Dec::decrypt(&key::(), &iv, &ciphertext).unwrap(), plaintext); // Decrypt the same ciphertext through `do_decrypt` in uneven pieces: the decryptor holds back // the block that might carry the padding, so some pieces release nothing. - let mut dec = Dec::do_decrypt_init(&key::<16>(), &iv).unwrap(); + let mut dec = Dec::do_decrypt_init(&key::(), &iv).unwrap(); let mut recovered = Vec::new(); let mut saw_empty = false; for range in [0..16, 16..17, 17..48] { diff --git a/crypto/aes/tests/sp800_38a_cbc_tests.rs b/crypto/aes/tests/sp800_38a_cbc_tests.rs index f3a566e88..9c0e9f0dc 100644 --- a/crypto/aes/tests/sp800_38a_cbc_tests.rs +++ b/crypto/aes/tests/sp800_38a_cbc_tests.rs @@ -16,11 +16,12 @@ //! any ciphertext. Decryption takes the IV directly, as init data. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CBC_128_Key, AES_CBC_192_Key, AES_CBC_256_Key}; use bouncycastle_cipher::modes::Cbc; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -81,28 +82,27 @@ fn flat(hex_strs: &[&str; 4]) -> [u8; 4 * BLOCK_LEN] { blocks(hex_strs).as_flattened().try_into().expect("4 blocks = 64 bytes") } -fn key_material(hex_str: &str) -> KeyMaterial { - let bytes = hex::decode(hex_str).expect("valid hex"); - assert_eq!(bytes.len(), N, "key length"); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +fn key_material, const N: usize>(hex_str: &str) -> K { + let bytes: [u8; N] = hex::decode(hex_str).expect("valid hex").try_into().expect("key length"); + K::from_bytes(&bytes).expect("a valid key") } /// Runs one Appendix F.2 encrypt subsection. /// /// Checks the whole message in one call, then again one block at a time -- the vector should not /// care how the calls are grouped. -fn check_encrypt(section: &str, key_hex: &str, expected: &[&str; 4]) +fn check_encrypt(section: &str, key_hex: &str, expected: &[&str; 4]) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(expected); // All four blocks in one call. - let (mut enc, got_iv) = Cbc::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cbc::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -113,7 +113,7 @@ where assert_eq!(data, flat(expected), "{section}: four blocks in one call"); // One block at a time. - let (mut enc, _) = Cbc::::do_encrypt_init_rng( + let (mut enc, _) = Cbc::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -129,25 +129,26 @@ where /// /// Checks one call, one block at a time, and the odd grouping `3 + 1` -- which is the grouping that /// leaves a one-block remainder after the pair loop in `do_decrypt_blocks_inplace`. -fn check_decrypt(section: &str, key_hex: &str, ciphertext: &[&str; 4]) +fn check_decrypt(section: &str, key_hex: &str, ciphertext: &[&str; 4]) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(ciphertext); - type Dec = Cbc; + type Dec = Cbc; // All four blocks in one call (two pairs, no remainder). - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); let mut data = flat(ciphertext); dec.do_decrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); assert_eq!(data, flat(&PLAINTEXTS), "{section}: four blocks in one call"); // One block at a time (never takes the pair path). - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); for (i, (c, p)) in ct.iter().zip(pt.iter()).enumerate() { let mut got = *c; dec.do_decrypt_blocks_inplace(got.as_chunks_mut().0).unwrap(); @@ -155,7 +156,7 @@ where } // 3 + 1: one pair plus a remainder, then a lone block. - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); let mut three: [u8; 3 * BLOCK_LEN] = ct[..3].as_flattened().try_into().unwrap(); dec.do_decrypt_blocks_inplace(three.as_chunks_mut().0).unwrap(); let mut one = ct[3]; @@ -166,32 +167,32 @@ where #[test] fn f_2_1_cbc_aes128_encrypt() { - check_encrypt::("F.2.1", KEY_128, &CIPHERTEXTS_128); + check_encrypt::("F.2.1", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_2_2_cbc_aes128_decrypt() { - check_decrypt::("F.2.2", KEY_128, &CIPHERTEXTS_128); + check_decrypt::("F.2.2", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_2_3_cbc_aes192_encrypt() { - check_encrypt::("F.2.3", KEY_192, &CIPHERTEXTS_192); + check_encrypt::("F.2.3", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_2_4_cbc_aes192_decrypt() { - check_decrypt::("F.2.4", KEY_192, &CIPHERTEXTS_192); + check_decrypt::("F.2.4", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_2_5_cbc_aes256_encrypt() { - check_encrypt::("F.2.5", KEY_256, &CIPHERTEXTS_256); + check_encrypt::("F.2.5", KEY_256, &CIPHERTEXTS_256); } #[test] fn f_2_6_cbc_aes256_decrypt() { - check_decrypt::("F.2.6", KEY_256, &CIPHERTEXTS_256); + check_decrypt::("F.2.6", KEY_256, &CIPHERTEXTS_256); } /// The one-shot API must agree with the vectors too, on the decrypt side where the IV is an input. @@ -204,23 +205,25 @@ fn f_2_6_cbc_aes256_decrypt() { /// `7649abac8119b246cee98e9b12e9197d`. They differ solely because CBC XORs the IV in first. #[test] fn cbc_differs_from_ecb_by_the_iv() { - let key = key_material::<16>(KEY_128); + let key = key_material::(KEY_128); let iv = block(IV); // The raw permutation on P1 alone is the ECB answer from F.1.1. let mut ecb = block(PLAINTEXTS[0]); - >::encrypt_block( - &>::new(&key).unwrap(), + >::encrypt_block( + &>::new(key.get_key()) + .unwrap(), &mut ecb, ); assert_eq!(ecb, block("3ad77bb40d7a3660a89ecaf32466ef97"), "F.1.1 block #1"); // CBC's C1 = CIPH_K(P1 XOR IV) is the F.2.1 answer, and differs. - let (mut enc, _) = Cbc::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::<16>::new(iv), - ) - .unwrap(); + let (mut enc, _) = + Cbc::::do_encrypt_init_rng( + &key, + &mut FixedSeedRNG::<16>::new(iv), + ) + .unwrap(); let mut cbc = block(PLAINTEXTS[0]); enc.do_encrypt_blocks_inplace(cbc.as_chunks_mut().0).unwrap(); assert_eq!(cbc, block(CIPHERTEXTS_128[0]), "F.2.1 block #1"); diff --git a/crypto/aes/tests/sp800_38a_cfb8_tests.rs b/crypto/aes/tests/sp800_38a_cfb8_tests.rs index 194ca6141..773520d7e 100644 --- a/crypto/aes/tests/sp800_38a_cfb8_tests.rs +++ b/crypto/aes/tests/sp800_38a_cfb8_tests.rs @@ -31,11 +31,12 @@ //! any ciphertext. Decryption takes the IV directly, as init data. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CFB8_128_Key, AES_CFB8_192_Key, AES_CFB8_256_Key}; use bouncycastle_cipher::modes::Cfb8; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -120,11 +121,9 @@ fn bytes(hex_str: &str) -> Vec { hex::decode(hex_str).expect("valid hex") } -fn key_material(hex_str: &str) -> KeyMaterial { - let raw = hex::decode(hex_str).expect("valid hex"); - assert_eq!(raw.len(), N, "key length"); - KeyMaterial::::from_bytes_as_type(&raw, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +fn key_material, const N: usize>(hex_str: &str) -> K { + let raw: [u8; N] = hex::decode(hex_str).expect("valid hex").try_into().expect("key length"); + K::from_bytes(&raw).expect("a valid key") } /// Chunk sizes that cut across the four-byte batch and the 16-byte block: 1 is the single-byte @@ -135,18 +134,19 @@ const CHUNKINGS: [usize; 7] = [1, 3, 4, 8, 9, 17, 18]; /// /// Checks the whole message in one call, then in every chunking above -- the vector should not care /// how the calls are grouped. -fn check_encrypt(section: &str, key_hex: &str, expected_hex: &str) +fn check_encrypt(section: &str, key_hex: &str, expected_hex: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let plaintext = bytes(PLAINTEXT); let expected = bytes(expected_hex); assert_eq!(plaintext.len(), 18, "{section}: the CFB8 subsections use 18 one-byte segments"); for chunk in [plaintext.len()].into_iter().chain(CHUNKINGS) { - let (mut enc, got_iv) = Cfb8::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cfb8::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -162,18 +162,19 @@ where } /// Runs one Appendix F.3 CFB8 decrypt subsection. -fn check_decrypt(section: &str, key_hex: &str, ciphertext_hex: &str) +fn check_decrypt(section: &str, key_hex: &str, ciphertext_hex: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let plaintext = bytes(PLAINTEXT); let ciphertext = bytes(ciphertext_hex); for chunk in [ciphertext.len()].into_iter().chain(CHUNKINGS) { let mut dec = - Cfb8::::do_decrypt_init(&key, &iv).unwrap(); + Cfb8::::do_decrypt_init(&key, &iv).unwrap(); let mut data = ciphertext.clone(); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -183,38 +184,38 @@ where // ...and the one-shot, where the IV is an input. let mut data = ciphertext.clone(); - Cfb8::::decrypt_inplace(&key, &iv, &mut data).unwrap(); + Cfb8::::decrypt_inplace(&key, &iv, &mut data).unwrap(); assert_eq!(data, plaintext, "{section}: one-shot"); } #[test] fn f_3_7_cfb8_aes128_encrypt() { - check_encrypt::("F.3.7", KEY_128, CIPHERTEXT_128); + check_encrypt::("F.3.7", KEY_128, CIPHERTEXT_128); } #[test] fn f_3_8_cfb8_aes128_decrypt() { - check_decrypt::("F.3.8", KEY_128, CIPHERTEXT_128); + check_decrypt::("F.3.8", KEY_128, CIPHERTEXT_128); } #[test] fn f_3_9_cfb8_aes192_encrypt() { - check_encrypt::("F.3.9", KEY_192, CIPHERTEXT_192); + check_encrypt::("F.3.9", KEY_192, CIPHERTEXT_192); } #[test] fn f_3_10_cfb8_aes192_decrypt() { - check_decrypt::("F.3.10", KEY_192, CIPHERTEXT_192); + check_decrypt::("F.3.10", KEY_192, CIPHERTEXT_192); } #[test] fn f_3_11_cfb8_aes256_encrypt() { - check_encrypt::("F.3.11", KEY_256, CIPHERTEXT_256); + check_encrypt::("F.3.11", KEY_256, CIPHERTEXT_256); } #[test] fn f_3_12_cfb8_aes256_decrypt() { - check_decrypt::("F.3.12", KEY_256, CIPHERTEXT_256); + check_decrypt::("F.3.12", KEY_256, CIPHERTEXT_256); } /// The spec's tabulated **Input Blocks** are the shift register and its **Output Blocks** are @@ -230,9 +231,10 @@ fn f_3_12_cfb8_aes256_decrypt() { /// a typo in any of them. #[test] fn the_tabulated_blocks_are_the_shift_register() { - let key = key_material::<16>(KEY_128); + let key = key_material::(KEY_128); let perm = - >::new(&key).expect("a valid key"); + >::new(key.get_key()) + .expect("a valid key"); let plaintext = bytes(PLAINTEXT); let ciphertext = bytes(CIPHERTEXT_128); @@ -260,7 +262,9 @@ fn the_tabulated_blocks_are_the_shift_register() { // Oj = CIPH_K(Ij) -- the *forward* cipher function, which is all CFB ever uses. let mut computed = input_block; - perm.encrypt_block(&mut computed); + >::encrypt_block( + &perm, &mut computed, + ); assert_eq!( computed, output_block, diff --git a/crypto/aes/tests/sp800_38a_cfb_tests.rs b/crypto/aes/tests/sp800_38a_cfb_tests.rs index 0242b8e84..a0e2e94e3 100644 --- a/crypto/aes/tests/sp800_38a_cfb_tests.rs +++ b/crypto/aes/tests/sp800_38a_cfb_tests.rs @@ -33,11 +33,12 @@ //! any ciphertext. Decryption takes the IV directly, as init data. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CFB_128_Key, AES_CFB_192_Key, AES_CFB_256_Key}; use bouncycastle_cipher::modes::Cfb; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -122,11 +123,9 @@ fn flat(hex_strs: &[&str; 4]) -> [u8; 4 * BLOCK_LEN] { blocks(hex_strs).as_flattened().try_into().expect("4 blocks = 64 bytes") } -fn key_material(hex_str: &str) -> KeyMaterial { - let bytes = hex::decode(hex_str).expect("valid hex"); - assert_eq!(bytes.len(), N, "key length"); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +fn key_material, const N: usize>(hex_str: &str) -> K { + let bytes: [u8; N] = hex::decode(hex_str).expect("valid hex").try_into().expect("key length"); + K::from_bytes(&bytes).expect("a valid key") } /// Chunk sizes that never line up with a 16-byte segment, for the stream-cipher checks. @@ -136,17 +135,18 @@ const ODD_CHUNKS: [usize; 3] = [5, 23, 63]; /// /// Checks the whole message in one call, then again one segment at a time, then again in chunks /// that straddle the segments -- the vector should not care how the calls are grouped. -fn check_encrypt(section: &str, key_hex: &str, expected: &[&str; 4]) +fn check_encrypt(section: &str, key_hex: &str, expected: &[&str; 4]) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(expected); let init = || { - let (enc, got_iv) = Cfb::::do_encrypt_init_rng( + let (enc, got_iv) = Cfb::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -185,25 +185,26 @@ where /// Checks one call, one segment at a time, the odd grouping `3 + 1` -- which is the grouping that /// leaves a one-block remainder after the pair loop in `do_decrypt_inplace` -- and chunks that /// straddle the segments. -fn check_decrypt(section: &str, key_hex: &str, ciphertext: &[&str; 4]) +fn check_decrypt(section: &str, key_hex: &str, ciphertext: &[&str; 4]) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); + let key = key_material::(key_hex); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(ciphertext); - type Dec = Cfb; + type Dec = Cfb; // All four segments in one call (two pairs, no remainder). - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); let mut data = flat(ciphertext); dec.do_decrypt_inplace(&mut data).unwrap(); assert_eq!(data, flat(&PLAINTEXTS), "{section}: four segments in one call"); // One segment at a time (never takes the pair path). - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); for (i, (c, p)) in ct.iter().zip(pt.iter()).enumerate() { let mut got = *c; dec.do_decrypt_inplace(&mut got).unwrap(); @@ -211,7 +212,7 @@ where } // 3 + 1: one pair plus a remainder, then a lone block. - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); let mut three: [u8; 3 * BLOCK_LEN] = ct[..3].as_flattened().try_into().unwrap(); dec.do_decrypt_inplace(&mut three).unwrap(); let mut one = ct[3]; @@ -221,7 +222,7 @@ where // In chunks that cut across the segments. for chunk in ODD_CHUNKS { - let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = Dec::::do_decrypt_init(&key, &iv).unwrap(); let mut data = flat(ciphertext); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -232,32 +233,32 @@ where #[test] fn f_3_13_cfb128_aes128_encrypt() { - check_encrypt::("F.3.13", KEY_128, &CIPHERTEXTS_128); + check_encrypt::("F.3.13", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_3_14_cfb128_aes128_decrypt() { - check_decrypt::("F.3.14", KEY_128, &CIPHERTEXTS_128); + check_decrypt::("F.3.14", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_3_15_cfb128_aes192_encrypt() { - check_encrypt::("F.3.15", KEY_192, &CIPHERTEXTS_192); + check_encrypt::("F.3.15", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_3_16_cfb128_aes192_decrypt() { - check_decrypt::("F.3.16", KEY_192, &CIPHERTEXTS_192); + check_decrypt::("F.3.16", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_3_17_cfb128_aes256_encrypt() { - check_encrypt::("F.3.17", KEY_256, &CIPHERTEXTS_256); + check_encrypt::("F.3.17", KEY_256, &CIPHERTEXTS_256); } #[test] fn f_3_18_cfb128_aes256_decrypt() { - check_decrypt::("F.3.18", KEY_256, &CIPHERTEXTS_256); + check_decrypt::("F.3.18", KEY_256, &CIPHERTEXTS_256); } /// The one-shot API must agree with the vectors too, on the decrypt side where the IV is an input. @@ -277,16 +278,17 @@ fn f_3_18_cfb128_aes256_decrypt() { /// /// It also confirms the transcription: the ciphertext and output-block columns above are related by /// an XOR that would not survive a typo in either. -fn check_output_blocks( +fn check_output_blocks( section: &str, key_hex: &str, ciphertexts: &[&str; 4], output_blocks: &[&str; 4], ) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { - let key = key_material::(key_hex); - let perm = P::new(&key).expect("a valid key"); + let key = key_material::(key_hex); + let perm = P::new(key.get_key()).expect("a valid key"); let pt = blocks(&PLAINTEXTS); let ct = blocks(ciphertexts); let o = blocks(output_blocks); @@ -314,13 +316,13 @@ fn check_output_blocks( #[test] fn the_tabulated_output_blocks_are_the_keystream() { - check_output_blocks::( + check_output_blocks::( "F.3.13", KEY_128, &CIPHERTEXTS_128, &OUTPUT_BLOCKS_128, ); - check_output_blocks::( + check_output_blocks::( "F.3.15", KEY_192, &CIPHERTEXTS_192, &OUTPUT_BLOCKS_192, ); - check_output_blocks::( + check_output_blocks::( "F.3.17", KEY_256, &CIPHERTEXTS_256, &OUTPUT_BLOCKS_256, ); } @@ -348,13 +350,14 @@ fn cfb128_agrees_with_ofb_on_the_first_block_only() { "F.3.13 and F.4.1 must tabulate the same O1 = CIPH_K(IV)" ); - let key = key_material::<16>(KEY_128); + let key = key_material::(KEY_128); let iv = block(IV); - let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::<16>::new(iv), - ) - .unwrap(); + let (mut enc, got_iv) = + Cfb::::do_encrypt_init_rng( + &key, + &mut FixedSeedRNG::<16>::new(iv), + ) + .unwrap(); assert_eq!(got_iv, iv); let mut c1 = block(PLAINTEXTS[0]); diff --git a/crypto/aes/tests/sp800_38a_ecb_tests.rs b/crypto/aes/tests/sp800_38a_ecb_tests.rs index 3f49390cc..1dde6e1ff 100644 --- a/crypto/aes/tests/sp800_38a_ecb_tests.rs +++ b/crypto/aes/tests/sp800_38a_ecb_tests.rs @@ -7,9 +7,8 @@ //! `bc-test-data` repository is present, so these vectors are the always-available known-answer //! floor. //! -//! ECB applies the raw permutation to each block independently, so an ECB example vector *is* a -//! block-permutation test vector. (That is the only reason ECB appears in this crate; see the -//! crate docs on why you must not use it to encrypt anything.) +//! The vectors are driven through [`Ecb`] over the AES engines, which is the AES-ECB this crate +//! exposes under `hazmat`; see the crate docs on why you must not use it to encrypt anything. //! //! The keys are the same three keys as FIPS 197 Appendix A.1, A.2 and A.3, so these vectors also //! pin each key expansion against a NIST-published answer, in both directions. @@ -17,11 +16,19 @@ //! Transcribed from the published SP 800-38A PDF, sections F.1.1 through F.1.6. use bouncycastle_aes::AES_BLOCK_LEN; -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_aes::hazmat::{ + AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key, AES128Internal, AES192Internal, + AES256Internal, +}; +use bouncycastle_cipher::modes::hazmat::ecb::Ecb; +use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; use bouncycastle_hex as hex; +type Ecb128 = Ecb; +type Ecb192 = Ecb; +type Ecb256 = Ecb; + /// The four plaintext blocks shared by every F.1 subsection. const PLAINTEXTS: [&str; 4] = [ "6bc1bee22e409f96e93d7e117393172a", @@ -64,31 +71,31 @@ fn block(hex_str: &str) -> [u8; AES_BLOCK_LEN] { hex::decode(hex_str).expect("valid hex").try_into().expect("16 bytes") } -fn key_material(hex_str: &str) -> KeyMaterial { - let bytes = hex::decode(hex_str).expect("valid hex"); - assert_eq!(bytes.len(), N, "key length"); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey) - .expect("a valid symmetric cipher key") +fn key, const N: usize>(hex_str: &str) -> K { + let bytes: [u8; N] = hex::decode(hex_str).expect("valid hex").try_into().expect("key length"); + K::from_bytes(&bytes).expect("a valid symmetric cipher key") } // ---- F.1.1 / F.1.2 ECB-AES128 ------------------------------------------------------------- #[test] fn f_1_1_ecb_aes128_encrypt() { - let aes = AES128Internal::new(&key_material::<16>(KEY_128)).unwrap(); + let (mut ecb, _) = + Ecb128::::do_encrypt_init(&key::(KEY_128)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_128.iter()).enumerate() { let mut b = block(pt); - aes.encrypt_block(&mut b); + ecb.do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(ct), "F.1.1 block #{}", i + 1); } } #[test] fn f_1_2_ecb_aes128_decrypt() { - let aes = AES128Internal::new(&key_material::<16>(KEY_128)).unwrap(); + let mut ecb = + Ecb128::::do_decrypt_init(&key::(KEY_128), &[]).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_128.iter()).enumerate() { let mut b = block(ct); - aes.decrypt_block(&mut b); + ecb.do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(pt), "F.1.2 block #{}", i + 1); } } @@ -97,20 +104,22 @@ fn f_1_2_ecb_aes128_decrypt() { #[test] fn f_1_3_ecb_aes192_encrypt() { - let aes = AES192Internal::new(&key_material::<24>(KEY_192)).unwrap(); + let (mut ecb, _) = + Ecb192::::do_encrypt_init(&key::(KEY_192)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_192.iter()).enumerate() { let mut b = block(pt); - aes.encrypt_block(&mut b); + ecb.do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(ct), "F.1.3 block #{}", i + 1); } } #[test] fn f_1_4_ecb_aes192_decrypt() { - let aes = AES192Internal::new(&key_material::<24>(KEY_192)).unwrap(); + let mut ecb = + Ecb192::::do_decrypt_init(&key::(KEY_192), &[]).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_192.iter()).enumerate() { let mut b = block(ct); - aes.decrypt_block(&mut b); + ecb.do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(pt), "F.1.4 block #{}", i + 1); } } @@ -119,20 +128,22 @@ fn f_1_4_ecb_aes192_decrypt() { #[test] fn f_1_5_ecb_aes256_encrypt() { - let aes = AES256Internal::new(&key_material::<32>(KEY_256)).unwrap(); + let (mut ecb, _) = + Ecb256::::do_encrypt_init(&key::(KEY_256)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_256.iter()).enumerate() { let mut b = block(pt); - aes.encrypt_block(&mut b); + ecb.do_encrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(ct), "F.1.5 block #{}", i + 1); } } #[test] fn f_1_6_ecb_aes256_decrypt() { - let aes = AES256Internal::new(&key_material::<32>(KEY_256)).unwrap(); + let mut ecb = + Ecb256::::do_decrypt_init(&key::(KEY_256), &[]).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_256.iter()).enumerate() { let mut b = block(ct); - aes.decrypt_block(&mut b); + ecb.do_decrypt_blocks_inplace(core::array::from_mut(&mut b)).unwrap(); assert_eq!(b, block(pt), "F.1.6 block #{}", i + 1); } } @@ -146,17 +157,20 @@ fn f_1_6_ecb_aes256_decrypt() { /// single-block call has only one lane. #[test] fn two_block_path_matches_the_f_1_vectors() { - let aes = AES128Internal::new(&key_material::<16>(KEY_128)).unwrap(); + let (mut enc, _) = + Ecb128::::do_encrypt_init(&key::(KEY_128)).unwrap(); + let mut dec = + Ecb128::::do_decrypt_init(&key::(KEY_128), &[]).unwrap(); // Blocks 1 and 2 as a pair, then 3 and 4. for chunk in 0..2 { let (i, j) = (chunk * 2, chunk * 2 + 1); let mut pair = [block(PLAINTEXTS[i]), block(PLAINTEXTS[j])]; - aes.encrypt_2blocks(&mut pair); + enc.do_encrypt_blocks_inplace(&mut pair).unwrap(); assert_eq!(pair[0], block(CIPHERTEXTS_128[i]), "pair {chunk} slot 0"); assert_eq!(pair[1], block(CIPHERTEXTS_128[j]), "pair {chunk} slot 1"); - aes.decrypt_2blocks(&mut pair); + dec.do_decrypt_blocks_inplace(&mut pair).unwrap(); assert_eq!(pair[0], block(PLAINTEXTS[i])); assert_eq!(pair[1], block(PLAINTEXTS[j])); } @@ -165,12 +179,13 @@ fn two_block_path_matches_the_f_1_vectors() { /// Swapping the two slots must swap the two results, and nothing else. #[test] fn two_block_path_is_slot_symmetric() { - let aes = AES256Internal::new(&key_material::<32>(KEY_256)).unwrap(); + let (mut enc, _) = + Ecb256::::do_encrypt_init(&key::(KEY_256)).unwrap(); let mut forward = [block(PLAINTEXTS[0]), block(PLAINTEXTS[1])]; let mut reversed = [block(PLAINTEXTS[1]), block(PLAINTEXTS[0])]; - aes.encrypt_2blocks(&mut forward); - aes.encrypt_2blocks(&mut reversed); + enc.do_encrypt_blocks_inplace(&mut forward).unwrap(); + enc.do_encrypt_blocks_inplace(&mut reversed).unwrap(); assert_eq!(forward[0], reversed[1]); assert_eq!(forward[1], reversed[0]); @@ -186,25 +201,29 @@ fn two_block_path_is_slot_symmetric() { /// is what pins the four 16-bit lanes of the `u64` planes to the four slots, in order. #[test] fn four_block_path_matches_the_f_1_vectors() { - let aes = AES192Internal::new(&key_material::<24>(KEY_192)).unwrap(); + let (mut enc, _) = + Ecb192::::do_encrypt_init(&key::(KEY_192)).unwrap(); + let mut dec = + Ecb192::::do_decrypt_init(&key::(KEY_192), &[]).unwrap(); let mut four = PLAINTEXTS.map(block); - aes.encrypt_4blocks(&mut four); + enc.do_encrypt_blocks_inplace(&mut four).unwrap(); assert_eq!(four, CIPHERTEXTS_192.map(block)); - aes.decrypt_4blocks(&mut four); + dec.do_decrypt_blocks_inplace(&mut four).unwrap(); assert_eq!(four, PLAINTEXTS.map(block)); } /// Permuting the four slots must permute the four results, and nothing else. #[test] fn four_block_path_is_slot_symmetric() { - let aes = AES256Internal::new(&key_material::<32>(KEY_256)).unwrap(); + let (mut enc, _) = + Ecb256::::do_encrypt_init(&key::(KEY_256)).unwrap(); // Every cyclic rotation of the four F.1 plaintexts. for shift in 0..4 { let mut four: [_; 4] = core::array::from_fn(|i| block(PLAINTEXTS[(i + shift) % 4])); - aes.encrypt_4blocks(&mut four); + enc.do_encrypt_blocks_inplace(&mut four).unwrap(); for i in 0..4 { assert_eq!(four[i], block(CIPHERTEXTS_256[(i + shift) % 4]), "shift {shift}, slot {i}"); } diff --git a/crypto/aes/tests/sp800_38c_tests.rs b/crypto/aes/tests/sp800_38c_tests.rs index 89952f604..8e5540002 100644 --- a/crypto/aes/tests/sp800_38c_tests.rs +++ b/crypto/aes/tests/sp800_38c_tests.rs @@ -18,11 +18,12 @@ //! direction is checked by round-tripping each vector's own `C` back to its `P`. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_CCM_128_Key, AES_CCM_192_Key, AES_CCM_256_Key}; use bouncycastle_cipher::modes::ModeNames; use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; @@ -33,11 +34,10 @@ use bouncycastle_hex as hex; /// Appendix C's key, the same in all four examples: `40414243 44454647 48494a4b 4c4d4e4f`. const APPENDIX_C_KEY: &str = "404142434445464748494a4b4c4d4e4f"; -fn key(hex_key: &str) -> KeyMaterial { - let bytes = hex::decode(hex_key).expect("valid hex key"); - assert_eq!(bytes.len(), N, "key length must match the parameter set"); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey) - .expect("a symmetric cipher key") +fn key, const N: usize>(hex_key: &str) -> K { + let bytes: [u8; N] = + hex::decode(hex_key).expect("valid hex key").try_into().expect("key length"); + K::from_bytes(&bytes).expect("a valid key") } fn buffer_len_error(r: Result) -> Option { @@ -55,7 +55,8 @@ fn check_vector< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, >( name: &str, key_hex: &str, @@ -67,12 +68,12 @@ fn check_vector< // it opts out. sweep: bool, ) { - type Enc = - CcmPacket; - type Dec = - CcmPacket; + type Enc = + CcmPacket; + type Dec = + CcmPacket; - let k = key::(key_hex); + let k = key::(key_hex); let nonce_bytes = hex::decode(nonce_hex).expect("valid hex nonce"); let nonce: [u8; NONCE_LEN] = nonce_bytes.try_into().expect("nonce length matches NONCE_LEN"); let plaintext = hex::decode(plaintext_hex).expect("valid hex plaintext"); @@ -87,7 +88,7 @@ fn check_vector< // --- Sec 6.1, detached tag --- let mut ct = vec![0u8; plaintext.len()]; - let (written, tag) = Enc::::encrypt_detached_out( + let (written, tag) = Enc::::encrypt_detached_out( &k, &nonce, aad, &plaintext, &mut ct, ) .expect("encryption"); @@ -97,7 +98,7 @@ fn check_vector< // --- Sec 6.1, the appendix's own inline `ciphertext || tag` layout --- let mut inline = vec![0u8; plaintext.len() + TAG_LEN]; - let n = Enc::::encrypt_out( + let n = Enc::::encrypt_out( &k, &nonce, aad, &plaintext, &mut inline, ) .expect("encryption"); @@ -106,7 +107,7 @@ fn check_vector< // --- Sec 6.2, both layouts --- let mut recovered = vec![0u8; plaintext.len()]; - let n = Dec::::decrypt_detached_out( + let n = Dec::::decrypt_detached_out( &k, &nonce, aad, @@ -119,8 +120,9 @@ fn check_vector< assert_eq!(recovered, plaintext, "{name}: detached round trip"); let mut recovered = vec![0u8; plaintext.len()]; - let n = Dec::::decrypt_out(&k, &nonce, aad, &c, &mut recovered) - .expect("decryption"); + let n = + Dec::::decrypt_out(&k, &nonce, aad, &c, &mut recovered) + .expect("decryption"); assert_eq!(n, plaintext.len()); assert_eq!(recovered, plaintext, "{name}: inline round trip"); @@ -130,7 +132,7 @@ fn check_vector< // up front; given that, the chunking must be invisible, exactly as for the other modes. for chunk in [1usize, 2, 3, 7, 16, 17] { let mut ccm = - Enc::::new(&k, &nonce, aad, plaintext.len()) + Enc::::new(&k, &nonce, aad, plaintext.len()) .expect("streaming init"); let mut streamed = plaintext.clone(); for piece in streamed.chunks_mut(chunk) { @@ -141,7 +143,7 @@ fn check_vector< assert_eq!(streamed_tag, want_tag, "{name}: tag, streamed in {chunk}-byte chunks"); let mut ccm = - Dec::::new(&k, &nonce, aad, plaintext.len()) + Dec::::new(&k, &nonce, aad, plaintext.len()) .expect("streaming init"); for piece in streamed.chunks_mut(chunk) { ccm.do_decrypt_inplace(piece).expect("update"); @@ -157,7 +159,7 @@ fn check_vector< /// `n = 7`, so `q = 8`: the widest length field A.1 allows, and the shortest permitted tag. #[test] fn appendix_c1() { - check_vector::<16, 7, 4, AES128Internal>( + check_vector::<16, 7, 4, AES_CCM_128_Key, AES128Internal>( "C.1", APPENDIX_C_KEY, "10111213141516", @@ -175,7 +177,7 @@ fn appendix_c1() { /// "minimum number of '0' bits, possibly none" is none. #[test] fn appendix_c2() { - check_vector::<16, 8, 6, AES128Internal>( + check_vector::<16, 8, 6, AES_CCM_128_Key, AES128Internal>( "C.2", APPENDIX_C_KEY, "1011121314151617", @@ -193,7 +195,7 @@ fn appendix_c2() { /// the payload spans two counter blocks. #[test] fn appendix_c3() { - check_vector::<16, 12, 8, AES128Internal>( + check_vector::<16, 12, 8, AES_CCM_128_Key, AES128Internal>( "C.3", APPENDIX_C_KEY, "101112131415161718191a1b", @@ -225,7 +227,7 @@ fn appendix_c4() { } assert_eq!(aad.len(), 65536, "Alen = 524288 bits"); - check_vector::<16, 13, 14, AES128Internal>( + check_vector::<16, 13, 14, AES_CCM_128_Key, AES128Internal>( "C.4", APPENDIX_C_KEY, "101112131415161718191a1b1c", @@ -266,8 +268,9 @@ fn framework_streaming_contract() { 12, 16, 16, - CcmEncryptor, - CcmDecryptor, + AES_CCM_128_Key, + CcmEncryptor, + CcmDecryptor, >(); } @@ -280,16 +283,18 @@ fn framework_streaming_contract_other_parameter_sets() { 12, 16, 16, - CcmEncryptor, - CcmDecryptor, + AES_CCM_192_Key, + CcmEncryptor, + CcmDecryptor, >(); framework(240).test_encryptor_decryptor::< 32, 12, 16, 16, - CcmEncryptor, - CcmDecryptor, + AES_CCM_256_Key, + CcmEncryptor, + CcmDecryptor, >(); // A 13-byte nonce (q = 2) with an 8-byte tag: the parameterization IEEE 802.11 CCMP uses, and // the one A.1's narrowest length field applies to. @@ -298,8 +303,9 @@ fn framework_streaming_contract_other_parameter_sets() { 13, 8, 8, - CcmEncryptor, - CcmDecryptor, + AES_CCM_128_Key, + CcmEncryptor, + CcmDecryptor, >(); // The empty frame: a message that is nothing but its AAD and tag. framework(0).test_encryptor_decryptor::< @@ -307,8 +313,9 @@ fn framework_streaming_contract_other_parameter_sets() { 12, 16, 16, - CcmEncryptor, - CcmDecryptor, + AES_CCM_128_Key, + CcmEncryptor, + CcmDecryptor, >(); } @@ -318,10 +325,10 @@ fn framework_streaming_contract_other_parameter_sets() { /// and its AAD 20, so that is the frame. #[test] fn the_fixed_frame_pair_agrees_with_the_direct_api_on_appendix_c3() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let k = key::(APPENDIX_C_KEY); let nonce_bytes = hex::decode("101112131415161718191a1b").unwrap(); let aad = hex::decode("000102030405060708090a0b0c0d0e0f10111213").unwrap(); let plaintext = hex::decode("202122232425262728292a2b2c2d2e2f3031323334353637").unwrap(); @@ -398,9 +405,9 @@ fn the_fixed_frame_pair_agrees_with_the_direct_api_on_appendix_c3() { /// AAD past `AAD_LEN`. #[test] fn the_adapters_refuse_more_than_the_declared_lengths() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); let mut out = [0xEEu8; 33]; @@ -458,9 +465,9 @@ fn the_adapters_refuse_more_than_the_declared_lengths() { /// call starts the data phase. #[test] fn an_empty_update_does_not_close_the_aad_phase() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let aad = b"header"; let message = b"payload"; @@ -479,7 +486,7 @@ fn an_empty_update_does_not_close_the_aad_phase() { // The AAD really was absorbed: the direct API with the same AAD must agree, and the // decryptor, given the same empty-then-AAD sequence, must verify it. let mut expected = [0u8; 7 + 16]; - let n = CcmPacket::::encrypt_out( + let n = CcmPacket::::encrypt_out( &k, &nonce, aad, message, &mut expected, ) .expect("direct"); @@ -507,9 +514,9 @@ fn an_empty_update_does_not_close_the_aad_phase() { /// its own bookkeeping goes wrong. #[test] fn exact_lengths_are_accepted_whole_and_split() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let aad = [0x11u8; 32]; let message = [0x5Au8; 32]; let nonce_seed = [0x24u8; 12]; @@ -560,9 +567,9 @@ fn exact_lengths_are_accepted_whole_and_split() { /// wants, and the AAD may fall short of its capacity, including all the way to none. #[test] fn the_aad_capacity_and_the_payload_length_are_independent() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); // AAD past AAD_LEN is refused, although it would fit in DATA_LEN. let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); @@ -580,11 +587,18 @@ fn the_aad_capacity_and_the_payload_length_are_independent() { let (_, _, tag) = enc.do_encrypt_final_detached().expect("final"); let mut direct = [0u8; 64]; - let (_, direct_tag) = - CcmPacket::::encrypt_detached_out( - &k, &nonce, aad, &message, &mut direct, - ) - .expect("direct"); + let (_, direct_tag) = CcmPacket::< + AES128Internal, + Encrypting, + AES_CCM_128_Key, + 16, + 16, + 12, + 16, + >::encrypt_detached_out( + &k, &nonce, aad, &message, &mut direct + ) + .expect("direct"); assert_eq!((sealed, tag), (direct, direct_tag), "aad of {} bytes", aad.len()); let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); @@ -601,9 +615,9 @@ fn the_aad_capacity_and_the_payload_length_are_independent() { /// excess ciphertext if it says detached. Nothing is released by either final. #[test] fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let message = [0x5Au8; 32]; let (mut enc, nonce) = Enc::do_encrypt_init(&k).expect("init"); @@ -662,10 +676,10 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { /// methods stay bound; see `the_adapters_refuse_more_than_the_declared_lengths`. #[test] fn trait_one_shots_take_any_length_and_agree_with_the_direct_api() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - type Direct = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + type Direct = CcmPacket; + let k = key::(APPENDIX_C_KEY); let rng = || FixedSeedRNG::<12>::new([0x24u8; 12]); // The frame, one byte either side of it, nothing, and well past it; AAD at, under and past @@ -766,9 +780,9 @@ fn trait_one_shots_take_any_length_and_agree_with_the_direct_api() { /// succeeding on the right amount, so a matcher that passed for the wrong reason would show. #[test] fn every_final_refuses_a_payload_of_the_wrong_length() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let aad = b"header"; let frame = [0x5Au8; 8]; let nonce_seed = [0x24u8; 12]; @@ -920,11 +934,11 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { /// used. #[test] fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { - type Enc = CcmPacket; - type Dec = CcmPacket; + type Enc = CcmPacket; + type Dec = CcmPacket; // The empty frame, so that every byte of a short `C` is a (missing) tag byte. - type StreamDec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type StreamDec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let nonce = [0u8; 12]; let mut out = [0u8; 16]; let mut nothing = [0u8; 0]; @@ -976,9 +990,9 @@ fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { #[test] fn a_short_inline_ciphertext_is_rejected_the_same_way_for_a_non_empty_frame() { const DATA_LEN: usize = 32; - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + let k = key::(APPENDIX_C_KEY); let frame = [0x5Au8; DATA_LEN]; let mut sealed = vec![0u8; Enc::encrypt_out_len(DATA_LEN)]; let (nonce, n) = Enc::encrypt_with_aad_out(&k, b"hdr", &frame, &mut sealed).expect("seal"); @@ -1026,9 +1040,9 @@ fn a_short_inline_ciphertext_is_rejected_the_same_way_for_a_non_empty_frame() { /// An output buffer that is too short is refused with the length required, before any work. #[test] fn undersized_output_buffers_are_refused() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmPacket; + type Dec = CcmPacket; + let k = key::(APPENDIX_C_KEY); let nonce = [0u8; 12]; let plaintext = [0xAAu8; 24]; @@ -1050,32 +1064,6 @@ fn undersized_output_buffers_are_refused() { assert_eq!(buffer_len_error(Dec::decrypt_out(&k, &nonce, &[], &ct, &mut too_small)), Some(24)); } -/// A key of the wrong [`KeyType`] is rejected by every entry point, in both directions. -#[test] -fn a_non_cipher_key_is_rejected() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let wrong = - KeyMaterial::<16>::from_bytes_as_type(&[0x11; 16], KeyType::MACKey).expect("a MAC key"); - let mut out = [0u8; 16]; - assert!(matches!( - Enc::encrypt_detached_out(&wrong, &[0u8; 12], &[], &[], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Enc::new(&wrong, &[0u8; 12], &[], 0), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::decrypt_out(&wrong, &[0u8; 12], &[], &[0u8; 16], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::new(&wrong, &[0u8; 12], &[], 0), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); -} - /// The direction is in the type, so the wrong direction's method is a **compile** error rather /// than a runtime one. This is what the `Dir` parameter buys over a runtime flag, and without a /// test the guarantee could quietly regress into an inherent method on the shared impl block. @@ -1085,9 +1073,9 @@ fn a_non_cipher_key_is_rejected() { /// `compile_fail` cannot express. #[test] fn each_direction_has_its_own_methods() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmPacket; + type Dec = CcmPacket; + let k = key::(APPENDIX_C_KEY); let nonce = [0x55u8; 12]; let mut enc = Enc::new(&k, &nonce, b"aad", 4).expect("encrypt init"); @@ -1110,39 +1098,57 @@ fn each_direction_has_its_own_methods() { fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), 264); - assert_eq!(size_of::>(), 296); - assert_eq!(size_of::>(), 328); + assert_eq!( + size_of::>(), + 264 + ); + assert_eq!( + size_of::>(), + 296 + ); + assert_eq!( + size_of::>(), + 328 + ); // Independent of NONCE_LEN and TAG_LEN: the nonce lives inside the counter template and the // tag is assembled at finalization, not held. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); // The direction marker is free, and does not change the layout. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); // The fixed-frame pair holds no payload: a 4 KiB frame costs exactly what a 16-byte one does. - let enc_64 = size_of::>(); - assert_eq!(enc_64, size_of::>()); + let enc_64 = + size_of::>(); + assert_eq!( + enc_64, + size_of::>() + ); assert_eq!(enc_64, 264 + 64 + 16, "CcmPacket, the AAD buffer, its length and the phase flag"); assert_eq!( - size_of::>() - enc_64, + size_of::>() + - enc_64, 4096 - 64, "the value grows by exactly the AAD capacity" ); // The decryptor adds the tag it holds back and that tag's length. - let dec_64 = size_of::>(); - assert_eq!(dec_64, size_of::>()); + let dec_64 = + size_of::>(); + assert_eq!( + dec_64, + size_of::>() + ); assert_eq!(dec_64, enc_64 + 16 + 8); } @@ -1151,16 +1157,26 @@ fn sizes_match_the_documented_memory_table() { /// A.1's `p < 2^8q`. With `n = 13`, `q = 2`, so the limit is 65535 and 65536 must be refused. #[test] fn payload_longer_than_the_q_limit_is_refused() { - let k = key::<16>(APPENDIX_C_KEY); + let k = key::(APPENDIX_C_KEY); let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; assert!( - CcmPacket::::new(&k, &nonce, &[], 65535) - .is_ok(), + CcmPacket::::new( + &k, + &nonce, + &[], + 65535 + ) + .is_ok(), "2^16 - 1 is the largest payload q = 2 can encode" ); assert!( matches!( - CcmPacket::::new(&k, &nonce, &[], 65536), + CcmPacket::::new( + &k, + &nonce, + &[], + 65536 + ), Err(SymmetricCipherError::GenericError(_)) ), "2^16 does not fit [p]_16" @@ -1179,20 +1195,22 @@ fn check_progressive_aad( c: &str, chunk: usize, ) { - let k = key::<16>(APPENDIX_C_KEY); + let k = key::(APPENDIX_C_KEY); let nonce: [u8; NONCE_LEN] = hex::decode(nonce).unwrap().try_into().unwrap(); let plaintext = hex::decode(plaintext).unwrap(); let c = hex::decode(c).unwrap(); let (want_ct, want_tag) = c.split_at(plaintext.len()); - let mut enc = - CcmPacket::::new_with_lengths( - &k, - &nonce, - aad.len(), - plaintext.len(), - ) - .unwrap(); + let mut enc = CcmPacket::< + AES128Internal, + Encrypting, + AES_CCM_128_Key, + 16, + 16, + NONCE_LEN, + TAG_LEN, + >::new_with_lengths(&k, &nonce, aad.len(), plaintext.len()) + .unwrap(); for piece in aad.chunks(chunk) { enc.do_update_aad(piece).unwrap(); } @@ -1202,14 +1220,16 @@ fn check_progressive_aad( assert_eq!(data, want_ct, "{label}: ciphertext, AAD in {chunk}-byte pieces"); assert_eq!(&tag[..], want_tag, "{label}: tag, AAD in {chunk}-byte pieces"); - let mut dec = - CcmPacket::::new_with_lengths( - &k, - &nonce, - aad.len(), - plaintext.len(), - ) - .unwrap(); + let mut dec = CcmPacket::< + AES128Internal, + Decrypting, + AES_CCM_128_Key, + 16, + 16, + NONCE_LEN, + TAG_LEN, + >::new_with_lengths(&k, &nonce, aad.len(), plaintext.len()) + .unwrap(); for piece in aad.chunks(chunk) { dec.do_update_aad(piece).unwrap(); } @@ -1255,9 +1275,9 @@ fn progressive_aad_matches_appendix_c() { /// until the AAD is complete. A refused call consumes nothing. #[test] fn progressive_aad_enforces_the_declared_length_and_order() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmPacket; + type Dec = CcmPacket; + let k = key::(APPENDIX_C_KEY); let nonce = [0x24u8; 12]; let aad = b"0123456789"; let message = *b"payload"; @@ -1327,10 +1347,10 @@ fn progressive_aad_enforces_the_declared_length_and_order() { /// here because nothing draws one. #[test] fn supplied_nonce_forms_agree_with_the_direct_api_at_any_a1_nonce_length() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - type Direct = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + type Enc = CcmEncryptor; + type Dec = CcmDecryptor; + type Direct = CcmPacket; + let k = key::(APPENDIX_C_KEY); let nonce = [0x11u8; 13]; let aad = [0x3Cu8; 32]; let frame = [0xA5u8; 48]; diff --git a/crypto/aes/tests/suspend_tests.rs b/crypto/aes/tests/suspend_tests.rs index 644970f56..677c110d0 100644 --- a/crypto/aes/tests/suspend_tests.rs +++ b/crypto/aes/tests/suspend_tests.rs @@ -6,22 +6,29 @@ //! rebuilds it from the re-supplied key. The test does part of an operation, suspends a clone, //! resumes it, and finishes both the same way. -use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; +use bouncycastle_aes::hazmat::{ + AES_ECB_128, AES_ECB_128_Key, AES_ECB_192, AES_ECB_192_Key, AES_ECB_256, AES_ECB_256_Key, +}; use bouncycastle_aes::{ - AES_CBC_128, AES_CBC_192, AES_CBC_256, AES_CCM_128_Packet, AES_CCM_192_Packet, - AES_CCM_256_Packet, AES_CFB_128, AES_CFB_192, AES_CFB_256, AES_CFB8_128, AES_CFB8_192, - AES_CFB8_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, AES_GCM_128, AES_GCM_192, AES_GCM_256, + AES_CBC_128, AES_CBC_128_Key, AES_CBC_192, AES_CBC_192_Key, AES_CBC_256, AES_CBC_256_Key, + AES_CCM_128_Key, AES_CCM_128_Packet, AES_CCM_192_Key, AES_CCM_192_Packet, AES_CCM_256_Key, + AES_CCM_256_Packet, AES_CFB_128, AES_CFB_128_Key, AES_CFB_192, AES_CFB_192_Key, AES_CFB_256, + AES_CFB_256_Key, AES_CFB8_128, AES_CFB8_128_Key, AES_CFB8_192, AES_CFB8_192_Key, AES_CFB8_256, + AES_CFB8_256_Key, AES_CTR_128, AES_CTR_128_Key, AES_CTR_192, AES_CTR_192_Key, AES_CTR_256, + AES_CTR_256_Key, AES_GCM_128, AES_GCM_128_Key, AES_GCM_192, AES_GCM_192_Key, AES_GCM_256, + AES_GCM_256_Key, }; use bouncycastle_cipher::Encrypting; use bouncycastle_cipher::padding::PKCS7; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ AEADCipherEncryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherEncryptor, + SymmetricCipherKey, }; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; -fn key() -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(&[0x42; K], KeyType::SymmetricCipherKey).unwrap() +/// The same `K` bytes wrapped as whichever alias's key type is asked for. +fn key, const K: usize>() -> Key { + Key::from_bytes(&[0x42; K]).unwrap() } fn round_trip( @@ -29,9 +36,10 @@ fn round_trip( finish: impl Fn(C) -> Vec, ) -> Vec where - C: SuspendableKeyed> + Clone, + C: SuspendableKeyed + Clone, + C::Key: SymmetricCipherKey, { - let key = key::(); + let key = key::(); TestFrameworkSuspendableKeyedState::new().test(&cipher, &key); let resumed = C::from_suspended(cipher.clone().suspend(), &key).unwrap(); let original_output = finish(cipher); @@ -39,13 +47,23 @@ where original_output } -/// Runs every alias family at one key length. The families are built and finished differently, -/// and each needs its own `SUSPENDED_STATE_LEN` as a const argument, which a generic function -/// cannot take from a type parameter; so the per-size repetition is a macro. +/// Runs every alias family at one key length, each `alias / key type` pair keyed with that +/// family's own key type. The families are built and finished differently, and each needs its own +/// `SUSPENDED_STATE_LEN` as a const argument, which a generic function cannot take from a type +/// parameter; so the per-size repetition is a macro. macro_rules! every_alias_family_is_suspendable { - ($k:literal, $cbc:ident, $ecb:ident, $cfb:ident, $cfb8:ident, $ctr:ident, $gcm:ident, $ccm:ident) => {{ + ( + $k:literal, + $cbc:ident / $cbc_key:ident, + $ecb:ident / $ecb_key:ident, + $cfb:ident / $cfb_key:ident, + $cfb8:ident / $cfb8_key:ident, + $ctr:ident / $ctr_key:ident, + $gcm:ident / $gcm_key:ident, + $ccm:ident / $ccm_key:ident $(,)? + ) => {{ type CbcEnc = $cbc; - let (mut cbc, _) = CbcEnc::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut cbc, _) = CbcEnc::do_encrypt_init(&key::<$cbc_key, $k>()).unwrap(); cbc.do_encrypt_out(&[0x11u8; 20], &mut [0u8; 16]).unwrap(); round_trip::<{ CbcEnc::SUSPENDED_STATE_LEN }, $k, _>(cbc, |mut e| { let mut out = [0u8; 16]; @@ -55,14 +73,14 @@ macro_rules! every_alias_family_is_suspendable { }); type EcbEnc = $ecb; - let (mut ecb, _) = EcbEnc::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut ecb, _) = EcbEnc::do_encrypt_init(&key::<$ecb_key, $k>()).unwrap(); ecb.do_encrypt_out(&[0x11u8; 20], &mut [0u8; 16]).unwrap(); round_trip::<{ EcbEnc::SUSPENDED_STATE_LEN }, $k, _>(ecb, |e| { let (last, n) = e.do_encrypt_final().unwrap(); last[..n].to_vec() }); - let (mut cfb, _) = $cfb::::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut cfb, _) = $cfb::::do_encrypt_init(&key::<$cfb_key, $k>()).unwrap(); cfb.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); round_trip::<{ $cfb::::SUSPENDED_STATE_LEN }, $k, _>(cfb, |mut e| { let mut data = [0x22u8; 25]; @@ -70,7 +88,7 @@ macro_rules! every_alias_family_is_suspendable { data.to_vec() }); - let (mut cfb8, _) = $cfb8::::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut cfb8, _) = $cfb8::::do_encrypt_init(&key::<$cfb8_key, $k>()).unwrap(); cfb8.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); round_trip::<{ $cfb8::::SUSPENDED_STATE_LEN }, $k, _>(cfb8, |mut e| { let mut data = [0x22u8; 9]; @@ -78,7 +96,7 @@ macro_rules! every_alias_family_is_suspendable { data.to_vec() }); - let (mut ctr, _) = $ctr::::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut ctr, _) = $ctr::::do_encrypt_init(&key::<$ctr_key, $k>()).unwrap(); ctr.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); round_trip::<{ $ctr::::SUSPENDED_STATE_LEN }, $k, _>(ctr, |mut e| { let mut data = [0x22u8; 25]; @@ -86,7 +104,7 @@ macro_rules! every_alias_family_is_suspendable { data.to_vec() }); - let (mut gcm, _) = $gcm::::do_encrypt_init(&key::<$k>()).unwrap(); + let (mut gcm, _) = $gcm::::do_encrypt_init(&key::<$gcm_key, $k>()).unwrap(); gcm.do_update_aad(b"header").unwrap(); gcm.do_encrypt_out(&[0x11u8; 7], &mut [0u8; 7]).unwrap(); round_trip::<{ $gcm::::SUSPENDED_STATE_LEN }, $k, _>(gcm, |mut e| { @@ -97,7 +115,7 @@ macro_rules! every_alias_family_is_suspendable { }); type CcmEnc = $ccm; - let mut ccm = CcmEnc::new(&key::<$k>(), &[0x24u8; 12], b"header", 32).unwrap(); + let mut ccm = CcmEnc::new(&key::<$ccm_key, $k>(), &[0x24u8; 12], b"header", 32).unwrap(); ccm.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); round_trip::<{ CcmEnc::SUSPENDED_STATE_LEN }, $k, _>(ccm, |mut e| { let mut data = [0x22u8; 25]; @@ -110,23 +128,41 @@ macro_rules! every_alias_family_is_suspendable { #[test] fn every_aes128_alias_family_is_suspendable() { every_alias_family_is_suspendable!( - 16, AES_CBC_128, AES_ECB_128, AES_CFB_128, AES_CFB8_128, AES_CTR_128, AES_GCM_128, - AES_CCM_128_Packet + 16, + AES_CBC_128 / AES_CBC_128_Key, + AES_ECB_128 / AES_ECB_128_Key, + AES_CFB_128 / AES_CFB_128_Key, + AES_CFB8_128 / AES_CFB8_128_Key, + AES_CTR_128 / AES_CTR_128_Key, + AES_GCM_128 / AES_GCM_128_Key, + AES_CCM_128_Packet / AES_CCM_128_Key, ); } #[test] fn every_aes192_alias_family_is_suspendable() { every_alias_family_is_suspendable!( - 24, AES_CBC_192, AES_ECB_192, AES_CFB_192, AES_CFB8_192, AES_CTR_192, AES_GCM_192, - AES_CCM_192_Packet + 24, + AES_CBC_192 / AES_CBC_192_Key, + AES_ECB_192 / AES_ECB_192_Key, + AES_CFB_192 / AES_CFB_192_Key, + AES_CFB8_192 / AES_CFB8_192_Key, + AES_CTR_192 / AES_CTR_192_Key, + AES_GCM_192 / AES_GCM_192_Key, + AES_CCM_192_Packet / AES_CCM_192_Key, ); } #[test] fn every_aes256_alias_family_is_suspendable() { every_alias_family_is_suspendable!( - 32, AES_CBC_256, AES_ECB_256, AES_CFB_256, AES_CFB8_256, AES_CTR_256, AES_GCM_256, - AES_CCM_256_Packet + 32, + AES_CBC_256 / AES_CBC_256_Key, + AES_ECB_256 / AES_ECB_256_Key, + AES_CFB_256 / AES_CFB_256_Key, + AES_CFB8_256 / AES_CFB8_256_Key, + AES_CTR_256 / AES_CTR_256_Key, + AES_GCM_256 / AES_GCM_256_Key, + AES_CCM_256_Packet / AES_CCM_256_Key, ); } diff --git a/crypto/ascon/benches/ascon_benches.rs b/crypto/ascon/benches/ascon_benches.rs index 07aad22cf..5b1e77faa 100644 --- a/crypto/ascon/benches/ascon_benches.rs +++ b/crypto/ascon/benches/ascon_benches.rs @@ -2,13 +2,12 @@ use bouncycastle_rng as rng; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; -use bouncycastle_ascon::Ascon_AEAD128; use bouncycastle_ascon::AsconCXof128; use bouncycastle_ascon::AsconHash256; use bouncycastle_ascon::AsconXof128; +use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; use bouncycastle_cipher::Encrypting; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherEncryptor, Hash, RNG, XOF}; +use bouncycastle_core::traits::{AEADCipherEncryptor, Hash, RNG, SymmetricCipherKey, XOF}; const DATA_LEN: usize = 16 * 1024; @@ -19,8 +18,7 @@ fn random_data(len: usize) -> Vec { } fn bench_aead128_encrypt(c: &mut Criterion) { - let key = - KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); + let key = Ascon_AEAD128_Key::from_bytes(&[0x42u8; 16]).unwrap(); let nonce = [0x24u8; 16]; let data = random_data(DATA_LEN); let mut out = vec![0u8; DATA_LEN + 16]; diff --git a/crypto/ascon/src/ascon_aead128.rs b/crypto/ascon/src/ascon_aead128.rs index 2e45c78ef..42b82af67 100644 --- a/crypto/ascon/src/ascon_aead128.rs +++ b/crypto/ascon/src/ascon_aead128.rs @@ -19,14 +19,16 @@ use core::fmt::{self, Debug, Display, Formatter}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::errors::{KeyMaterialError, SuspendableError, SymmetricCipherError}; +use bouncycastle_core::errors::{ + KeyMaterialError, RNGError, SuspendableError, SymmetricCipherError, +}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, SuspendableKeyed, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; -use bouncycastle_rng::HashDRBG_SHA512; +use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; use bouncycastle_utils::ct::ct_eq_bytes; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; @@ -86,16 +88,14 @@ impl Phase { /// associated data and the tag inline: /// /// ``` -/// use bouncycastle_ascon::Ascon_AEAD128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; /// use bouncycastle_cipher::{Decrypting, Encrypting}; -/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; /// /// type Enc = Ascon_AEAD128; /// type Dec = Ascon_AEAD128; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .expect("a 16-byte symmetric cipher key"); +/// let key = Ascon_AEAD128_Key::new_from_os().expect("a fresh key"); /// /// let message = b"hello"; /// let mut ciphertext = [0u8; 5 + 16]; // Enc::encrypt_out_len(5): ciphertext || tag @@ -121,11 +121,10 @@ impl Phase { /// so they need no [`Secret`] wrapper; the encrypting side carries the same fields unused. /// /// ```compile_fail -/// # use bouncycastle_ascon::Ascon_AEAD128; +/// # use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; /// # use bouncycastle_cipher::Decrypting; -/// # use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// # use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; -/// # let key = KeyMaterial::<16>::from_bytes_as_type(&[7; 16], KeyType::SymmetricCipherKey).unwrap(); +/// # use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; +/// # let key = Ascon_AEAD128_Key::from_bytes(&[7; 16]).unwrap(); /// let mut dec = Ascon_AEAD128::::do_decrypt_init(&key, &[0u8; 16]).unwrap(); /// dec.do_encrypt_out(&[0u8; 4], &mut [0u8; 4]); // no such method on the decrypting type /// ``` @@ -177,37 +176,18 @@ fn fresh_nonce() -> Result<[u8; NONCE_LEN], SymmetricCipherError> { } impl Ascon_AEAD128 { - /// Validate a [`KeyMaterial`] for use with Ascon-AEAD128 and return its key words. - /// The key must be tagged as a [`KeyType::SymmetricCipherKey`] and carry at least the - /// algorithm's 128-bit security strength (SP 800-232 R1/R2). - fn checked_key(key: &KeyMaterial) -> Result<[u64; 2], SymmetricCipherError> { - if key.key_type() != KeyType::SymmetricCipherKey { - return Err(KeyMaterialError::InvalidKeyType( - "Ascon-AEAD128 requires a SymmetricCipherKey", - ) - .into()); - } - if key.security_strength() < SecurityStrength::_128bit { - return Err(KeyMaterialError::SecurityStrength( - "Ascon-AEAD128 requires a key with at least 128-bit security strength", - ) - .into()); - } - let bytes = key.ref_to_bytes(); - if bytes.len() != KEY_LEN { - return Err(KeyMaterialError::InvalidLength.into()); - } - Ok([load_u64_le(bytes, 0), load_u64_le(bytes, 8)]) - } - - /// SP 800-232 Sec 4.1.1 / 4.1.2 step 1, the same in both directions: validates `key` as a - /// [`KeyType::SymmetricCipherKey`] of at least 128-bit strength and initialises the state - /// under `nonce`. The direction is whatever type this is called on. - fn new( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { - let key_words = Self::checked_key(key)?; + /// The key as two words. The key policy (SP 800-232 R1/R2) is [`Ascon_AEAD128_Key`]'s, checked + /// when the key is made, so there is nothing left to fail here. + fn key_words(key: &Ascon_AEAD128_Key) -> [u64; 2] { + let bytes = key.get_key().ref_to_bytes(); + [load_u64_le(bytes, 0), load_u64_le(bytes, 8)] + } + + /// SP 800-232 Sec 4.1.1 / 4.1.2 step 1, the same in both directions: initialises the state + /// under `key` and `nonce`. The key policy is [`Ascon_AEAD128_Key`]'s, checked when the key is + /// made. The direction is whatever type this is called on. + fn new(key: &Ascon_AEAD128_Key, nonce: &[u8; NONCE_LEN]) -> Result { + let key_words = Self::key_words(key); let mut key_secret: Secret<[u64; 2]> = Secret::new(); *key_secret = key_words; @@ -441,16 +421,18 @@ impl Algorithm for Ascon_AEAD128 { const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } -impl SymmetricCipherEncryptor for Ascon_AEAD128 { +impl SymmetricCipherEncryptor + for Ascon_AEAD128 +{ fn do_encrypt_init( - key: &KeyMaterial, + key: &Ascon_AEAD128_Key, ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { let nonce = fresh_nonce()?; Ok((Self::new(key, &nonce)?, nonce)) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &Ascon_AEAD128_Key, rng: &mut dyn RNG, ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { let mut nonce = [0u8; NONCE_LEN]; @@ -489,13 +471,15 @@ impl SymmetricCipherEncryptor for Ascon_AEAD128 for Ascon_AEAD128 { +impl AEADCipherEncryptor + for Ascon_AEAD128 +{ fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.absorb_aad(aad) } fn do_encrypt_init_nonce( - key: &KeyMaterial, + key: &Ascon_AEAD128_Key, nonce: &[u8; NONCE_LEN], ) -> Result { Self::new(key, nonce) @@ -511,9 +495,11 @@ impl AEADCipherEncryptor for Ascon_AEAD128 } } -impl SymmetricCipherDecryptor for Ascon_AEAD128 { +impl SymmetricCipherDecryptor + for Ascon_AEAD128 +{ fn do_decrypt_init( - key: &KeyMaterial, + key: &Ascon_AEAD128_Key, nonce: &[u8; NONCE_LEN], ) -> Result { Self::new(key, nonce) @@ -574,7 +560,9 @@ impl SymmetricCipherDecryptor for Ascon_AEAD128 for Ascon_AEAD128 { +impl AEADCipherDecryptor + for Ascon_AEAD128 +{ /// # Errors /// [`SymmetricCipherError::StateError`] if `aad` is non-empty and /// [`SymmetricCipherDecryptor::do_decrypt_out`] has already been called. @@ -645,6 +633,39 @@ impl Ascon_AEAD128 { } } +/// An Ascon-AEAD128 key: a [`KeyType::SymmetricCipherKey`] of at least 128-bit strength +/// (SP 800-232 R1/R2). +#[derive(Clone, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub struct Ascon_AEAD128_Key(KeyMaterial); + +impl SymmetricCipherKey for Ascon_AEAD128_Key { + fn from_keymaterial(key: KeyMaterial) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType( + "Ascon-AEAD128 requires a SymmetricCipherKey", + )); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::SecurityStrength( + "Ascon-AEAD128 requires a key with at least 128-bit security strength", + )); + } + if key.key_len() != KEY_LEN { + return Err(KeyMaterialError::InvalidLength); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} + impl Debug for Ascon_AEAD128 { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Ascon-AEAD128 (key/state masked)") @@ -685,9 +706,8 @@ const ENC_DIR_BITS: u8 = 0; const DEC_DIR_BITS: u8 = 4; impl SuspendableKeyed for Ascon_AEAD128 { - // The 128-bit key must be re-supplied when resuming; it is never part of the serialized state, - // and is re-validated exactly as construction validates it. - type Key = KeyMaterial; + // The 128-bit key must be re-supplied when resuming; it is never part of the serialized state. + type Key = Ascon_AEAD128_Key; fn suspend(self) -> [u8; SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN] { self.suspend_as(ENC_DIR_BITS) @@ -705,7 +725,7 @@ impl SuspendableKeyed for Ascon_AEA /// The core's state, then the held-back bytes and their count; see /// [`SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN`]. impl SuspendableKeyed for Ascon_AEAD128 { - type Key = KeyMaterial; + type Key = Ascon_AEAD128_Key; fn suspend(self) -> [u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN] { let mut out = [0u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN]; @@ -758,7 +778,7 @@ impl Ascon_AEAD128 { /// direction is not `dir_bits` is refused, so a state never resumes in the other direction. fn resume_as( serialized_state: [u8; CIPHER_STATE_LEN], - key: &KeyMaterial, + key: &Ascon_AEAD128_Key, dir_bits: u8, ) -> Result { // infallible: check_lib_ver returns a slice of exactly CIPHER_STATE_LEN - 3 = 43 bytes. @@ -789,7 +809,7 @@ impl Ascon_AEAD128 { return Err(SuspendableError::InvalidData); } - let key_words = Self::checked_key(key).map_err(|_| SuspendableError::InvalidData)?; + let key_words = Self::key_words(key); let mut key_secret = Secret::<[u64; 2]>::new(); *key_secret = key_words; diff --git a/crypto/ascon/src/lib.rs b/crypto/ascon/src/lib.rs index e8fbfa5c5..bb761ecd6 100644 --- a/crypto/ascon/src/lib.rs +++ b/crypto/ascon/src/lib.rs @@ -45,12 +45,11 @@ //! //! Authenticated encryption (one-shot, under a nonce the caller manages): //! ``` -//! use bouncycastle_ascon::Ascon_AEAD128; +//! use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherKey}; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = Ascon_AEAD128_Key::new_from_os().expect("a fresh key"); //! let nonce = [1u8; 16]; // MUST be unique per encryption under a given key //! let ad = b"associated data"; //! let plaintext = b"secret message"; @@ -69,14 +68,14 @@ //! bytes it has seen, in case they are an inline tag, so `do_decrypt_final_detached_out` is //! where they come out: //! ``` -//! use bouncycastle_ascon::Ascon_AEAD128; +//! use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, //! }; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = Ascon_AEAD128_Key::new_from_os().expect("a fresh key"); //! //! let plaintext = b"secret message!!"; //! let (mut enc, nonce) = Ascon_AEAD128::::do_encrypt_init(&key).unwrap(); @@ -102,14 +101,14 @@ //! [`bouncycastle_core::traits::AEADCipherEncryptor::encrypt_with_aad_out`] / //! [`bouncycastle_core::traits::AEADCipherDecryptor::decrypt_with_aad_out`] are the one-shots with AAD: //! ``` -//! use bouncycastle_ascon::Ascon_AEAD128; +//! use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, //! }; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = Ascon_AEAD128_Key::new_from_os().expect("a fresh key"); //! let plaintext = b"secret message!!"; //! //! // No AAD: just a symmetric cipher. @@ -217,8 +216,9 @@ mod ascon_hash256; mod ascon_xof128; pub use ascon_aead128::{ - Ascon_AEAD128, KEY_LEN, NONCE_LEN, SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN, - SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN, TAG_LEN, + Ascon_AEAD128, Ascon_AEAD128_Key, KEY_LEN, NONCE_LEN, + SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN, SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN, + TAG_LEN, }; pub use ascon_cxof128::{AsconCXof128, AsconCXof128Squeezer, SUSPENDED_ASCON_CXOF128_STATE_LEN}; pub use ascon_hash256::{AsconHash256, SUSPENDED_ASCON_HASH256_STATE_LEN}; diff --git a/crypto/ascon/tests/aead128_tests.rs b/crypto/ascon/tests/aead128_tests.rs index 1fbb42d1e..a68bf3826 100644 --- a/crypto/ascon/tests/aead128_tests.rs +++ b/crypto/ascon/tests/aead128_tests.rs @@ -9,7 +9,7 @@ //! the detached-tag (`*_detached`) and the inline `ciphertext || tag` layouts -- the latter also //! through the `SymmetricCipherEncryptor`/`SymmetricCipherDecryptor` traits they extend. -use bouncycastle_ascon::Ascon_AEAD128; +use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::do_hazardous_operations; @@ -17,6 +17,7 @@ use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherKey, }; use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; use bouncycastle_hex as hex; @@ -72,14 +73,14 @@ fn pattern(len: usize) -> Vec { /// all-zero key (Count=1), which `KeyMaterial::from_bytes_as_type` would otherwise tag /// `KeyType::Zeroized` / `SecurityStrength::None`; force the type/strength the way a caller who /// knows the provenance of the key would (see `cli/src/helpers.rs::parse_seed`). -fn key_material(key: &[u8; 16]) -> KeyMaterial<16> { +fn key_material(key: &[u8; 16]) -> Ascon_AEAD128_Key { let mut km = KeyMaterial::<16>::from_bytes_as_type(key, KeyType::SymmetricCipherKey).unwrap(); do_hazardous_operations(&mut km, |k| { k.set_key_type(KeyType::SymmetricCipherKey)?; k.set_security_strength(SecurityStrength::_128bit) }) .unwrap(); - km + Ascon_AEAD128_Key::from_keymaterial(km).unwrap() } fn enc_oneshot(key: &[u8; 16], nonce: &[u8; 16], ad: &[u8], pt: &[u8]) -> Vec { @@ -471,7 +472,7 @@ fn aead_debug_display_are_masked() { #[test] fn aead128_encryptor_decryptor_trait_framework() { TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::<16, 16, 16, 16, Ascon_AEAD128, Ascon_AEAD128>(); + .test_encryptor_decryptor::<16, 16, 16, 16, Ascon_AEAD128_Key, Ascon_AEAD128, Ascon_AEAD128>(); } /// The two tag layouts must agree byte for byte: `direct_ciphertext || direct_tag`, produced by diff --git a/crypto/ascon/tests/ascon_bc-test-data.rs b/crypto/ascon/tests/ascon_bc-test-data.rs index 8499386cf..1e79577f8 100644 --- a/crypto/ascon/tests/ascon_bc-test-data.rs +++ b/crypto/ascon/tests/ascon_bc-test-data.rs @@ -8,17 +8,17 @@ //! These full sweeps (1025–1089 cases each) complement the small embedded vector sets in the //! per-primitive test files. -use bouncycastle_ascon::Ascon_AEAD128; use bouncycastle_ascon::AsconCXof128; use bouncycastle_ascon::AsconHash256; use bouncycastle_ascon::AsconXof128; +use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Hash, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, XOF, + SymmetricCipherEncryptor, SymmetricCipherKey, XOF, }; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_hex as hex; @@ -88,7 +88,7 @@ fn to_16(bytes: &[u8], what: &str) -> [u8; 16] { /// (Count=1), which `KeyMaterial::from_bytes_as_type` would otherwise tag /// `KeyType::Zeroized` / `SecurityStrength::None`; force the type/strength the way a caller /// who knows the provenance of the key would (see `cli/src/helpers.rs::parse_seed`). -fn key_material(key: &[u8; 16]) -> KeyMaterial<16> { +fn key_material(key: &[u8; 16]) -> Ascon_AEAD128_Key { let mut km = KeyMaterial::<16>::from_bytes_as_type(key, KeyType::SymmetricCipherKey).unwrap(); do_hazardous_operations(&mut km, |k| { @@ -97,7 +97,7 @@ fn key_material(key: &[u8; 16]) -> KeyMaterial<16> { }) .unwrap(); - km + Ascon_AEAD128_Key::from_keymaterial(km).unwrap() } #[test] diff --git a/crypto/ascon/tests/ascon_wycheproof.rs b/crypto/ascon/tests/ascon_wycheproof.rs index cdd82cd6e..d8e7cb9fc 100644 --- a/crypto/ascon/tests/ascon_wycheproof.rs +++ b/crypto/ascon/tests/ascon_wycheproof.rs @@ -9,7 +9,7 @@ //! A `valid` case must encrypt to exactly `ct || tag` and decrypt back to `msg`. An `invalid` case //! must be rejected with `AEADTagCheckFailed`, leaving the output buffer zeroized. -use bouncycastle_ascon::{Ascon_AEAD128, KEY_LEN, NONCE_LEN, TAG_LEN}; +use bouncycastle_ascon::{Ascon_AEAD128, Ascon_AEAD128_Key, KEY_LEN, NONCE_LEN, TAG_LEN}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::do_hazardous_operations; @@ -17,12 +17,13 @@ use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherKey, }; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; /// Wraps the vector's key bytes as a cipher key, promoting them if `KeyMaterial`'s entropy /// heuristic declined to (as `ascon_bc-test-data.rs` does for the NIST KAT keys). -fn cipher_key(bytes: &[u8]) -> KeyMaterial { +fn cipher_key(bytes: &[u8]) -> Ascon_AEAD128_Key { let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey) .expect("a 16-byte key"); do_hazardous_operations(&mut key, |k| { @@ -30,7 +31,7 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { k.set_security_strength(SecurityStrength::_128bit) }) .expect("promoting a wycheproof test key"); - key + Ascon_AEAD128_Key::from_keymaterial(key).expect("a valid key") } #[test] diff --git a/crypto/cipher/src/lib.rs b/crypto/cipher/src/lib.rs index 92b46f49a..168293261 100644 --- a/crypto/cipher/src/lib.rs +++ b/crypto/cipher/src/lib.rs @@ -22,15 +22,15 @@ //! ``` //! use bouncycastle_cipher::modes::Cbc; //! use bouncycastle_cipher::Encrypting; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherEncryptor, SuspendableKeyed}; //! # mod toy { include!("../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! -//! type ToyCbc = Cbc; +//! type ToyCbc = Cbc; //! const STATE_LEN: usize = ToyCbc::SUSPENDED_STATE_LEN; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! let (mut enc, _iv) = ToyCbc::do_encrypt_init(&key).unwrap(); //! let mut first = [[0x11u8; 16]]; //! enc.do_encrypt_blocks_inplace(&mut first).unwrap(); diff --git a/crypto/cipher/src/modes/cbc.rs b/crypto/cipher/src/modes/cbc.rs index 8cddae314..058eeca03 100644 --- a/crypto/cipher/src/modes/cbc.rs +++ b/crypto/cipher/src/modes/cbc.rs @@ -16,23 +16,21 @@ //! //! # Usage Examples //! -//! The direction is part of the type: [`Cbc`](Cbc) implements -//! [`BlockCipherEncryptor`] and nothing else, and [`Cbc`](Cbc) implements +//! The direction is part of the type: [`Cbc`](Cbc) implements +//! [`BlockCipherEncryptor`] and nothing else, and [`Cbc`](Cbc) implements //! [`BlockCipherDecryptor`] and nothing else. //! The IV is generated and returned; there is no API for supplying one. //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::Cbc; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyCbc = Cbc; +//! type ToyCbc = Cbc; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! //! // 48 bytes: three whole blocks. A length that is not a multiple of 16 would not compile. //! let plaintext: [u8; 48] = *b"The quick brown fox jumps over the lazy dog. OK!"; @@ -51,16 +49,14 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::Cbc; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyCbc = Cbc; +//! type ToyCbc = Cbc; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! //! let (mut encryptor, iv) = //! ToyCbc::::do_encrypt_init(&key).expect("encrypt init"); @@ -80,17 +76,16 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; //! use bouncycastle_core::errors::SymmetricCipherError; -//! use bouncycastle_core::traits::BlockCipherEncryptor; +//! use bouncycastle_core::traits::{BlockCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::Cbc; //! use bouncycastle_cipher::Encrypting; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = ToyCipherKey::from_bytes(&[0x42; 16]).unwrap(); //! let mut data = [0u8; 20]; //! assert_eq!( -//! Cbc::::encrypt_inplace(&key, &mut data), +//! Cbc::::encrypt_inplace(&key, &mut data), //! Err(SymmetricCipherError::DataNotBlockAligned) //! ); //! ``` @@ -121,10 +116,10 @@ use crate::modes::iv::random_iv; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SuspendableKeyed, + SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::suspendable_state::{ @@ -135,7 +130,7 @@ use core::marker::PhantomData; /// CBC mode over any [`ElectronicCodeBook`], with the direction encoded in the type. /// /// `Dir` is [`Encrypting`] or [`Decrypting`]. [`BlockCipherEncryptor`] is implemented only for the -/// former and [`BlockCipherDecryptor`] only for the latter, so a `Cbc<_, Encrypting, _, _>` has no +/// former and [`BlockCipherDecryptor`] only for the latter, so a `Cbc<_, Encrypting, ToyCipherKey, _, _>` has no /// decryption methods at all -- using one in the wrong direction is a compile error rather than a /// runtime check. /// @@ -147,19 +142,22 @@ use core::marker::PhantomData; /// a zeroize-on-drop wrapper) and one block of chaining value. The chaining value is an IV or a /// ciphertext block, both of which are public, so it is deliberately not wrapped in a `Secret`. #[derive(Clone)] -pub struct Cbc +pub struct Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, /// `Cj-1`, initialised to the IV. See the module docs on why there is only one field for both. chain: [u8; BLOCK_LEN], _dir: PhantomData, + _key: PhantomData, } -impl Cbc +impl Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header and the chaining /// block. See [`bouncycastle_utils::suspendable_state`]. @@ -240,10 +238,11 @@ where } } -impl Algorithm - for Cbc +impl Algorithm + for Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::CBC_ALG_NAME`]. const ALG_NAME: &'static str = P::CBC_ALG_NAME; @@ -251,27 +250,27 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - BlockCipherEncryptor for Cbc +impl + BlockCipherEncryptor + for Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins an encryption flow, generating the IV from the library's default OS-backed DRBG. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } /// As [`BlockCipherEncryptor::do_encrypt_init`], but takes the IV from the provided RNG. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; let iv = random_iv::(rng)?; - Ok((Self { perm, chain: iv, _dir: PhantomData }, iv)) + Ok((Self { perm, chain: iv, _dir: PhantomData, _key: PhantomData }, iv)) } /// Strictly serial: `Cj` is the input to block `j + 1`, so there is no pair path here. See the @@ -287,19 +286,18 @@ where } } -impl - BlockCipherDecryptor for Cbc +impl + BlockCipherDecryptor + for Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins a decryption flow from the IV returned by /// [`BlockCipherEncryptor::do_encrypt_init`]. - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; BLOCK_LEN], - ) -> Result { - let perm = P::new(key)?; - Ok(Self { perm, chain: *init_data, _dir: PhantomData }) + fn do_decrypt_init(key: &K, init_data: &[u8; BLOCK_LEN]) -> Result { + let perm = P::new(key.get_key())?; + Ok(Self { perm, chain: *init_data, _dir: PhantomData, _key: PhantomData }) } /// Walks the input in fours through `decrypt_4blocks`, then pairs through `decrypt_2blocks`, @@ -328,33 +326,35 @@ where /// The suspended state is the chaining block `Cj-1`, in both directions; the permutation is /// rebuilt from the re-supplied key. See [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Cbc +impl SuspendableComponent + for Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = BLOCK_LEN; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { out.copy_from_slice(&self.chain); } fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut chain = [0u8; BLOCK_LEN]; chain.copy_from_slice(state); - Ok(Self { perm, chain, _dir: PhantomData }) + Ok(Self { perm, chain, _dir: PhantomData, _key: PhantomData }) } } /// `N` must be [`Cbc::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cbc +impl SuspendableKeyed + for Cbc where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) diff --git a/crypto/cipher/src/modes/ccm.rs b/crypto/cipher/src/modes/ccm.rs index 6aaada22e..0305f0e91 100644 --- a/crypto/cipher/src/modes/ccm.rs +++ b/crypto/cipher/src/modes/ccm.rs @@ -20,16 +20,15 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_core::errors::SymmetricCipherError; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; //! use bouncycastle_cipher::modes::CcmPacket; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyCcm = CcmPacket; +//! type ToyCcm = CcmPacket; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! //! // Supplied, not generated //! // It is the caller's responsibility that it never repeat under this key. @@ -110,11 +109,11 @@ use crate::modes::iv::random_iv; use crate::stream::StreamCipher; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::{ElectronicCodeBook, KeyStream}; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::ct::ct_eq_bytes; @@ -132,57 +131,58 @@ use crate::{Decrypting, Encrypting}; /// AAD and payload lengths are declared per message, and the nonce is the caller's. The /// trait-implementing pair, [`CcmEncryptor`] / [`CcmDecryptor`], is built on this type. /// -/// `Dir` is [`Encrypting`] or [`Decrypting`], `CcmPacket` has Sec 6.1's methods and -/// nothing else, and `CcmPacket` has Sec 6.2's. +/// `Dir` is [`Encrypting`] or [`Decrypting`], `CcmPacket` has Sec 6.1's methods and +/// nothing else, and `CcmPacket` has Sec 6.2's. /// /// Asking an encryptor to verify a tag does not compile -- `do_decrypt_final` exists only on -/// `CcmPacket`: +/// `CcmPacket`: /// /// A nonce length A.1 does not permit does not compile: /// /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::SymmetricCipherKey; /// use bouncycastle_cipher::modes::CcmPacket; /// use bouncycastle_cipher::Encrypting; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .unwrap(); +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// // n = 6 is not in {7, ..., 13}: it would make q = 9, which A.1 does not allow. -/// let _ = CcmPacket::::new(&key, &[0u8; 6], &[], 0); +/// let _ = CcmPacket::::new(&key, &[0u8; 6], &[], 0); /// ``` /// /// Nor does an odd tag length: /// /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::SymmetricCipherKey; /// use bouncycastle_cipher::modes::CcmPacket; /// use bouncycastle_cipher::Encrypting; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .unwrap(); +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// // t = 15 is not in {4, 6, 8, 10, 12, 14, 16}. -/// let _ = CcmPacket::::new(&key, &[0u8; 12], &[], 0); +/// let _ = CcmPacket::::new(&key, &[0u8; 12], &[], 0); /// ``` #[derive(Clone)] pub struct CcmPacket< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { // The CTR half (Sec 6.1 steps 5-8): the payload keystream `S1 || S2 || ...`, and the key // schedule, which the CBC-MAC half below shares (Sec 5.2). ctr: StreamCipher< - CcmKeyStream, + CcmKeyStream, Dir, + K, KEY_LEN, NONCE_LEN, BLOCK_LEN, @@ -213,16 +213,18 @@ pub struct CcmPacket< impl< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, -> CcmPacket +> CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The spec's `q`: the octet length of the payload-length field `Q`. A.1 requires `n + q = 15`. - const Q_LEN: usize = CcmKeyStream::::Q_LEN; + const Q_LEN: usize = CcmKeyStream::::Q_LEN; /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the CTR state, /// the CBC-MAC chaining block and three counts as `u64`s. See [`bouncycastle_utils::suspendable_state`]. @@ -231,8 +233,9 @@ where /// The CTR half's share of the suspended state. const CTR_STATE_LEN: usize = , + CcmKeyStream, Dir, + K, KEY_LEN, NONCE_LEN, BLOCK_LEN, @@ -294,7 +297,7 @@ where /// [`SymmetricCipherError::GenericError`] if `payload_len` exceeds A.1's `2^8q - 1`; see /// [`CcmPacket`] for the table. pub fn new( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], payload_len: usize, @@ -303,7 +306,7 @@ where // which is the one path every construction goes through; duplicating them here would be // two more `Err` sites that could drift apart from it. `P::new`'s own `KeyType`/strength // checks are the only key validation needed, exactly as for every other mode in this crate. - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; let mut ccm = Self::from_perm_with_lengths(perm, nonce, aad.len(), payload_len)?; // Exactly the length just declared, so nothing is left owed. ccm.absorb_aad(aad); @@ -325,15 +328,14 @@ where /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } - /// # use toy::ToyBlockCipher; - /// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; + /// # use toy::{ToyBlockCipher, ToyCipherKey}; + /// use bouncycastle_core::traits::SymmetricCipherKey; /// use bouncycastle_cipher::modes::CcmPacket; /// use bouncycastle_cipher::Encrypting; /// - /// type ToyCcm = CcmPacket; + /// type ToyCcm = CcmPacket; /// - /// let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) - /// .expect("a 16-byte symmetric cipher key"); + /// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// let nonce = [0x01u8; 12]; /// let header: [&[u8]; 2] = [b"version: 1; ", b"route: a->b"]; /// let mut message = *b"attack at dawn"; @@ -357,12 +359,12 @@ where /// # Errors /// As [`Self::new`]. pub fn new_with_lengths( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, ) -> Result { - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Self::from_perm_with_lengths(perm, nonce, aad_len, payload_len) } @@ -539,7 +541,7 @@ where | ((((TAG_LEN - 2) / 2) as u8) << 3) | ((Self::Q_LEN - 1) as u8); b0[1..1 + NONCE_LEN].copy_from_slice(nonce); - CcmKeyStream::::put_q_field(&mut b0, payload_len); + CcmKeyStream::::put_q_field(&mut b0, payload_len); b0 } @@ -614,7 +616,7 @@ where // rather than a plain local that outlives this function's stack frame unzeroed. let keystream = self.ctr.keystream(); let mut s0: Secret<[u8; BLOCK_LEN]> = Secret::new(); - *s0 = CcmKeyStream::::counter_block( + *s0 = CcmKeyStream::::counter_block( &keystream.ctr_template, 0, ); keystream.perm.encrypt_block(&mut s0); @@ -631,10 +633,17 @@ where /// Sec 6.1, the generation-encryption process. Present only on the encrypting direction, so a /// decryptor cannot be asked to produce a tag. -impl - CcmPacket +impl< + P, + K, + const KEY_LEN: usize, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, +> CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Encrypts `data` in place and authenticates it. /// @@ -684,7 +693,7 @@ where /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, plus /// [`Self::new`]'s errors. pub fn encrypt_detached_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -712,7 +721,7 @@ where /// # Errors /// As [`Self::encrypt_detached_out`]. pub fn encrypt_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -732,10 +741,17 @@ where /// Sec 6.2, the decryption-verification process. Present only on the decrypting direction, so an /// encryptor cannot be asked to verify a tag. -impl - CcmPacket +impl< + P, + K, + const KEY_LEN: usize, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, +> CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Decrypts `data` in place and authenticates the recovered plaintext. /// @@ -797,7 +813,7 @@ where /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, plus /// [`Self::new`]'s errors. pub fn decrypt_detached_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -838,7 +854,7 @@ where /// [`CcmDecryptor::decrypt_with_aad_out`](AEADCipherDecryptor::decrypt_with_aad_out) -- agrees /// on the same input. Otherwise as [`Self::decrypt_detached_out`]. pub fn decrypt_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -855,13 +871,15 @@ where impl< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, -> Algorithm for CcmPacket +> Algorithm for CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::CCM_ALG_NAME`]. const ALG_NAME: &'static str = P::CCM_ALG_NAME; @@ -876,9 +894,10 @@ where /// through [`CcmPacket`]. It shares its key schedule with the CBC-MAC half, which reaches it through /// [`StreamCipher::keystream`]. #[derive(Clone)] -struct CcmKeyStream +struct CcmKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, // `Ctr_i` with its counter field zeroed (A.3, Table 3): the flags octet and the nonce, which @@ -888,12 +907,15 @@ where // The index `j` of the next keystream block. Starts at 1: step 7 sets `S = S1 || ... || Sm`, // and `S0` is reserved for the tag. next_ctr: u64, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl - CcmKeyStream +impl + CcmKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The spec's `q`: the octet length of the payload-length field `Q`, which is also the width /// of the counter field. A.1 requires `n + q = 15`. @@ -913,7 +935,7 @@ where let mut ctr_template = [0u8; BLOCK_LEN]; ctr_template[0] = (Self::Q_LEN - 1) as u8; ctr_template[1..1 + NONCE_LEN].copy_from_slice(nonce); - Self { perm, ctr_template, next_ctr: 1 } + Self { perm, ctr_template, next_ctr: 1, _key: PhantomData } } /// The nonce `N`, read back out of the counter template: A.3 Table 3 puts it in octets @@ -949,25 +971,25 @@ where } } -impl Algorithm - for CcmKeyStream +impl Algorithm + for CcmKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - KeyStream for CcmKeyStream +impl + KeyStream + for CcmKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - fn new( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { - Ok(Self::from_perm(P::new(key)?, nonce)) + fn new(key: &K, nonce: &[u8; NONCE_LEN]) -> Result { + Ok(Self::from_perm(P::new(key.get_key())?, nonce)) } /// Every counter value from `next_ctr` to `2^8q - 1`. Never the binding limit in practice: @@ -1006,6 +1028,7 @@ where struct CcmAdapter< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, @@ -1013,9 +1036,10 @@ struct CcmAdapter< const AAD_LEN: usize, const DATA_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - ccm: CcmPacket, + ccm: CcmPacket, // Associated data is authenticated but not encrypted, and travels in the clear, so it is not // secret and is not wrapped. aad: [u8; AAD_LEN], @@ -1027,15 +1051,17 @@ struct CcmAdapter< impl< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> CcmAdapter +> CcmAdapter where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The compile-time check for the trait adapters, run from both [`CcmEncryptor`]'s and /// [`CcmDecryptor`]'s constructors and from their one-shots, which never build an adapter, so @@ -1051,7 +1077,7 @@ where // known at compile time, so it is a compile error rather than `CcmPacket::new`'s `Err`. assert!( DATA_LEN as u64 - <= CcmPacket::::MAX_PAYLOAD_LEN, + <= CcmPacket::::MAX_PAYLOAD_LEN, "CCM: DATA_LEN exceeds the payload limit 2^8q - 1 that NONCE_LEN implies (A.1)" ); } @@ -1137,17 +1163,18 @@ where /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -/// use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::{ +/// AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +/// SymmetricCipherKey, +/// }; /// use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor}; /// /// // Frames of exactly 40 payload bytes, with up to 16 bytes of header. -/// type Enc = CcmEncryptor; -/// type Dec = CcmDecryptor; +/// type Enc = CcmEncryptor; +/// type Dec = CcmDecryptor; /// -/// let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .expect("a 16-byte symmetric cipher key"); +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// let header = b"frame 7"; /// let frame = [0x5Au8; 40]; /// @@ -1186,16 +1213,15 @@ where /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::AEADCipherEncryptor; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::{AEADCipherEncryptor, SymmetricCipherKey}; /// use bouncycastle_cipher::modes::CcmEncryptor; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let key = ToyCipherKey::from_bytes(&[0x42; 16]).unwrap(); /// // n = 8 is permitted by A.1; the caller's counter supplies it. /// let counter = 7u64.to_be_bytes(); /// let mut sealed = [0u8; 256 + 16]; -/// let n = CcmEncryptor::::encrypt_with_aad_nonce_out( +/// let n = CcmEncryptor::::encrypt_with_aad_nonce_out( /// &key, &counter, b"hdr", &[0x5A; 256], &mut sealed, /// ).unwrap(); /// assert_eq!(n, 256 + 16); @@ -1205,28 +1231,26 @@ where /// /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; /// use bouncycastle_cipher::modes::CcmEncryptor; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// // n = 8 is permitted by A.1, but too short for a random draw. -/// let _ = CcmEncryptor::::do_encrypt_init(&key); +/// let _ = CcmEncryptor::::do_encrypt_init(&key); /// ``` /// /// Nor does a `DATA_LEN` that `B0` could not carry under this `NONCE_LEN` (A.1's `p < 2^8q`): /// /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; /// use bouncycastle_cipher::modes::CcmEncryptor; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); /// // n = 13 leaves q = 2, so the payload is at most 65535 bytes. -/// let _ = CcmEncryptor::::do_encrypt_init(&key); +/// let _ = CcmEncryptor::::do_encrypt_init(&key); /// ``` /// /// # Memory @@ -1237,27 +1261,31 @@ where #[derive(Clone)] pub struct CcmEncryptor< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, ->(CcmAdapter) +>(CcmAdapter) where - P: ElectronicCodeBook + ModeNames; + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames; impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> Algorithm for CcmEncryptor +> Algorithm for CcmEncryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; @@ -1265,15 +1293,17 @@ where impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> CcmEncryptor +> CcmEncryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Every final comes here: Sec 6.1 steps 4 and 8, the tag. The header goes in first if no /// payload call put it there, which is the `DATA_LEN = 0` message. @@ -1305,26 +1335,26 @@ where impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> SymmetricCipherEncryptor - for CcmEncryptor +> SymmetricCipherEncryptor + for CcmEncryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { Self::check_random_nonce(); @@ -1332,7 +1362,7 @@ where // and every other mode in this crate; `random_iv` is CBC/CFB's same OS-backed draw -- // Sec 5.3 asks only for uniqueness, not CBC/CFB's unpredictability, but a CSPRNG draw is // the only way to be unique without state `do_encrypt_init` does not have. - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; let nonce = random_iv::(rng)?; Ok((Self(CcmAdapter::new(perm, &nonce)), nonce)) } @@ -1395,11 +1425,11 @@ where /// The AEAD one-shot with no AAD; see /// [`encrypt_with_aad_out`](AEADCipherEncryptor::encrypt_with_aad_out). fn encrypt_out( - key: &KeyMaterial, + key: &K, plaintext: &[u8], ciphertext: &mut [u8], ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - >::encrypt_with_aad_out( + >::encrypt_with_aad_out( key, &[], plaintext, @@ -1410,12 +1440,12 @@ where /// The AEAD one-shot with no AAD; see /// [`encrypt_with_aad_rng_out`](AEADCipherEncryptor::encrypt_with_aad_rng_out). fn encrypt_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, plaintext: &[u8], ciphertext: &mut [u8], ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - >::encrypt_with_aad_rng_out( + >::encrypt_with_aad_rng_out( key, rng, &[], plaintext, ciphertext, ) } @@ -1425,16 +1455,18 @@ where /// final flushes nothing and returns only the tag. impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> AEADCipherEncryptor - for CcmEncryptor +> AEADCipherEncryptor + for CcmEncryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Holds back `aad` until the payload begins; see `CcmAdapter`. /// @@ -1450,10 +1482,10 @@ where /// nonce in place of the draw, which is Sec 5.3's own model -- "the nonce is not required to /// be random" -- and takes any A.1 nonce length, since nothing is drawn. fn do_encrypt_init_nonce( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], ) -> Result { - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Ok(Self(CcmAdapter::new(perm, nonce))) } @@ -1463,14 +1495,14 @@ where /// As [`encrypt_detached_rng_out`](AEADCipherEncryptor::encrypt_detached_rng_out), less the /// RNG's. fn encrypt_detached_nonce_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { - CcmAdapter::::check_shape(); - CcmPacket::::encrypt_detached_out( + CcmAdapter::::check_shape(); + CcmPacket::::encrypt_detached_out( key, nonce, aad, plaintext, ciphertext, ) } @@ -1481,14 +1513,14 @@ where /// As [`encrypt_with_aad_rng_out`](AEADCipherEncryptor::encrypt_with_aad_rng_out), less the /// RNG's. fn encrypt_with_aad_nonce_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], ) -> Result { - CcmAdapter::::check_shape(); - CcmPacket::::encrypt_out( + CcmAdapter::::check_shape(); + CcmPacket::::encrypt_out( key, nonce, aad, plaintext, ciphertext, ) } @@ -1509,7 +1541,7 @@ where /// [`do_encrypt_init`](SymmetricCipherEncryptor::do_encrypt_init) draws it; see /// [`encrypt_detached_rng_out`](AEADCipherEncryptor::encrypt_detached_rng_out). fn encrypt_detached_out( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], @@ -1526,7 +1558,7 @@ where /// `plaintext`, checked before anything is drawn from `rng`; plus [`CcmPacket::new`]'s errors and /// the RNG's. fn encrypt_detached_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, aad: &[u8], plaintext: &[u8], @@ -1537,10 +1569,10 @@ where return Err(SymmetricCipherError::OutputBufferTooSmall(plaintext.len())); } Self::check_random_nonce(); - CcmAdapter::::check_shape(); + CcmAdapter::::check_shape(); let nonce = random_iv::(rng)?; let (written, tag) = - CcmPacket::::encrypt_detached_out( + CcmPacket::::encrypt_detached_out( key, &nonce, aad, plaintext, ciphertext, )?; Ok((nonce, written, tag)) @@ -1549,7 +1581,7 @@ where /// [`CcmPacket::encrypt_out`] under a nonce drawn from the default DRBG; see /// [`encrypt_with_aad_rng_out`](AEADCipherEncryptor::encrypt_with_aad_rng_out). fn encrypt_with_aad_out( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], @@ -1565,7 +1597,7 @@ where /// As [`encrypt_detached_rng_out`](AEADCipherEncryptor::encrypt_detached_rng_out), with /// `ciphertext` needing `plaintext.len() + TAG_LEN` bytes. fn encrypt_with_aad_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, aad: &[u8], plaintext: &[u8], @@ -1577,10 +1609,10 @@ where return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); } Self::check_random_nonce(); - CcmAdapter::::check_shape(); + CcmAdapter::::check_shape(); let nonce = random_iv::(rng)?; let written = - CcmPacket::::encrypt_out( + CcmPacket::::encrypt_out( key, &nonce, aad, plaintext, ciphertext, )?; Ok((nonce, written)) @@ -1612,6 +1644,7 @@ where #[derive(Clone)] pub struct CcmDecryptor< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, @@ -1619,9 +1652,10 @@ pub struct CcmDecryptor< const AAD_LEN: usize, const DATA_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - inner: CcmAdapter, + inner: CcmAdapter, // The bytes past the `DATA_LEN`th, as they arrive: an inline tag, if the final says the // layout is inline, and excess ciphertext if it says detached. Public either way (the tag // travels in the clear), so not wrapped. @@ -1631,15 +1665,17 @@ pub struct CcmDecryptor< impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> Algorithm for CcmDecryptor +> Algorithm for CcmDecryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; @@ -1647,15 +1683,17 @@ where impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> CcmDecryptor +> CcmDecryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Every final comes here once it has settled which bytes are the tag: Sec 6.2 step 10, /// through [`CcmPacket::do_decrypt_final`]. The header goes in first if no payload call put it @@ -1673,23 +1711,22 @@ where impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> SymmetricCipherDecryptor - for CcmDecryptor +> SymmetricCipherDecryptor + for CcmDecryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - fn do_decrypt_init( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { + fn do_decrypt_init(key: &K, nonce: &[u8; NONCE_LEN]) -> Result { // `P::new`'s own checks are the only key validation needed; see the encryptor. - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Ok(Self { inner: CcmAdapter::new(perm, nonce), tag: [0u8; TAG_LEN], tag_len: 0 }) } @@ -1769,12 +1806,12 @@ where /// The AEAD one-shot with no AAD; see /// [`decrypt_with_aad_out`](AEADCipherDecryptor::decrypt_with_aad_out). fn decrypt_out( - key: &KeyMaterial, + key: &K, init_data: &[u8; NONCE_LEN], ciphertext: &[u8], plaintext: &mut [u8], ) -> Result { - >::decrypt_with_aad_out( + >::decrypt_with_aad_out( key, init_data, &[], @@ -1788,16 +1825,18 @@ where /// AAD of any length; see [`CcmEncryptor`]. impl< P, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> AEADCipherDecryptor - for CcmDecryptor +> AEADCipherDecryptor + for CcmDecryptor where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// As [`CcmEncryptor::do_update_aad`](AEADCipherEncryptor::do_update_aad); the concatenation /// must match the encryptor's byte for byte or the tag check fails. @@ -1835,7 +1874,7 @@ where /// `ciphertext`; [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify; /// plus [`CcmPacket::new`]'s errors. fn decrypt_detached_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -1846,8 +1885,8 @@ where if plaintext.len() < ciphertext.len() { return Err(SymmetricCipherError::OutputBufferTooSmall(ciphertext.len())); } - CcmAdapter::::check_shape(); - CcmPacket::::decrypt_detached_out( + CcmAdapter::::check_shape(); + CcmPacket::::decrypt_detached_out( key, nonce, aad, ciphertext, tag, plaintext, ) } @@ -1860,7 +1899,7 @@ where /// needing [`decrypt_out_len`](SymmetricCipherDecryptor::decrypt_out_len) bytes, and /// [`SymmetricCipherError::DecryptionFailed`] for a `C` shorter than the tag it must end with. fn decrypt_with_aad_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -1871,8 +1910,8 @@ where if plaintext.len() < needed { return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); } - CcmAdapter::::check_shape(); - CcmPacket::::decrypt_out( + CcmAdapter::::check_shape(); + CcmPacket::::decrypt_out( key, nonce, aad, ciphertext, plaintext, ) } @@ -1884,16 +1923,18 @@ where impl< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, -> SuspendableComponent for CcmPacket +> SuspendableComponent for CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = Self::CTR_STATE_LEN + BLOCK_LEN + 8 + 8 + 8; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { let (ctr, rest) = out.split_at_mut(Self::CTR_STATE_LEN); @@ -1930,16 +1971,18 @@ where impl< P, Dir, + K, const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const N: usize, -> SuspendableKeyed for CcmPacket +> SuspendableKeyed for CcmPacket where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) @@ -1953,13 +1996,14 @@ where /// The suspended state is the counter template and the next counter index; the permutation is /// rebuilt from the re-supplied key. Crate-private like the type, reachable only through /// [`CcmPacket`]'s state. -impl SuspendableComponent - for CcmKeyStream +impl + SuspendableComponent for CcmKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = BLOCK_LEN + 8; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); @@ -1969,7 +2013,7 @@ where } fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut r = Cursor::new(state); let ctr_template = r.array::(); // A.3: the flags octet is `[q-1]_3` and nothing else, and the counter field is zero in @@ -1982,7 +2026,7 @@ where return Err(SuspendableError::InvalidData); } debug_assert!(r.is_done()); - Ok(Self { perm, ctr_template, next_ctr }) + Ok(Self { perm, ctr_template, next_ctr, _key: PhantomData }) } } @@ -1997,7 +2041,12 @@ mod tests { //! the AAD length encoding against the document rather than against this implementation. use super::*; - use bouncycastle_core::key_material::KeyType; + use bouncycastle_core::key_material::KeyMaterial; + use toy::{ToyBlockCipher, ToyCipherKey}; + + mod toy { + include!("../../tests/common/toy_block_cipher.rs"); + } /// A stand-in permutation: the identity. `B0` and `Ctr_i` are formatted *before* any cipher /// call, so the identity is enough to read them back out of the state, and it keeps these @@ -2019,7 +2068,7 @@ mod tests { const GCM_ALG_NAME: &'static str = "identity_GCM"; } - impl ElectronicCodeBook<16, 16> for Identity { + impl ElectronicCodeBook for Identity { fn new(_key: &KeyMaterial<16>) -> Result { Ok(Identity) } @@ -2031,14 +2080,11 @@ mod tests { fn decrypt_4blocks(&self, _blocks: &mut [[u8; 16]; 4]) {} } - fn key() -> KeyMaterial<16> { - KeyMaterial::<16>::from_bytes_as_type( - &[ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, - 0x4e, 0x4f, - ], - KeyType::SymmetricCipherKey, - ) + fn key() -> ToyCipherKey { + ToyCipherKey::from_bytes(&[ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, + ]) .expect("Appendix C's 128-bit key") } @@ -2054,7 +2100,9 @@ mod tests { fn c1_b0_matches_the_spec() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; assert_eq!( - CcmPacket::::format_b0(&nonce, true, 4), + CcmPacket::::format_b0( + &nonce, true, 4 + ), [0x4f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0, 0, 0, 0, 0, 0, 0, 4], "C.1 B0: flags 0x4f = Adata 1 | [(4-2)/2]_3 = 001 | [8-1]_3 = 111, then Q = [4]_64" ); @@ -2065,8 +2113,12 @@ mod tests { #[test] fn adata_flag_is_bit_6_of_the_flags_octet() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let with = CcmPacket::::format_b0(&nonce, true, 4); - let without = CcmPacket::::format_b0(&nonce, false, 4); + let with = CcmPacket::::format_b0( + &nonce, true, 4, + ); + let without = CcmPacket::::format_b0( + &nonce, false, 4, + ); assert_eq!(without[0], 0x0f, "a = 0 clears bit 6, leaving the t and q fields alone"); assert_eq!(with[0] ^ without[0], 1 << 6, "Adata is bit 6 and nothing else"); assert_eq!(with[1..], without[1..], "the flag must not disturb N or Q"); @@ -2080,11 +2132,18 @@ mod tests { #[test] fn the_constructor_absorbs_b0() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let ccm = - CcmPacket::::new(&key(), &nonce, &[], 4).unwrap(); + let ccm = CcmPacket::::new( + &key(), + &nonce, + &[], + 4, + ) + .unwrap(); assert_eq!( *ccm.y, - CcmPacket::::format_b0(&nonce, false, 4) + CcmPacket::::format_b0( + &nonce, false, 4 + ) ); assert_eq!(ccm.mac_pos, 0, "a whole block was absorbed, so nothing is part-filled"); } @@ -2098,7 +2157,9 @@ mod tests { fn c4_b0_matches_the_spec() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; assert_eq!( - CcmPacket::::format_b0(&nonce, true, 32), + CcmPacket::::format_b0( + &nonce, true, 32 + ), [ 0x71, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x00, 0x20 @@ -2115,9 +2176,11 @@ mod tests { #[test] fn counter_blocks_match_the_spec() { let nonce_c1 = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let mut ks = CcmKeyStream::::from_perm(Identity, &nonce_c1); + let mut ks = + CcmKeyStream::::from_perm(Identity, &nonce_c1); // `Ctr0` is the template with a zero counter field. - let ctr0 = CcmKeyStream::::counter_block(&ks.ctr_template, 0); + let ctr0 = + CcmKeyStream::::counter_block(&ks.ctr_template, 0); assert_eq!( ctr0, [0x07, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0, 0, 0, 0, 0, 0, 0, 0], @@ -2133,9 +2196,10 @@ mod tests { let nonce_c4 = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; - let ks4 = CcmKeyStream::::from_perm(Identity, &nonce_c4); + let ks4 = + CcmKeyStream::::from_perm(Identity, &nonce_c4); assert_eq!( - CcmKeyStream::::counter_block(&ks4.ctr_template, 0), + CcmKeyStream::::counter_block(&ks4.ctr_template, 0), [ 0x01, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x00, 0x00 @@ -2152,10 +2216,11 @@ mod tests { #[test] fn a_fresh_keystream_has_every_counter_but_ctr0_left() { // n = 13, so q = 2: counters 1 ..= 65535. - let ks = CcmKeyStream::::from_perm(Identity, &[0u8; 13]); + let ks = + CcmKeyStream::::from_perm(Identity, &[0u8; 13]); assert_eq!(ks.remaining_blocks(), 65535); // n = 7, so q = 8: counters 1 ..= 2^64 - 1, which is `u64::MAX` of them. - let ks = CcmKeyStream::::from_perm(Identity, &[0u8; 7]); + let ks = CcmKeyStream::::from_perm(Identity, &[0u8; 7]); assert_eq!(ks.remaining_blocks(), u64::MAX); } @@ -2164,14 +2229,12 @@ mod tests { /// the identity, which ignores the key and so could not pass the key-policy checks. #[test] fn ccm_keystream_conforms_to_the_key_stream_framework() { - mod toy { - include!("../../tests/common/toy_block_cipher.rs"); - } use bouncycastle_core_test_framework::key_stream::TestFrameworkKeyStream; - use toy::ToyBlockCipher; let framework = TestFrameworkKeyStream::new(); - framework.test::<16, 7, 16, CcmKeyStream>(); - framework.test::<16, 13, 16, CcmKeyStream>(); + framework + .test::<16, 7, 16, ToyCipherKey, CcmKeyStream>( + ); + framework.test::<16, 13, 16, ToyCipherKey, CcmKeyStream>(); } /// A.2.2's three AAD length encodings, at and around both boundaries. @@ -2185,7 +2248,7 @@ mod tests { /// which through the public API would need a 4 GiB AAD. #[test] fn aad_length_encoding_matches_a_2_2() { - type Mode = CcmPacket; + type Mode = CcmPacket; // Case 1: 0 < a < 2^16 - 2^8, two octets, `[a]_16`. assert_eq!( @@ -2245,7 +2308,7 @@ mod tests { /// encoding readable back out, which is what pins the ordering rather than just the value. #[test] fn the_constructor_prefixes_the_aad_with_its_length() { - type Mode = CcmPacket; + type Mode = CcmPacket; let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; // 14 zero bytes of AAD: the 2-byte length plus 14 bytes is exactly one 16-byte block, so // there is no padding to reason about. diff --git a/crypto/cipher/src/modes/cfb.rs b/crypto/cipher/src/modes/cfb.rs index a953ef6a2..4511868ee 100644 --- a/crypto/cipher/src/modes/cfb.rs +++ b/crypto/cipher/src/modes/cfb.rs @@ -26,7 +26,7 @@ //! As a stream cipher producing an XOR key stream, the forward (encryption) and reverse (decryption) //! are the same: both directions apply `CIPH_K`. //! -//! So [`Cfb`](Cfb) is implemented over a permutation that impls [`ElectronicCodeBook`], +//! So [`Cfb`](Cfb) is implemented over a permutation that impls [`ElectronicCodeBook`], //! but never calls [`ElectronicCodeBook::decrypt_block`]. A permutation that implements only the //! forward direction would still work here. The two directions differ only in which of the two //! values -- the byte that came in, or the byte that went out -- is the ciphertext to be fed back into @@ -47,8 +47,8 @@ //! //! # Usage Examples //! -//! The direction is part of the type: [`Cfb`](Cfb) implements -//! [`StreamCipherEncryptor`] and nothing else, and [`Cfb`](Cfb) implements +//! The direction is part of the type: [`Cfb`](Cfb) implements +//! [`StreamCipherEncryptor`] and nothing else, and [`Cfb`](Cfb) implements //! [`StreamCipherDecryptor`] and nothing else. //! //! They take a `&mut [u8]` of any length; there is no padding layer and the ciphertext is exactly @@ -56,17 +56,15 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::{Cfb, Cfb8}; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyCfb = Cfb; -//! type ToyCfb8 = Cfb8; +//! type ToyCfb = Cfb; +//! type ToyCfb8 = Cfb8; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! //! // Start with the plaintext. //! let plaintext = b"the quick brown fox!!"; @@ -141,11 +139,10 @@ use crate::modes::iv::random_iv; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::suspendable_state::{ @@ -167,15 +164,16 @@ use crate::modes::cfb8; /// blocks is handled. /// /// `Dir` is [`Encrypting`] or [`Decrypting`]. [`StreamCipherEncryptor`] is implemented only for the -/// former and [`StreamCipherDecryptor`] only for the latter, so a `Cfb<_, Encrypting, _, _>` has no +/// former and [`StreamCipherDecryptor`] only for the latter, so a `Cfb<_, Encrypting, ToyCipherKey, _, _>` has no /// decryption methods at all -- using one in the wrong direction is a compile error rather than a /// runtime check. /// /// The initialization data is one block, so `INIT_DATA_LEN == BLOCK_LEN`. #[derive(Clone)] -pub struct Cfb +pub struct Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, /// `buf[..used]` is the ciphertext of the current segment so far, i.e. the head of `I_{j+1}`; @@ -197,11 +195,14 @@ where /// Bytes of the current segment already processed, `0..=BLOCK_LEN`. used: usize, _dir: PhantomData, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl Cfb +impl Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the segment /// buffer and the `used` count as a `u64`. See [`bouncycastle_utils::suspendable_state`]. @@ -210,7 +211,7 @@ where /// `I1 = IV`, with no segment open: the first byte in either direction will compute `O1`. #[inline] fn start(perm: P, iv: [u8; BLOCK_LEN]) -> Self { - Self { perm, buf: iv, used: BLOCK_LEN, _dir: PhantomData } + Self { perm, buf: iv, used: BLOCK_LEN, _dir: PhantomData, _key: PhantomData } } /// Makes the next keystream byte available: if the current segment is complete, `buf` is the @@ -354,10 +355,11 @@ where } } -impl Algorithm - for Cfb +impl Algorithm + for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::CFB_ALG_NAME`]. const ALG_NAME: &'static str = P::CFB_ALG_NAME; @@ -365,25 +367,24 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor for Cfb +impl + SymmetricCipherEncryptor for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins an encryption flow, generating the IV from the library's default OS-backed DRBG. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } /// As [`SymmetricCipherEncryptor::do_encrypt_init`], but takes the IV from the provided RNG. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; // `I1 = IV`. let iv = random_iv::(rng)?; Ok((Self::start(perm, iv), iv)) @@ -426,10 +427,11 @@ where } } -impl StreamCipherEncryptor - for Cfb +impl + StreamCipherEncryptor for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Encrypts `data`, of any length, in place. /// @@ -451,18 +453,16 @@ where } } -impl - SymmetricCipherDecryptor for Cfb +impl + SymmetricCipherDecryptor for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins a decryption flow from the IV returned by /// [`SymmetricCipherEncryptor::do_encrypt_init`]. - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; BLOCK_LEN], - ) -> Result { - let perm = P::new(key)?; + fn do_decrypt_init(key: &K, init_data: &[u8; BLOCK_LEN]) -> Result { + let perm = P::new(key.get_key())?; // `I1 = IV`, exactly as on the encrypt side. Ok(Self::start(perm, *init_data)) } @@ -504,10 +504,11 @@ where } } -impl StreamCipherDecryptor - for Cfb +impl + StreamCipherDecryptor for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Decrypts `data`, of any length, in place. /// @@ -539,13 +540,14 @@ where /// The suspended state is `buf` and `used` -- the open segment, which is public ciphertext and /// the keystream not yet used against it -- in both directions; the permutation is rebuilt from /// the re-supplied key. See [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Cfb +impl SuspendableComponent + for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = BLOCK_LEN + 8; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); @@ -555,23 +557,24 @@ where } fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut r = Cursor::new(state); let buf = r.array::(); // `used` is `0..=BLOCK_LEN`; anything past the buffer would index out of it. let used = bounded_usize(r.u64(), BLOCK_LEN)?; debug_assert!(r.is_done()); - Ok(Self { perm, buf, used, _dir: PhantomData }) + Ok(Self { perm, buf, used, _dir: PhantomData, _key: PhantomData }) } } /// `N` must be [`Cfb::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cfb +impl SuspendableKeyed + for Cfb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) diff --git a/crypto/cipher/src/modes/cfb8.rs b/crypto/cipher/src/modes/cfb8.rs index 0d4859f6e..f81c603d1 100644 --- a/crypto/cipher/src/modes/cfb8.rs +++ b/crypto/cipher/src/modes/cfb8.rs @@ -57,11 +57,10 @@ use crate::modes::iv::random_iv; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::suspendable_state::{ @@ -97,19 +96,23 @@ use crate::modes::cfb; /// byte and lives only in a local, so no keystream outlives the call that used it. No partial /// segment is stored either, because a segment is one byte. #[derive(Clone)] -pub struct Cfb8 +pub struct Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, /// `Ij`: the IV, then the shift register. See the module docs. chain: [u8; BLOCK_LEN], _dir: PhantomData, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl Cfb8 +impl Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header and the shift /// register. See [`bouncycastle_utils::suspendable_state`]. @@ -169,10 +172,11 @@ where } } -impl Algorithm - for Cfb8 +impl Algorithm + for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::CFB8_ALG_NAME`]. const ALG_NAME: &'static str = P::CFB8_ALG_NAME; @@ -180,28 +184,28 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor for Cfb8 +impl + SymmetricCipherEncryptor + for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins an encryption flow, generating the IV from the library's default OS-backed DRBG. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } /// As [`SymmetricCipherEncryptor::do_encrypt_init`], but takes the IV from the provided RNG. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; // `I1 = IV`. let iv = random_iv::(rng)?; - Ok((Self { perm, chain: iv, _dir: PhantomData }, iv)) + Ok((Self { perm, chain: iv, _dir: PhantomData, _key: PhantomData }, iv)) } /// Every input byte produces exactly one output byte. @@ -241,10 +245,11 @@ where } } -impl StreamCipherEncryptor - for Cfb8 +impl + StreamCipherEncryptor for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Encrypts `data`, of any length, in place: `Cj = Pj XOR MSB_8(CIPH_K(Ij))` for each byte, /// then `Cj` shifts into the register. @@ -261,20 +266,19 @@ where } } -impl - SymmetricCipherDecryptor for Cfb8 +impl + SymmetricCipherDecryptor + for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Begins a decryption flow from the IV returned by /// [`SymmetricCipherEncryptor::do_encrypt_init`]. - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; BLOCK_LEN], - ) -> Result { - let perm = P::new(key)?; + fn do_decrypt_init(key: &K, init_data: &[u8; BLOCK_LEN]) -> Result { + let perm = P::new(key.get_key())?; // `I1 = IV`, exactly as on the encrypt side. - Ok(Self { perm, chain: *init_data, _dir: PhantomData }) + Ok(Self { perm, chain: *init_data, _dir: PhantomData, _key: PhantomData }) } /// Nothing is held back, so every input byte can be released immediately. @@ -314,10 +318,11 @@ where } } -impl StreamCipherDecryptor - for Cfb8 +impl + StreamCipherDecryptor for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Decrypts `data`, of any length, in place: `Pj = Cj XOR MSB_8(CIPH_K(Ij))` for each byte, /// with the *ciphertext* byte -- the one that came in, not the plaintext going out -- shifted @@ -347,33 +352,35 @@ where /// The suspended state is the shift register `Ij`, in both directions; the permutation is /// rebuilt from the re-supplied key. See [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Cfb8 +impl SuspendableComponent + for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = BLOCK_LEN; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { out.copy_from_slice(&self.chain); } fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut chain = [0u8; BLOCK_LEN]; chain.copy_from_slice(state); - Ok(Self { perm, chain, _dir: PhantomData }) + Ok(Self { perm, chain, _dir: PhantomData, _key: PhantomData }) } } /// `N` must be [`Cfb8::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cfb8 +impl SuspendableKeyed + for Cfb8 where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) diff --git a/crypto/cipher/src/modes/ctr.rs b/crypto/cipher/src/modes/ctr.rs index 4c49449c3..d19011268 100644 --- a/crypto/cipher/src/modes/ctr.rs +++ b/crypto/cipher/src/modes/ctr.rs @@ -40,17 +40,15 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::Ctr; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! //! // A 12-byte nonce, leaving a 4-byte counter. -//! type ToyCtr = Ctr; +//! type ToyCtr = Ctr; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::from_bytes(&[0x42; 16]).expect("a 16-byte symmetric cipher key"); //! //! let plaintext = b"the quick brown fox!!"; //! let mut data = *plaintext; @@ -66,18 +64,17 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; //! use bouncycastle_core::traits::{ -//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor +//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::modes::Ctr; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyCtr = Ctr; +//! type ToyCtr = Ctr; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::from_bytes(&[0x42; 16]).expect("a 16-byte symmetric cipher key"); //! let mut data = [0x5Au8; 40]; //! //! let (mut encryptor, nonce) = ToyCtr::::do_encrypt_init(&key).expect("init"); @@ -129,7 +126,7 @@ use bouncycastle_utils::secret::Secret; #[allow(unused_imports)] use bouncycastle_core::errors::SymmetricCipherError; #[allow(unused_imports)] -use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherKey}; // end of imports needed for docs /// CTR mode over any [`ElectronicCodeBook`] permutation function, with the direction encoded in the type: the @@ -149,20 +146,20 @@ use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// # use toy::{ToyBlockCipher, ToyCipherKey}; +/// use bouncycastle_core::traits::{SymmetricCipherEncryptor, SymmetricCipherKey}; /// use bouncycastle_cipher::modes::Ctr; /// use bouncycastle_cipher::Encrypting; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 4-byte counter -/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 1-byte counter +/// let key = ToyCipherKey::new_from_os().expect("a fresh key"); +/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 4-byte counter +/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 1-byte counter /// ``` -pub type Ctr = +pub type Ctr = StreamCipher< - CtrKeyStream, + CtrKeyStream, Dir, + K, KEY_LEN, INIT_DATA_LEN, BLOCK_LEN, @@ -180,13 +177,14 @@ pub type Ctr( +pub(crate) fn apply_counter_blocks( perm: &P, next: &mut u64, counter_block: impl Fn(u64) -> [u8; BLOCK_LEN], blocks: &mut [[u8; BLOCK_LEN]], ) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { let (fours, rest) = blocks.as_chunks_mut::<4>(); let mut ks4: Secret<[[u8; BLOCK_LEN]; 4]> = Secret::new(); @@ -209,7 +207,7 @@ pub(crate) fn apply_counter_blocks( +fn apply_batch( perm: &P, next: &mut u64, counter_block: &impl Fn(u64) -> [u8; BLOCK_LEN], @@ -217,7 +215,8 @@ fn apply_batch( keystream: &mut [[u8; BLOCK_LEN]; N], batch: impl Fn(&P, &mut [[u8; BLOCK_LEN]; N]), ) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { for slot in keystream.iter_mut() { *slot = counter_block(*next); diff --git a/crypto/cipher/src/modes/gcm.rs b/crypto/cipher/src/modes/gcm.rs index b4f582d41..57d39ffc6 100644 --- a/crypto/cipher/src/modes/gcm.rs +++ b/crypto/cipher/src/modes/gcm.rs @@ -24,17 +24,15 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_core::errors::SymmetricCipherError; //! use bouncycastle_cipher::modes::Gcm; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyGcm = Gcm; +//! type ToyGcm = Gcm; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! let aad = b"header, sent in the clear"; //! let plaintext: [u8; 16] = *b"attack at dawn!!"; //! @@ -57,16 +55,14 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::Gcm; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyGcm = Gcm; +//! type ToyGcm = Gcm; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! let aad = b"header, sent in the clear"; //! let plaintext = *b"attack at dawn!!"; //! @@ -86,18 +82,17 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +//! SymmetricCipherKey, //! }; //! use bouncycastle_cipher::modes::Gcm; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyGcm = Gcm; +//! type ToyGcm = Gcm; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! let aad = b"some associated data"; //! let message = b"a message that streams in over more than one call"; //! @@ -170,11 +165,11 @@ use crate::modes::hazmat::ctr_key_stream::CtrKeyStream; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::ct::ct_eq_bytes; @@ -213,13 +208,14 @@ impl Phase { /// [`Encrypting`] / [`Decrypting`]. See the module docs for the two APIs this type exposes and /// [`GCM_NONCE_LEN`] / `TAG_LEN` for what is fixed and what is chosen. #[derive(Clone)] -pub struct Gcm +pub struct Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// `GCTR_K(inc32(J0), .)`: Algorithm 4 step 3 / Algorithm 5 step 4, started at counter 2 (see /// [`Gcm::setup`]). - ctr: Ctr, + ctr: Ctr, /// `GHASH_H` over `A || 0^v || C || 0^u`, Algorithm 4/5 step 5/6. ghash: Ghash, /// `CIPH_K(J0)`, the one-time mask for the tag (step 6's `GCTR_K(J0, S) = S (+) CIPH_K(J0)`, @@ -241,9 +237,10 @@ where _dir: PhantomData, } -impl Gcm +impl Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the CTR state, /// the GHASH state, the two byte counts, the phase, the held-back tail and its length. See @@ -253,7 +250,7 @@ where /// The CTR half's share of the suspended state. const CTR_STATE_LEN: usize = - as SuspendableComponent>::STATE_LEN; + as SuspendableComponent>::STATE_LEN; /// The compile-time shape check: `TAG_LEN` must be one of Sec 5.2.1.2's five recommended tag /// lengths in bytes (96, 104, 112, 120, 128 bits -- Appendix C's 32- and 64-bit tags are a @@ -370,17 +367,20 @@ where } } -impl Algorithm for Gcm +impl Algorithm + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const ALG_NAME: &'static str = P::GCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl Gcm +impl Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Encrypts `data` in place (GCTR, Algorithm 4 step 3) and absorbs the resulting ciphertext /// into GHASH (step 5). Nothing is held back. @@ -408,25 +408,24 @@ where } } -impl - SymmetricCipherEncryptor - for Gcm +impl + SymmetricCipherEncryptor + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { Self::check_shape(); - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; let nonce = crate::modes::iv::random_iv::(rng)?; Ok((Self::setup(perm, nonce), nonce)) } @@ -463,11 +462,12 @@ where /// The AEAD view: [`AEADCipherEncryptor`] over the [`SymmetricCipherEncryptor`] impl above, with /// `FINAL_LEN = TAG_LEN`. The encryptor holds nothing back, so the detached final flushes nothing /// and returns only the tag. -impl - AEADCipherEncryptor - for Gcm +impl + AEADCipherEncryptor + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.absorb_aad(aad) @@ -477,11 +477,11 @@ where /// nonce in place of the draw; SP 800-38D Sec 8.2.1's deterministic construction is the /// caller's to follow. fn do_encrypt_init_nonce( - key: &KeyMaterial, + key: &K, nonce: &[u8; GCM_NONCE_LEN], ) -> Result { Self::check_shape(); - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Ok(Self::setup(perm, *nonce)) } @@ -495,9 +495,10 @@ where } } -impl Gcm +impl Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Absorbs `data` (ciphertext) into GHASH, then decrypts it in place. Order matters and is the /// reverse of the encryptor's: GHASH must see ciphertext on both sides, so it is absorbed @@ -538,14 +539,14 @@ where /// explicitly permits this: "in Algorithm 5, the verification of the tag may precede the /// computation of the plaintext". Only on success is `data` decrypted. fn verify_then_decrypt( - key: &KeyMaterial, + key: &K, nonce: &[u8; GCM_NONCE_LEN], aad: &[u8], data: &mut [u8], tag: &[u8; TAG_LEN], ) -> Result<(), SymmetricCipherError> { Self::check_shape(); - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; let mut gcm = Self::setup(perm, *nonce); gcm.absorb_aad(aad)?; gcm.absorb_data(data)?; @@ -559,18 +560,19 @@ where } } -impl - SymmetricCipherDecryptor - for Gcm +impl + SymmetricCipherDecryptor + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { fn do_decrypt_init( - key: &KeyMaterial, + key: &K, init_data: &[u8; GCM_NONCE_LEN], ) -> Result { Self::check_shape(); - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Ok(Self::setup(perm, *init_data)) } @@ -647,13 +649,13 @@ where /// verifies the tag first and only then decrypts, so this one-shot never exposes /// unauthenticated plaintext. The streaming path above, by its nature, still does. fn decrypt_out( - key: &KeyMaterial, + key: &K, init_data: &[u8; GCM_NONCE_LEN], ciphertext: &[u8], plaintext: &mut [u8], ) -> Result { plaintext.fill(0); - >::decrypt_with_aad_out( + >::decrypt_with_aad_out( key, init_data, &[], @@ -666,11 +668,12 @@ where /// The AEAD view: [`AEADCipherDecryptor`] over the [`SymmetricCipherDecryptor`] impl above, with /// `FINAL_LEN = TAG_LEN`. The one-shots are overridden, as `decrypt_out` is, to check the tag /// before any plaintext is written. -impl - AEADCipherDecryptor - for Gcm +impl + AEADCipherDecryptor + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.absorb_aad(aad) @@ -698,7 +701,7 @@ where /// Verifies `tag` before decrypting, so no unauthenticated plaintext reaches `plaintext`; on /// failure what was written there is zeroized. fn decrypt_detached_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; GCM_NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -725,7 +728,7 @@ where /// The inline layout with AAD: splits the trailing `TAG_LEN` bytes off as the tag and verifies /// it before decrypting, as the detached one-shot does, zeroizing `plaintext` on failure. fn decrypt_with_aad_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; GCM_NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -751,13 +754,14 @@ where /// the up-to-`TAG_LEN` bytes a decryptor holds back. `H` and `CIPH_K(J0)` are not in it: both /// derive from the key and the nonce, and `setup` re-derives them on resume. See /// [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Gcm +impl SuspendableComponent + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = Self::CTR_STATE_LEN + GHASH_STATE_LEN + 8 + 8 + 1 + TAG_LEN + 8; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { let (ctr, rest) = out.split_at_mut(Self::CTR_STATE_LEN); @@ -781,10 +785,10 @@ where // `setup` re-derives `H` and `CIPH_K(J0)` from the key and the nonce, which is the // leading part of the CTR state. Its fresh CTR and GHASH are then replaced by the // suspended ones; the CTR read expands the key a second time, a one-off cost at resume. - let nonce = CtrKeyStream::::nonce_from_state(ctr); - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let nonce = CtrKeyStream::::nonce_from_state(ctr); + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut gcm = Self::setup(perm, nonce); - gcm.ctr = as SuspendableComponent>::read_state( + gcm.ctr = as SuspendableComponent>::read_state( ctr, key, )?; gcm.ghash.restore_state(ghash)?; @@ -801,12 +805,13 @@ where } /// `N` must be [`Gcm::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Gcm +impl SuspendableKeyed + for Gcm where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) diff --git a/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs b/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs index 91f17f23b..9cfb00266 100644 --- a/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs +++ b/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs @@ -5,10 +5,10 @@ use crate::modes::ModeNames; use crate::modes::ctr::apply_counter_blocks; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::{ElectronicCodeBook, KeyStream}; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::Algorithm; +use bouncycastle_core::traits::{Algorithm, SymmetricCipherKey}; use bouncycastle_utils::suspendable_state::{Cursor, CursorMut, SuspendableComponent}; +use core::marker::PhantomData; // Imports needed for docs #[allow(unused_imports)] @@ -30,9 +30,15 @@ use crate::stream::StreamCipher; /// The permutation, the nonce and the next counter value. The nonce and the counter are both /// public, so they are plain fields; no keystream is kept between calls. #[derive(Clone)] -pub struct CtrKeyStream -where - P: ElectronicCodeBook + ModeNames, +pub struct CtrKeyStream< + P, + K, + const KEY_LEN: usize, + const BLOCK_LEN: usize, + const INIT_DATA_LEN: usize, +> where + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, /// `N`: the message nonce, the leading bytes of every counter block. @@ -44,12 +50,15 @@ where /// its state back out of those bytes could not tell "just started" from "completely used up". /// This counts to `BLOCK_LIMIT` and stops there. next_counter: u64, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl - CtrKeyStream +impl + CtrKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Bytes of counter at the end of each block: whatever the nonce leaves. const CTR_LEN: usize = BLOCK_LEN - INIT_DATA_LEN; @@ -97,7 +106,7 @@ where counter < Self::BLOCK_LIMIT, "start_at must not be handed an already-exhausted counter" ); - Self { perm, nonce, next_counter: counter } + Self { perm, nonce, next_counter: counter, _key: PhantomData } } /// The nonce `N` of a suspended keystream, read out of the state [`SuspendableComponent`] @@ -124,10 +133,11 @@ where } } -impl Algorithm - for CtrKeyStream +impl Algorithm + for CtrKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::CTR_ALG_NAME`]. const ALG_NAME: &'static str = P::CTR_ALG_NAME; @@ -135,19 +145,17 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - KeyStream - for CtrKeyStream +impl + KeyStream + for CtrKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Expands the key; the keystream starts at `T1 = N | [0]m`. - fn new( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result { + fn new(key: &K, init_data: &[u8; INIT_DATA_LEN]) -> Result { Self::check_shape(); - let perm = P::new(key)?; + let perm = P::new(key.get_key())?; Ok(Self::start(perm, *init_data)) } @@ -171,13 +179,14 @@ where /// The suspended state is the nonce and the next counter value; the permutation is rebuilt from /// the re-supplied key. See [`bouncycastle_utils::suspendable_state`]. -impl - SuspendableComponent for CtrKeyStream +impl + SuspendableComponent for CtrKeyStream where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = INIT_DATA_LEN + 8; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); @@ -188,7 +197,7 @@ where fn read_state(state: &[u8], key: &Self::Key) -> Result { Self::check_shape(); - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = P::new(key.get_key()).map_err(|_| SuspendableError::InvalidData)?; let mut r = Cursor::new(state); let nonce = r.array::(); // The counter counts to `BLOCK_LIMIT` and stops there (that is the exhausted state, with @@ -198,7 +207,7 @@ where return Err(SuspendableError::InvalidData); } debug_assert!(r.is_done()); - Ok(Self { perm, nonce, next_counter }) + Ok(Self { perm, nonce, next_counter, _key: PhantomData }) } } @@ -215,21 +224,17 @@ mod tests { //! the public API" case QUALITY_AND_STYLE.md carves out for a unit test here rather than an //! integration test. - use super::modes_common::{ForwardOnlyToy, TOY_LEN, Toy}; + use super::modes_common::toy_block_cipher::ToyBlockCipher; + use super::modes_common::{ForwardOnlyToy, TOY_LEN, Toy, ToyCipherKey}; use super::*; use crate::Encrypting; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::StreamCipherEncryptor; use bouncycastle_core_test_framework::key_stream::TestFrameworkKeyStream; - use toy::ToyBlockCipher; - - mod toy { - include!("../../../tests/common/toy_block_cipher.rs"); - } - type ToyKeyStream = CtrKeyStream; - type ToyCtr = Ctr; + type ToyKeyStream = CtrKeyStream; + type ToyCtr = Ctr; fn key() -> KeyMaterial<16> { KeyMaterial::<16>::from_bytes_as_type(&[0x5Au8; 16], KeyType::SymmetricCipherKey) @@ -278,19 +283,19 @@ mod tests { #[test] fn ctr_keystream_conforms_to_the_key_stream_framework() { let framework = TestFrameworkKeyStream::new(); - framework.test::>(); - framework.test::>(); + framework.test::>(); + framework.test::>(); framework - .test::>(); + .test::>(); } /// The keystream reports the permutation's CTR name, not its bare `ALG_NAME`; `Ctr` forwards /// it, which `tests/modes/alg_name_tests.rs` checks. #[test] fn ctr_keystream_reports_the_ctr_name() { - assert_eq!(CtrKeyStream::::ALG_NAME, Toy::CTR_ALG_NAME); + assert_eq!(CtrKeyStream::::ALG_NAME, Toy::CTR_ALG_NAME); assert_eq!( - CtrKeyStream::::MAX_SECURITY_STRENGTH, + CtrKeyStream::::MAX_SECURITY_STRENGTH, Toy::MAX_SECURITY_STRENGTH ); } diff --git a/crypto/cipher/src/modes/hazmat/ecb.rs b/crypto/cipher/src/modes/hazmat/ecb.rs index 381164a18..225724831 100644 --- a/crypto/cipher/src/modes/hazmat/ecb.rs +++ b/crypto/cipher/src/modes/hazmat/ecb.rs @@ -15,16 +15,14 @@ //! //! ``` //! # mod toy { include!("../../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; //! use bouncycastle_cipher::modes::hazmat::ecb::Ecb; //! use bouncycastle_cipher::{Decrypting, Encrypting}; //! -//! type ToyEcb = Ecb; +//! type ToyEcb = Ecb; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyCipherKey::new_from_os().expect("a fresh key"); //! let mut data = [0x5Au8; 32]; // two equal blocks //! //! let (bytes_written, no_iv): (usize, [u8; 0]) = ToyEcb::::encrypt_inplace(&key, &mut data).expect("encryption"); @@ -84,10 +82,10 @@ use crate::modes::ModeNames; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SuspendableKeyed, + SymmetricCipherKey, }; use bouncycastle_utils::suspendable_state::{ LIB_VERSION_LEN, SuspendableComponent, resume_component, suspend_component, @@ -100,7 +98,7 @@ use core::marker::PhantomData; /// Considerations". Provided for interoperability and test vectors. /// /// `Dir` is [`Encrypting`] or [`Decrypting`]. [`BlockCipherEncryptor`] is implemented only for the -/// former and [`BlockCipherDecryptor`] only for the latter, so an `Ecb<_, Encrypting, _, _>` has no +/// former and [`BlockCipherDecryptor`] only for the latter, so an `Ecb<_, Encrypting, ToyCipherKey, _, _>` has no /// decryption methods at all -- using one in the wrong direction is a compile error rather than a /// runtime check. /// @@ -109,32 +107,37 @@ use core::marker::PhantomData; /// zeroize-on-drop wrapper. Nothing chains from one block to the next, so unlike `Cbc` and `Cfb` /// there is no block of chaining value: `size_of::>() == size_of::

()`. #[derive(Clone)] -pub struct Ecb +pub struct Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { perm: P, _dir: PhantomData

, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl Ecb +impl Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header alone, since ECB /// has no state between blocks. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = LIB_VERSION_LEN; /// Expands the key. Both `_init` constructors are this; there is nothing else to set up. - fn new(key: &KeyMaterial) -> Result { - Ok(Self { perm: P::new(key)?, _dir: PhantomData }) + fn new(key: &K) -> Result { + Ok(Self { perm: P::new(key.get_key())?, _dir: PhantomData, _key: PhantomData }) } } -impl Algorithm - for Ecb +impl Algorithm + for Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// The permutation's name for this mode, [`ModeNames::ECB_ALG_NAME`]. const ALG_NAME: &'static str = P::ECB_ALG_NAME; @@ -143,16 +146,15 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl BlockCipherEncryptor - for Ecb +impl + BlockCipherEncryptor for Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Expands the key. ECB has no initialization data (SP 800-38A Table D.2 lists the IV column /// as "Not applicable"), so the returned init data is the empty array. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; 0]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; 0]), SymmetricCipherError> { Ok((Self::new(key)?, [])) } @@ -165,7 +167,7 @@ where /// applicable" -- so silently ignoring the RNG would leave that mistaken expectation /// undisturbed. Use [`do_encrypt_init`](Self::do_encrypt_init), or a mode that has an IV. fn do_encrypt_init_rng( - _key: &KeyMaterial, + _key: &K, _rng: &mut dyn RNG, ) -> Result<(Self, [u8; 0]), SymmetricCipherError> { unimplemented!( @@ -200,17 +202,15 @@ where } } -impl BlockCipherDecryptor - for Ecb +impl + BlockCipherDecryptor for Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { /// Expands the key. The init data is the empty array [`BlockCipherEncryptor::do_encrypt_init`] /// returned; there is nothing in it to use. - fn do_decrypt_init( - key: &KeyMaterial, - _init_data: &[u8; 0], - ) -> Result { + fn do_decrypt_init(key: &K, _init_data: &[u8; 0]) -> Result { Self::new(key) } @@ -239,13 +239,14 @@ where /// ECB carries nothing from one block to the next, so its suspended state is empty and resuming /// is re-expanding the key. It is implemented so that the padded adapters over it, which do hold /// a partial block, can be suspended. See [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Ecb +impl SuspendableComponent + for Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { const STATE_LEN: usize = 0; - type Key = KeyMaterial; + type Key = K; fn write_state(&self, out: &mut [u8]) { debug_assert!(out.is_empty()); @@ -258,12 +259,13 @@ where } /// `N` must be [`Ecb::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Ecb +impl SuspendableKeyed + for Ecb where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, { - type Key = KeyMaterial; + type Key = K; fn suspend(self) -> [u8; N] { suspend_component(&self) diff --git a/crypto/cipher/src/modes/mod.rs b/crypto/cipher/src/modes/mod.rs index 238dd6006..0a34e5993 100644 --- a/crypto/cipher/src/modes/mod.rs +++ b/crypto/cipher/src/modes/mod.rs @@ -71,28 +71,29 @@ //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; +//! # use toy::{ToyBlockCipher, ToyCipherKey}; +//! use bouncycastle_core::traits::SymmetricCipherKey; //! use bouncycastle_cipher::modes::{Cbc, CcmPacket, Cfb, Cfb8, Ctr, Gcm}; //! //! // CBC, CFB, and CFB8 take a permutation, a direction, key length, and a block length. -//! type ToyCbc = Cbc; -//! type ToyCfb = Cfb; -//! type ToyCfb8 = Cfb8; +//! type ToyCbc = Cbc; +//! type ToyCfb = Cfb; +//! type ToyCfb8 = Cfb8; //! //! // CTR takes one more parameter: the nonce length, which fixes the counter width at //! // `BLOCK_LEN - NONCE_LEN`. 12 bytes of nonce leaves the maximum 4-byte counter. -//! type ToyCtr = Ctr; +//! type ToyCtr = Ctr; //! //! // CCM takes the permutation, a direction, key length, and a block length like the rest, //! // plus the nonce length and the tag length -- both CCM-specific choices rather than cipher params. //! // The nonce length caps the payload (SP 800-38C A.1: `n + q = 15`, `p < 2^8q`) and the tag //! // length is the forgery bound; 12 and 16 are the usual pair. -//! type ToyCcm = CcmPacket; +//! type ToyCcm = CcmPacket; //! //! // GCM mode is specified in NIST SP 800-38D. `Gcm` fixes the nonce at 12 bytes (Sec 5.2.1.1 //! // recommends restricting support to 96 bits), and the block is always 16, so neither is a //! // parameter. -//! type ToyGcm = Gcm; +//! type ToyGcm = Gcm; //! ``` //! //! ## Encrypting and decrypting diff --git a/crypto/cipher/src/padding/padded_block_cipher.rs b/crypto/cipher/src/padding/padded_block_cipher.rs index fe42b95b2..1f60aaa4b 100644 --- a/crypto/cipher/src/padding/padded_block_cipher.rs +++ b/crypto/cipher/src/padding/padded_block_cipher.rs @@ -8,11 +8,10 @@ //! all, in which case `do_encrypt_final` reports 0 of the `FINAL_LEN` bytes as output. use bouncycastle_core::errors::{PaddingError, SuspendableError, SymmetricCipherError}; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, BlockCipherPadding, RNG, - SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ @@ -37,25 +36,30 @@ const GROUP: usize = 8; pub struct PaddedBlockCipherEncryptor< E, P, + K, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, > where - E: BlockCipherEncryptor, + E: BlockCipherEncryptor, P: BlockCipherPadding, + K: SymmetricCipherKey, { encryptor: E, _padding: PhantomData

, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, /// Partial plaintext block; `buf_len < BLOCK_LEN` between calls. buf: Secret<[u8; BLOCK_LEN]>, buf_len: usize, } -impl Algorithm - for PaddedBlockCipherEncryptor +impl Algorithm + for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor, + E: BlockCipherEncryptor, P: BlockCipherPadding, + K: SymmetricCipherKey, { /// The inner cipher's name; padding does not change what the algorithm is. const ALG_NAME: &'static str = E::ALG_NAME; @@ -63,26 +67,43 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = E::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor - for PaddedBlockCipherEncryptor +impl + SymmetricCipherEncryptor + for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor, + E: BlockCipherEncryptor, P: BlockCipherPadding, + K: SymmetricCipherKey, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { let (encryptor, init_data) = E::do_encrypt_init(key)?; - Ok((Self { encryptor, _padding: PhantomData, buf: Secret::new(), buf_len: 0 }, init_data)) + Ok(( + Self { + encryptor, + _padding: PhantomData, + _key: PhantomData, + buf: Secret::new(), + buf_len: 0, + }, + init_data, + )) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { let (encryptor, init_data) = E::do_encrypt_init_rng(key, rng)?; - Ok((Self { encryptor, _padding: PhantomData, buf: Secret::new(), buf_len: 0 }, init_data)) + Ok(( + Self { + encryptor, + _padding: PhantomData, + _key: PhantomData, + buf: Secret::new(), + buf_len: 0, + }, + init_data, + )) } /// Whole blocks among the buffered bytes plus `input_len`. @@ -189,15 +210,19 @@ where pub struct PaddedBlockCipherDecryptor< D, P, + K, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, > where - D: BlockCipherDecryptor, + D: BlockCipherDecryptor, P: BlockCipherPadding, + K: SymmetricCipherKey, { decryptor: D, _padding: PhantomData

, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, /// Partial ciphertext block; `buf_len < BLOCK_LEN` between calls. buf: [u8; BLOCK_LEN], buf_len: usize, @@ -205,11 +230,12 @@ pub struct PaddedBlockCipherDecryptor< held: Option<[u8; BLOCK_LEN]>, } -impl Algorithm - for PaddedBlockCipherDecryptor +impl Algorithm + for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor, + D: BlockCipherDecryptor, P: BlockCipherPadding, + K: SymmetricCipherKey, { /// The inner cipher's name; padding does not change what the algorithm is. const ALG_NAME: &'static str = D::ALG_NAME; @@ -217,20 +243,22 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = D::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherDecryptor - for PaddedBlockCipherDecryptor +impl + SymmetricCipherDecryptor + for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor, + D: BlockCipherDecryptor, + K: SymmetricCipherKey, P: BlockCipherPadding, { fn do_decrypt_init( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ) -> Result { Ok(Self { decryptor: D::do_decrypt_init(key, init_data)?, _padding: PhantomData, + _key: PhantomData, buf: [0u8; BLOCK_LEN], buf_len: 0, held: None, @@ -339,11 +367,12 @@ where } } -impl - PaddedBlockCipherEncryptor +impl + PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the inner /// cipher's state, the partial plaintext block and its length as a `u64`. See @@ -354,11 +383,12 @@ where /// The suspended state is the inner cipher's followed by the buffered partial block. That block /// is plaintext, so the state must be protected; see [`bouncycastle_utils::suspendable_state`]. -impl - SuspendableComponent for PaddedBlockCipherEncryptor +impl + SuspendableComponent for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { const STATE_LEN: usize = E::STATE_LEN + BLOCK_LEN + 8; type Key = E::Key; @@ -381,17 +411,25 @@ where // A full block is encrypted as soon as it is full, so `buf_len < BLOCK_LEN` between calls. let buf_len = bounded_usize(r.u64(), BLOCK_LEN - 1)?; debug_assert!(r.is_done()); - Ok(Self { encryptor, _padding: PhantomData, buf, buf_len }) + Ok(Self { encryptor, _padding: PhantomData, _key: PhantomData, buf, buf_len }) } } /// `N` must be [`PaddedBlockCipherEncryptor::SUSPENDED_STATE_LEN`]; anything else is a compile /// error. -impl - SuspendableKeyed for PaddedBlockCipherEncryptor +impl< + E, + P, + K, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + const N: usize, +> SuspendableKeyed for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { type Key = E::Key; @@ -404,11 +442,12 @@ where } } -impl - PaddedBlockCipherDecryptor +impl + PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the inner /// cipher's state, the partial ciphertext block and its length as a `u64`, a flag for @@ -419,11 +458,12 @@ where /// The suspended state is the inner cipher's, the buffered partial block, and the withheld /// block if there is one (all ciphertext). See [`bouncycastle_utils::suspendable_state`]. -impl - SuspendableComponent for PaddedBlockCipherDecryptor +impl + SuspendableComponent for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { const STATE_LEN: usize = D::STATE_LEN + BLOCK_LEN + 8 + 1 + BLOCK_LEN; type Key = D::Key; @@ -463,17 +503,25 @@ where _ => return Err(SuspendableError::InvalidData), }; debug_assert!(r.is_done()); - Ok(Self { decryptor, _padding: PhantomData, buf, buf_len, held }) + Ok(Self { decryptor, _padding: PhantomData, _key: PhantomData, buf, buf_len, held }) } } /// `N` must be [`PaddedBlockCipherDecryptor::SUSPENDED_STATE_LEN`]; anything else is a compile /// error. -impl - SuspendableKeyed for PaddedBlockCipherDecryptor +impl< + D, + P, + K, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + const N: usize, +> SuspendableKeyed for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent, P: BlockCipherPadding, + K: SymmetricCipherKey, { type Key = D::Key; diff --git a/crypto/cipher/src/stream.rs b/crypto/cipher/src/stream.rs index 21d93ccaa..38cec1e50 100644 --- a/crypto/cipher/src/stream.rs +++ b/crypto/cipher/src/stream.rs @@ -21,11 +21,10 @@ use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::KeyStream; -use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::secret::Secret; @@ -59,11 +58,13 @@ use core::marker::PhantomData; pub struct StreamCipher< KS, Dir, + K, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, > where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { keystream: KS, /// The keystream block currently being consumed. Meaningful only while `used < BLOCK_LEN`. @@ -72,12 +73,15 @@ pub struct StreamCipher< /// and the next byte needs a fresh keystream block. used: usize, _marker: PhantomData

, + /// Records `K`, which no other field mentions; without it `K` would be an unused parameter. + _key: PhantomData, } -impl - StreamCipher +impl + StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// Wraps a keystream that has already been constructed and positioned, such as one that /// starts part-way into its counter space (GCM's GCTR starts at `inc32(J0)`). @@ -88,7 +92,13 @@ where /// [`SymmetricCipherEncryptor`] constructors are the safe path. pub fn from_keystream(keystream: KS) -> Self { Self::check_shape(); - Self { keystream, pending: Secret::new(), used: BLOCK_LEN, _marker: PhantomData } + Self { + keystream, + pending: Secret::new(), + used: BLOCK_LEN, + _marker: PhantomData, + _key: PhantomData, + } } /// The wrapped keystream, for a construction that shares its key schedule with something @@ -162,10 +172,11 @@ where } } -impl Algorithm - for StreamCipher +impl Algorithm + for StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// The keystream's name. const ALG_NAME: &'static str = KS::ALG_NAME; @@ -173,16 +184,15 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = KS::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor - for StreamCipher +impl + SymmetricCipherEncryptor + for StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// Begins an encryption flow, drawing the init data from the library's default OS-backed DRBG. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { let mut rng = HashDRBG_SHA512::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } @@ -190,7 +200,7 @@ where /// As [`SymmetricCipherEncryptor::do_encrypt_init`], but draws the init data from `rng`. /// Never panics: `INIT_DATA_LEN == 0` is ruled out at compile time; see [`StreamCipher`]. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { Self::check_shape(); @@ -237,11 +247,12 @@ where } } -impl - StreamCipherEncryptor - for StreamCipher +impl + StreamCipherEncryptor + for StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// XORs the next `data.len()` keystream bytes into `data`. /// @@ -253,16 +264,17 @@ where } } -impl - SymmetricCipherDecryptor - for StreamCipher +impl + SymmetricCipherDecryptor + for StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// Begins a decryption flow from the init data returned by /// [`SymmetricCipherEncryptor::do_encrypt_init`]. fn do_decrypt_init( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ) -> Result { Self::check_shape(); @@ -306,11 +318,12 @@ where } } -impl - StreamCipherDecryptor - for StreamCipher +impl + StreamCipherDecryptor + for StreamCipher where - KS: KeyStream, + KS: KeyStream, + K: SymmetricCipherKey, { /// The same XOR as encryption. /// @@ -321,10 +334,11 @@ where } } -impl - StreamCipher +impl + StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent, + K: SymmetricCipherKey, { /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the keystream's /// state, the pending keystream block and the `used` count as a `u64`. See @@ -336,10 +350,11 @@ where /// The suspended state is the keystream's own state followed by the pending keystream block and /// how much of it is used. The pending block is live keystream, which is why the whole state /// must be protected and never resumed twice; see [`bouncycastle_utils::suspendable_state`]. -impl - SuspendableComponent for StreamCipher +impl + SuspendableComponent for StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent, + K: SymmetricCipherKey, { const STATE_LEN: usize = KS::STATE_LEN + BLOCK_LEN + 8; type Key = KS::Key; @@ -364,7 +379,7 @@ where // `used` is `0..=BLOCK_LEN`, with `BLOCK_LEN` meaning nothing is pending. let used = bounded_usize(r.u64(), BLOCK_LEN)?; debug_assert!(r.is_done()); - Ok(Self { keystream, pending, used, _marker: PhantomData }) + Ok(Self { keystream, pending, used, _marker: PhantomData, _key: PhantomData }) } } @@ -372,13 +387,15 @@ where impl< KS, Dir, + K, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, const N: usize, -> SuspendableKeyed for StreamCipher +> SuspendableKeyed for StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent, + K: SymmetricCipherKey, { type Key = KS::Key; diff --git a/crypto/cipher/tests/ccm_suspend_tests.rs b/crypto/cipher/tests/ccm_suspend_tests.rs index 28dace9ae..56798ef8c 100644 --- a/crypto/cipher/tests/ccm_suspend_tests.rs +++ b/crypto/cipher/tests/ccm_suspend_tests.rs @@ -16,19 +16,18 @@ use bouncycastle_cipher::modes::CcmPacket; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SuspendableError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::SuspendableKeyed; +use bouncycastle_core::traits::{SuspendableKeyed, SymmetricCipherKey}; use bouncycastle_utils::suspendable_state::LIB_VERSION_LEN; #[path = "common/toy_block_cipher.rs"] mod toy_block_cipher; -use toy_block_cipher::ToyBlockCipher; +use toy_block_cipher::{ToyBlockCipher, ToyCipherKey}; /// A 12-byte nonce, so `q = 3`: the counter field is the template's last three octets and the /// payload limit is `2^24 - 1`. const NONCE_LEN: usize = 12; const BLOCK_LEN: usize = 16; -type ToyCcm = CcmPacket; +type ToyCcm = CcmPacket; const N: usize = ToyCcm::::SUSPENDED_STATE_LEN; /// A.1's `2^8q - 1`, which bounds both the owed payload and, since there is one counter block per @@ -41,8 +40,8 @@ const NEXT_CTR: usize = TEMPLATE + BLOCK_LEN; const MAC_POS: usize = N - 24; const OWED: usize = N - 8; -fn key() -> KeyMaterial<16> { - KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap() +fn key() -> ToyCipherKey { + ToyCipherKey::from_bytes(&[0x42; 16]).unwrap() } /// A freshly constructed encryptor's state: `next_ctr = 1`, `mac_pos = 0` after `B0`, `owed` as diff --git a/crypto/cipher/tests/common/toy_block_cipher.rs b/crypto/cipher/tests/common/toy_block_cipher.rs index db600cbf2..8e9dac19d 100644 --- a/crypto/cipher/tests/common/toy_block_cipher.rs +++ b/crypto/cipher/tests/common/toy_block_cipher.rs @@ -15,11 +15,12 @@ // with `#[path]` from integration tests, so it carries no inner attributes or `//!` docs. use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; +use bouncycastle_core::errors::{KeyMaterialError, RNGError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::Algorithm; +use bouncycastle_core::traits::{Algorithm, SymmetricCipherKey}; +use bouncycastle_rng::HashDRBG_SHA256; /// Key and block length of [`ToyBlockCipher`]: the same as AES-128, so the toy exercises the same /// shapes a real cipher would. @@ -47,7 +48,7 @@ impl ModeNames for ToyBlockCipher { const GCM_ALG_NAME: &'static str = "ToyBlockCipher_GCM"; } -impl ElectronicCodeBook for ToyBlockCipher { +impl ElectronicCodeBook for ToyBlockCipher { /// Rejects the same keys a real permutation would, so that key-policy checks are meaningful. fn new(key: &KeyMaterial) -> Result { if key.key_type() != KeyType::SymmetricCipherKey { @@ -105,3 +106,31 @@ impl ElectronicCodeBook for ToyBlockCipher { } } } + +/// The key type for a mode built over [`ToyBlockCipher`]: a 16-byte symmetric cipher key of at +/// least 128-bit strength, so the modes' key-policy checks behave as they do with a real key. +/// +/// One key type for every mode is fine for a toy -- the per-mode distinction is what the real +/// cipher crates make, and it is their tests that check it. +#[derive(Clone, PartialEq, Eq)] +pub struct ToyCipherKey(KeyMaterial); + +impl SymmetricCipherKey for ToyCipherKey { + fn from_keymaterial(key: KeyMaterial) -> Result { + if key.key_type() != KeyType::SymmetricCipherKey { + return Err(KeyMaterialError::InvalidKeyType("Must be SymmetricCipherKey")); + } + if key.security_strength() < SecurityStrength::_128bit { + return Err(KeyMaterialError::SecurityStrength("ToyCipherKey needs a 128-bit key")); + } + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG_SHA256::new_from_os()) + } +} diff --git a/crypto/cipher/tests/modes/alg_name_tests.rs b/crypto/cipher/tests/modes/alg_name_tests.rs index 4bfe59ba9..243793d66 100644 --- a/crypto/cipher/tests/modes/alg_name_tests.rs +++ b/crypto/cipher/tests/modes/alg_name_tests.rs @@ -15,7 +15,7 @@ use bouncycastle_cipher::padding::{ }; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::traits::Algorithm; -use common::Toy; +use common::{Toy, ToyCipherKey}; fn check(name: &str) { assert_eq!(A::ALG_NAME, name); @@ -43,37 +43,65 @@ fn the_toy_names_every_mode_differently() { #[test] fn each_mode_reports_its_own_name() { - check::>(Toy::CBC_ALG_NAME); - check::>(Toy::CBC_ALG_NAME); - check::>(Toy::ECB_ALG_NAME); - check::>(Toy::ECB_ALG_NAME); - check::>(Toy::CFB_ALG_NAME); - check::>(Toy::CFB_ALG_NAME); - check::>(Toy::CFB8_ALG_NAME); - check::>(Toy::CFB8_ALG_NAME); - check::>(Toy::GCM_ALG_NAME); - check::>(Toy::GCM_ALG_NAME); - check::>(Toy::CCM_ALG_NAME); - check::>(Toy::CCM_ALG_NAME); - check::>(Toy::CCM_ALG_NAME); - check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CBC_ALG_NAME); + check::>(Toy::CBC_ALG_NAME); + check::>(Toy::ECB_ALG_NAME); + check::>(Toy::ECB_ALG_NAME); + check::>(Toy::CFB_ALG_NAME); + check::>(Toy::CFB_ALG_NAME); + check::>(Toy::CFB8_ALG_NAME); + check::>(Toy::CFB8_ALG_NAME); + check::>(Toy::GCM_ALG_NAME); + check::>(Toy::GCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); } /// The padding layer and the stream-cipher wrapper add no name of their own. #[test] fn the_wrappers_forward_the_inner_name() { - check::>(Toy::CTR_ALG_NAME); - check::>(Toy::CTR_ALG_NAME); - check::, PKCS7, 16, 16, 16>>( - Toy::CBC_ALG_NAME, - ); - check::, PKCS7, 16, 16, 16>>( - Toy::CBC_ALG_NAME, - ); - check::, NoPadding, 16, 0, 16>>( - Toy::ECB_ALG_NAME, - ); - check::, NoPadding, 16, 0, 16>>( - Toy::ECB_ALG_NAME, - ); + check::>(Toy::CTR_ALG_NAME); + check::>(Toy::CTR_ALG_NAME); + check::< + PaddedBlockCipherEncryptor< + Cbc, + PKCS7, + ToyCipherKey, + 16, + 16, + 16, + >, + >(Toy::CBC_ALG_NAME); + check::< + PaddedBlockCipherDecryptor< + Cbc, + PKCS7, + ToyCipherKey, + 16, + 16, + 16, + >, + >(Toy::CBC_ALG_NAME); + check::< + PaddedBlockCipherEncryptor< + Ecb, + NoPadding, + ToyCipherKey, + 16, + 0, + 16, + >, + >(Toy::ECB_ALG_NAME); + check::< + PaddedBlockCipherDecryptor< + Ecb, + NoPadding, + ToyCipherKey, + 16, + 0, + 16, + >, + >(Toy::ECB_ALG_NAME); } diff --git a/crypto/cipher/tests/modes/cbc_tests.rs b/crypto/cipher/tests/modes/cbc_tests.rs index 43ad60dcc..40198e34b 100644 --- a/crypto/cipher/tests/modes/cbc_tests.rs +++ b/crypto/cipher/tests/modes/cbc_tests.rs @@ -8,20 +8,19 @@ mod common; use bouncycastle_cipher::modes::Cbc; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; -use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyCbc = Cbc; -type SwappedCbc = Cbc; -type SwappedFourCbc = Cbc; +type ToyCbc = Cbc; +type SwappedCbc = Cbc; +type SwappedFourCbc = Cbc; /// The implementor hook `do_encrypt_blocks_inplace`, by value, for tests whose data is /// block-shaped. fn enc_blocks( - enc: &mut impl BlockCipherEncryptor, + enc: &mut impl BlockCipherEncryptor, plaintext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *plaintext; @@ -31,7 +30,7 @@ fn enc_blocks( /// The implementor hook `do_decrypt_blocks_inplace`, by value. fn dec_blocks( - dec: &mut impl BlockCipherDecryptor, + dec: &mut impl BlockCipherDecryptor, ciphertext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *ciphertext; @@ -44,13 +43,13 @@ fn dec_blocks( /// The toy must be a real permutation before any conclusion drawn from it is worth anything. #[test] fn the_toy_permutation_conforms_to_the_trait() { - TestFrameworkElectronicCodeBook::new().test::(); + TestFrameworkElectronicCodeBook::new().test::(); } #[test] fn cbc_conforms_to_the_block_cipher_framework() { TestFrameworkBlockCipher::new() - .test::, ToyCbc>(); + .test::, ToyCbc>(); } // ---- chaining and call sequencing -------------------------------------------------------- @@ -306,16 +305,6 @@ fn identical_plaintext_gives_different_ciphertext() { ); } -// ---- key handling ------------------------------------------------------------------------ - -#[test] -fn a_key_of_the_wrong_type_is_rejected() { - let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8) + 1); - let seed = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::Seed).unwrap(); - assert!(ToyCbc::::do_encrypt_init(&seed).is_err()); - assert!(ToyCbc::::do_decrypt_init(&seed, &[0u8; TOY_LEN]).is_err()); -} - /// The one-shots (`encrypt` / `decrypt` on a `[u8; LEN]`, in place) must produce exactly what the /// streaming API produces over the same blocks, for an odd block count (pairs plus a one-block /// tail) and an even one (pairs only), in both directions. diff --git a/crypto/cipher/tests/modes/ccm_tests.rs b/crypto/cipher/tests/modes/ccm_tests.rs index d67bfe535..bdc938708 100644 --- a/crypto/cipher/tests/modes/ccm_tests.rs +++ b/crypto/cipher/tests/modes/ccm_tests.rs @@ -17,18 +17,21 @@ use bouncycastle_cipher::modes::{CcmDecryptor, CcmPacket, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{AEADCipherDecryptor, SymmetricCipherDecryptor}; -use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; /// The default shape under test: a 12-byte nonce, so `q = 3`, and a full 16-byte tag. const NONCE_LEN: usize = 12; const TAG_LEN: usize = 16; -type ToyCcm = CcmPacket; -type SwappedCcm = CcmPacket; -type SwappedFourCcm = CcmPacket; -type ForwardOnlyCcm = CcmPacket; +type ToyCcm = CcmPacket; +type SwappedCcm = + CcmPacket; +type SwappedFourCcm = + CcmPacket; +type ForwardOnlyCcm = + CcmPacket; fn pinned_nonce() -> [u8; NONCE_LEN] { core::array::from_fn(|i| 0xA0 ^ (i as u8)) @@ -39,39 +42,41 @@ fn message(len: usize) -> Vec { } /// One-shot Sec 6.1 over the toy permutation `P`, detached: the ciphertext and the tag. -fn encrypt + ModeNames>( +fn encrypt + ModeNames>( nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], ) -> (Vec, [u8; TAG_LEN]) { let mut ct = vec![0u8; plaintext.len()]; - let (written, tag) = - CcmPacket::::encrypt_detached_out( - &toy_key(), - nonce, - aad, - plaintext, - &mut ct, - ) - .unwrap(); + let (written, tag) = CcmPacket::< + P, + Encrypting, + ToyCipherKey, + TOY_LEN, + TOY_LEN, + NONCE_LEN, + TAG_LEN, + >::encrypt_detached_out(&toy_key(), nonce, aad, plaintext, &mut ct) + .unwrap(); assert_eq!(written, plaintext.len(), "CCM never expands the payload"); (ct, tag) } /// Sec 6.1 through the streaming API over `P`, `chunk` bytes per `do_encrypt_inplace`. -fn stream_encrypt + ModeNames>( +fn stream_encrypt + ModeNames>( nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], chunk: usize, ) -> (Vec, [u8; TAG_LEN]) { - let mut ccm = CcmPacket::::new( - &toy_key(), - nonce, - aad, - plaintext.len(), - ) - .unwrap(); + let mut ccm = + CcmPacket::::new( + &toy_key(), + nonce, + aad, + plaintext.len(), + ) + .unwrap(); let mut data = plaintext.to_vec(); for piece in data.chunks_mut(chunk) { ccm.do_encrypt_inplace(piece).unwrap(); @@ -81,19 +86,20 @@ fn stream_encrypt + ModeNames>( } /// Sec 6.2 through the streaming API over `P`, `chunk` bytes per `do_decrypt_inplace`. -fn stream_decrypt + ModeNames>( +fn stream_decrypt + ModeNames>( nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], tag: &[u8; TAG_LEN], chunk: usize, ) -> Result, SymmetricCipherError> { - let mut ccm = CcmPacket::::new( - &toy_key(), - nonce, - aad, - ciphertext.len(), - )?; + let mut ccm = + CcmPacket::::new( + &toy_key(), + nonce, + aad, + ciphertext.len(), + )?; let mut data = ciphertext.to_vec(); for piece in data.chunks_mut(chunk) { ccm.do_decrypt_inplace(piece)?; @@ -310,8 +316,8 @@ fn tag_length_changes_the_tag_but_not_the_ciphertext_and_tags_do_not_nest() { macro_rules! check_tag_len { ($t:literal) => {{ - type Enc = CcmPacket; - type Dec = CcmPacket; + type Enc = CcmPacket; + type Dec = CcmPacket; let mut ct = vec![0u8; plaintext.len()]; let (_, tag) = Enc::encrypt_detached_out(&toy_key(), &nonce, aad, &plaintext, &mut ct).unwrap(); @@ -348,21 +354,22 @@ fn tag_length_changes_the_tag_but_not_the_ciphertext_and_tags_do_not_nest() { /// is the mode's business and not the permutation's. #[test] fn every_permitted_nonce_length_works() { - fn round_trip( - key: &KeyMaterial, - expected_max_payload: u64, - ) where - P: ElectronicCodeBook + ModeNames, + fn round_trip(key: &K, expected_max_payload: u64) + where + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { // The full 16-byte tag, deliberately. `Toy` permutes each byte independently, so under // it the CBC-MAC is sixteen independent byte-chains and a `t`-byte tag witnesses only the // first `t` of them; the last nonce octet flipped below sits at block octet `N`, which an // 8-byte tag would never see once `N >= 8`. Real AES mixes every byte into every other, // so this is a limit of the toy, not of the mode. - type Enc = CcmPacket; - type Dec = CcmPacket; + type Enc = + CcmPacket; + type Dec = + CcmPacket; assert_eq!( - Enc::::MAX_PAYLOAD_LEN, + Enc::::MAX_PAYLOAD_LEN, expected_max_payload, "n = {N}: the payload limit 2^8q - 1 that q = 15 - n implies" ); @@ -371,12 +378,12 @@ fn every_permitted_nonce_length_works() { let plaintext = message(100); let mut ct = vec![0u8; plaintext.len()]; let (_, tag) = - Enc::::encrypt_detached_out(key, &nonce, b"aad", &plaintext, &mut ct) + Enc::::encrypt_detached_out(key, &nonce, b"aad", &plaintext, &mut ct) .unwrap(); assert_ne!(ct, plaintext, "nonce length {N}: must actually encrypt"); let mut back = vec![0u8; plaintext.len()]; - Dec::::decrypt_detached_out(key, &nonce, b"aad", &ct, &tag, &mut back) + Dec::::decrypt_detached_out(key, &nonce, b"aad", &ct, &tag, &mut back) .unwrap(); assert_eq!(back, plaintext, "nonce length {N}: round trip"); @@ -386,7 +393,7 @@ fn every_permitted_nonce_length_works() { wrong[N - 1] ^= 0x01; assert!( matches!( - Dec::::decrypt_detached_out( + Dec::::decrypt_detached_out( key, &wrong, b"aad", &ct, &tag, &mut back ), Err(SymmetricCipherError::AEADTagCheckFailed) @@ -397,13 +404,13 @@ fn every_permitted_nonce_length_works() { // `q = 8` makes `2^8q` exactly `2^64`, which does not fit a `u64`, so the bound is `u64::MAX`. let toy = toy_key(); - round_trip::(&toy, u64::MAX); - round_trip::(&toy, (1 << 56) - 1); - round_trip::(&toy, (1 << 48) - 1); - round_trip::(&toy, (1 << 40) - 1); - round_trip::(&toy, (1 << 32) - 1); - round_trip::(&toy, (1 << 24) - 1); - round_trip::(&toy, (1 << 16) - 1); + round_trip::(&toy, u64::MAX); + round_trip::(&toy, (1 << 56) - 1); + round_trip::(&toy, (1 << 48) - 1); + round_trip::(&toy, (1 << 40) - 1); + round_trip::(&toy, (1 << 32) - 1); + round_trip::(&toy, (1 << 24) - 1); + round_trip::(&toy, (1 << 16) - 1); } /// Which entry points release unauthenticated plaintext on a forgery, pinned side by side. @@ -451,7 +458,7 @@ fn one_shots_release_nothing_on_forgery_but_the_streams_do() { // the update that brings it, the finals release nothing, and a rejected tag cannot take it // back. The detached final leaves its (unused) buffer alone rather than zeroizing it, since // there is nothing of the plaintext in it to zeroize. - type Dec = CcmDecryptor; + type Dec = CcmDecryptor; let mut dec = Dec::do_decrypt_init(&toy_key(), &nonce).unwrap(); dec.do_update_aad(b"aad").unwrap(); diff --git a/crypto/cipher/tests/modes/cfb8_tests.rs b/crypto/cipher/tests/modes/cfb8_tests.rs index d3ee96f6f..6fb291c6f 100644 --- a/crypto/cipher/tests/modes/cfb8_tests.rs +++ b/crypto/cipher/tests/modes/cfb8_tests.rs @@ -16,29 +16,35 @@ mod common; use bouncycastle_cipher::modes::{Cfb, Cfb8, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; -use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyCfb8 = Cfb8; -type SwappedCfb8 = Cfb8; -type ForwardOnlyCfb8 = Cfb8; -type SwappedFourCfb8 = Cfb8; +type ToyCfb8 = Cfb8; +type SwappedCfb8 = Cfb8; +type ForwardOnlyCfb8 = Cfb8; +type SwappedFourCfb8 = Cfb8; /// `do_encrypt_inplace`, by value. -fn enc(e: &mut impl StreamCipherEncryptor, plaintext: &[u8]) -> Vec { +fn enc( + e: &mut impl StreamCipherEncryptor, + plaintext: &[u8], +) -> Vec { let mut data = plaintext.to_vec(); e.do_encrypt_inplace(&mut data).unwrap(); data } /// `do_decrypt_inplace`, by value. -fn dec(d: &mut impl StreamCipherDecryptor, ciphertext: &[u8]) -> Vec { +fn dec( + d: &mut impl StreamCipherDecryptor, + ciphertext: &[u8], +) -> Vec { let mut data = ciphertext.to_vec(); d.do_decrypt_inplace(&mut data).unwrap(); data @@ -46,7 +52,7 @@ fn dec(d: &mut impl StreamCipherDecryptor, ciphertext: &[u8]) /// `do_decrypt_inplace` in `chunk`-byte calls, by value. The last call may be shorter. fn dec_chunked( - d: &mut impl StreamCipherDecryptor, + d: &mut impl StreamCipherDecryptor, ciphertext: &[u8], chunk: usize, ) -> Vec { @@ -92,7 +98,7 @@ const CHUNKINGS: [usize; 11] = [1, 2, 3, 7, 8, 9, 15, 16, 17, 32, 100]; #[test] fn cfb8_conforms_to_the_stream_cipher_framework() { TestFrameworkStreamCipher::new() - .test::, ToyCfb8>(); + .test::, ToyCfb8>(); } // ---- the spec equations ------------------------------------------------------------------- @@ -139,7 +145,8 @@ fn reference_cfb8(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> fn the_mode_matches_the_spec_equations() { let key = toy_key(); let iv = pinned_iv(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); for len in [1, 2, TOY_LEN - 1, TOY_LEN, TOY_LEN + 1, 3 * TOY_LEN + 5] { let plaintext = message(len); @@ -208,8 +215,11 @@ fn cfb8_is_not_cfb128() { let cfb8 = enc(&mut pinned_encryptor(iv), &plaintext); let (mut cfb, got) = - Cfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)) - .unwrap(); + Cfb::::do_encrypt_init_rng( + &key, + &mut pinned_rng(iv), + ) + .unwrap(); assert_eq!(got, iv); let mut cfb128 = plaintext.clone(); cfb.do_encrypt_inplace(&mut cfb128).unwrap(); @@ -223,7 +233,8 @@ fn cfb8_is_not_cfb128() { assert_ne!(wrong, plaintext, "CFB8 must not decrypt a CFB128 ciphertext"); let mut wrong = cfb8.clone(); - Cfb::::decrypt_inplace(&key, &iv, &mut wrong).unwrap(); + Cfb::::decrypt_inplace(&key, &iv, &mut wrong) + .unwrap(); assert_ne!(wrong, plaintext, "CFB128 must not decrypt a CFB8 ciphertext"); } @@ -357,20 +368,19 @@ fn call_chunking_does_not_change_the_result() { /// single-call-versus-chunked comparison, kept free of an AES dependency. #[test] fn chunking_matches_a_single_call_at_every_batch_remainder() { - fn check(name: &str) + fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { let key_bytes: [u8; KEY_LEN] = core::array::from_fn(|i| (i as u8).wrapping_mul(31).wrapping_add(7)); - let key = - KeyMaterial::::from_bytes_as_type(&key_bytes, KeyType::SymmetricCipherKey) - .expect("a valid key"); + let key = K::from_bytes(&key_bytes).expect("a valid key"); let iv: [u8; 16] = core::array::from_fn(|i| 0xC3 ^ (i as u8)); let plaintext: Vec = (0..171).map(|i| (i * 7 + i / 16) as u8).collect(); let encryptor = || { - let (enc, got) = Cfb8::::do_encrypt_init_rng( + let (enc, got) = Cfb8::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::<16>::new(iv), ) @@ -379,7 +389,7 @@ fn chunking_matches_a_single_call_at_every_batch_remainder() { enc }; let decryptor = || { - Cfb8::::do_decrypt_init(&key, &iv).expect("decrypt init") + Cfb8::::do_decrypt_init(&key, &iv).expect("decrypt init") }; // The reference: the whole message in one call. @@ -414,8 +424,8 @@ fn chunking_matches_a_single_call_at_every_batch_remainder() { } } - check::("Toy"); - check::("ForwardOnlyToy"); + check::("Toy"); + check::("ForwardOnlyToy"); } /// The pair path in `do_decrypt_inplace` must actually be taken. @@ -645,16 +655,6 @@ fn identical_plaintext_gives_different_ciphertext() { ); } -// ---- key handling ------------------------------------------------------------------------ - -#[test] -fn a_key_of_the_wrong_type_is_rejected() { - let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8) + 1); - let seed = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::Seed).unwrap(); - assert!(ToyCfb8::::do_encrypt_init(&seed).is_err()); - assert!(ToyCfb8::::do_decrypt_init(&seed, &[0u8; TOY_LEN]).is_err()); -} - // ---- every length, no padding ------------------------------------------------------------ /// CFB8 is a stream cipher with a one-byte segment: every length round-trips, the ciphertext is diff --git a/crypto/cipher/tests/modes/cfb_tests.rs b/crypto/cipher/tests/modes/cfb_tests.rs index 40349d2cd..2bce830e8 100644 --- a/crypto/cipher/tests/modes/cfb_tests.rs +++ b/crypto/cipher/tests/modes/cfb_tests.rs @@ -17,29 +17,35 @@ mod common; use bouncycastle_cipher::modes::{Cbc, Cfb, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; -use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyCfb = Cfb; -type SwappedCfb = Cfb; -type ForwardOnlyCfb = Cfb; -type SwappedFourCfb = Cfb; +type ToyCfb = Cfb; +type SwappedCfb = Cfb; +type ForwardOnlyCfb = Cfb; +type SwappedFourCfb = Cfb; /// `do_encrypt_inplace`, by value. -fn enc(e: &mut impl StreamCipherEncryptor, plaintext: &[u8]) -> Vec { +fn enc( + e: &mut impl StreamCipherEncryptor, + plaintext: &[u8], +) -> Vec { let mut data = plaintext.to_vec(); e.do_encrypt_inplace(&mut data).unwrap(); data } /// `do_decrypt_inplace`, by value. -fn dec(d: &mut impl StreamCipherDecryptor, ciphertext: &[u8]) -> Vec { +fn dec( + d: &mut impl StreamCipherDecryptor, + ciphertext: &[u8], +) -> Vec { let mut data = ciphertext.to_vec(); d.do_decrypt_inplace(&mut data).unwrap(); data @@ -47,7 +53,7 @@ fn dec(d: &mut impl StreamCipherDecryptor, ciphertext: &[u8]) /// `do_encrypt_inplace` in `chunk`-byte calls, by value. The last call may be shorter. fn enc_chunked( - e: &mut impl StreamCipherEncryptor, + e: &mut impl StreamCipherEncryptor, plaintext: &[u8], chunk: usize, ) -> Vec { @@ -60,7 +66,7 @@ fn enc_chunked( /// `do_decrypt_inplace` in `chunk`-byte calls, by value. The last call may be shorter. fn dec_chunked( - d: &mut impl StreamCipherDecryptor, + d: &mut impl StreamCipherDecryptor, ciphertext: &[u8], chunk: usize, ) -> Vec { @@ -106,7 +112,7 @@ const CHUNKINGS: [usize; 12] = [1, 3, 5, 7, 15, 16, 17, 31, 32, 33, 64, 100]; #[test] fn cfb_conforms_to_the_stream_cipher_framework() { TestFrameworkStreamCipher::new() - .test::, ToyCfb>(); + .test::, ToyCfb>(); } // ---- the spec equations ------------------------------------------------------------------- @@ -152,7 +158,8 @@ fn reference_cfb(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> fn the_mode_matches_the_spec_equations() { let key = toy_key(); let iv = pinned_iv(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); for len in [5 * TOY_LEN, 5 * TOY_LEN + 9, TOY_LEN - 1, 1] { let plaintext = message(len); @@ -191,9 +198,11 @@ fn the_mode_matches_the_spec_equations() { ); // ...and CFB is not CBC: CBC computes `CIPH_K(P1 XOR IV)`, CFB computes `P1 XOR CIPH_K(IV)`. - let (mut cbc, _) = - Cbc::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)) - .unwrap(); + let (mut cbc, _) = Cbc::::do_encrypt_init_rng( + &key, + &mut pinned_rng(iv), + ) + .unwrap(); let mut cbc_c1: [u8; TOY_LEN] = plaintext[..TOY_LEN].try_into().unwrap(); cbc.do_encrypt_blocks_inplace(cbc_c1.as_chunks_mut().0).unwrap(); assert_ne!(&cbc_c1[..], &ct[..TOY_LEN], "CFB must not agree with CBC"); @@ -210,7 +219,8 @@ fn the_mode_matches_the_spec_equations() { fn the_final_short_segment_is_xored_with_the_leading_keystream_bytes() { let key = toy_key(); let iv = pinned_iv(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); let mut o1 = iv; perm.encrypt_block(&mut o1); @@ -385,20 +395,19 @@ fn call_chunking_does_not_change_the_result() { /// single-call-versus-chunked comparison, kept free of an AES dependency. #[test] fn chunking_matches_a_single_call_over_several_batches() { - fn check(name: &str) + fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { let key_bytes: [u8; KEY_LEN] = core::array::from_fn(|i| (i as u8).wrapping_mul(31).wrapping_add(7)); - let key = - KeyMaterial::::from_bytes_as_type(&key_bytes, KeyType::SymmetricCipherKey) - .expect("a valid key"); + let key = K::from_bytes(&key_bytes).expect("a valid key"); let iv: [u8; 16] = core::array::from_fn(|i| 0xC3 ^ (i as u8)); let plaintext: Vec = (0..171).map(|i| (i * 7 + i / 16) as u8).collect(); let encryptor = || { - let (enc, got) = Cfb::::do_encrypt_init_rng( + let (enc, got) = Cfb::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::<16>::new(iv), ) @@ -406,8 +415,9 @@ fn chunking_matches_a_single_call_over_several_batches() { assert_eq!(got, iv, "{name}: the pinned RNG should reproduce the IV"); enc }; - let decryptor = - || Cfb::::do_decrypt_init(&key, &iv).expect("decrypt init"); + let decryptor = || { + Cfb::::do_decrypt_init(&key, &iv).expect("decrypt init") + }; // The reference: the whole message in one call. let mut reference = plaintext.clone(); @@ -441,8 +451,8 @@ fn chunking_matches_a_single_call_over_several_batches() { } } - check::("Toy"); - check::("ForwardOnlyToy"); + check::("Toy"); + check::("ForwardOnlyToy"); } /// The pair path in `do_decrypt_inplace` must actually be taken, and only where a pair of whole @@ -712,16 +722,6 @@ fn identical_plaintext_gives_different_ciphertext() { ); } -// ---- key handling ------------------------------------------------------------------------ - -#[test] -fn a_key_of_the_wrong_type_is_rejected() { - let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8) + 1); - let seed = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::Seed).unwrap(); - assert!(ToyCfb::::do_encrypt_init(&seed).is_err()); - assert!(ToyCfb::::do_decrypt_init(&seed, &[0u8; TOY_LEN]).is_err()); -} - // ---- every length, no padding ------------------------------------------------------------ /// CFB is a stream cipher: every length round-trips, the ciphertext is exactly as long as the @@ -767,6 +767,6 @@ fn sizes_match_the_documented_memory_table() { // The docs say CFB is one `usize` bigger than CBC. assert_eq!( size_of::>(), - size_of::>() + size_of::() + size_of::>() + size_of::() ); } diff --git a/crypto/cipher/tests/modes/common/mod.rs b/crypto/cipher/tests/modes/common/mod.rs index 91e6f5510..77a7b319e 100644 --- a/crypto/cipher/tests/modes/common/mod.rs +++ b/crypto/cipher/tests/modes/common/mod.rs @@ -23,7 +23,11 @@ use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::Algorithm; +use bouncycastle_core::traits::{Algorithm, SymmetricCipherKey}; + +#[path = "../../common/toy_block_cipher.rs"] +pub mod toy_block_cipher; +pub use toy_block_cipher::ToyCipherKey; /// Block and key length of the toy ciphers, chosen to match AES so the tests exercise the same /// shapes the real thing will. @@ -69,7 +73,7 @@ impl ModeNames for Toy { const GCM_ALG_NAME: &'static str = "Toy_GCM"; } -impl ElectronicCodeBook for Toy { +impl ElectronicCodeBook for Toy { fn new(key: &KeyMaterial) -> Result { validate(key)?; let mut bytes = [0u8; TOY_LEN]; @@ -142,7 +146,7 @@ impl ModeNames for SwappedPairToy { const GCM_ALG_NAME: &'static str = "SwappedPairToy_GCM"; } -impl ElectronicCodeBook for SwappedPairToy { +impl ElectronicCodeBook for SwappedPairToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } @@ -215,7 +219,7 @@ impl ModeNames for ForwardOnlyToy { const GCM_ALG_NAME: &'static str = "ForwardOnlyToy_GCM"; } -impl ElectronicCodeBook for ForwardOnlyToy { +impl ElectronicCodeBook for ForwardOnlyToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } @@ -270,7 +274,7 @@ impl ModeNames for SwappedFourToy { const GCM_ALG_NAME: &'static str = "SwappedFourToy_GCM"; } -impl ElectronicCodeBook for SwappedFourToy { +impl ElectronicCodeBook for SwappedFourToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } @@ -306,9 +310,8 @@ impl ElectronicCodeBook for SwappedFourToy { } } -/// Builds a `KeyMaterial` for the toys from a fixed non-zero pattern. -pub fn toy_key() -> KeyMaterial { +/// Builds a [`ToyCipherKey`] for the toys from a fixed non-zero pattern. +pub fn toy_key() -> ToyCipherKey { let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey) - .expect("a valid toy key") + ToyCipherKey::from_bytes(&bytes).expect("a valid toy key") } diff --git a/crypto/cipher/tests/modes/ctr_tests.rs b/crypto/cipher/tests/modes/ctr_tests.rs index ee37e51bd..e1dc88289 100644 --- a/crypto/cipher/tests/modes/ctr_tests.rs +++ b/crypto/cipher/tests/modes/ctr_tests.rs @@ -27,43 +27,49 @@ use bouncycastle_cipher::modes::{Ctr, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; -use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; /// The default shape under test: a 12-byte nonce, so a 4-byte counter. const NONCE_LEN: usize = 12; -type ToyCtr = Ctr; -type SwappedCtr = Ctr; -type ForwardOnlyCtr = Ctr; -type SwappedFourCtr = Ctr; +type ToyCtr = Ctr; +type SwappedCtr = Ctr; +type ForwardOnlyCtr = Ctr; +type SwappedFourCtr = Ctr; /// A 15-byte nonce leaves a **1-byte** counter, so the whole counter space is 256 blocks -- 4 KiB /// of keystream. That makes the exhaustion behaviour reachable in a test. const SHORT_CTR_NONCE_LEN: usize = 15; -type TinyCtr = Ctr; +type TinyCtr = Ctr; /// Capacity of a 1-byte counter, in bytes. const TINY_CAPACITY: usize = 256 * TOY_LEN; -fn enc(e: &mut impl StreamCipherEncryptor, plaintext: &[u8]) -> Vec { +fn enc( + e: &mut impl StreamCipherEncryptor, + plaintext: &[u8], +) -> Vec { let mut data = plaintext.to_vec(); e.do_encrypt_inplace(&mut data).unwrap(); data } -fn dec(d: &mut impl StreamCipherDecryptor, ciphertext: &[u8]) -> Vec { +fn dec( + d: &mut impl StreamCipherDecryptor, + ciphertext: &[u8], +) -> Vec { let mut data = ciphertext.to_vec(); d.do_decrypt_inplace(&mut data).unwrap(); data } fn dec_chunked( - d: &mut impl StreamCipherDecryptor, + d: &mut impl StreamCipherDecryptor, ciphertext: &[u8], chunk: usize, ) -> Vec { @@ -104,7 +110,7 @@ const CHUNKINGS: [usize; 12] = [1, 3, 5, 7, 15, 16, 17, 31, 32, 33, 64, 100]; #[test] fn ctr_conforms_to_the_stream_cipher_framework() { TestFrameworkStreamCipher::new() - .test::, ToyCtr>(); + .test::, ToyCtr>(); } // ---- the spec equations ------------------------------------------------------------------- @@ -142,7 +148,8 @@ fn reference_ctr(perm: &Toy, nonce: [u8; NONCE_LEN], input: &[u8]) -> Vec { fn the_mode_matches_the_spec_equations() { let key = toy_key(); let nonce = pinned_nonce(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); for len in [1, TOY_LEN - 1, TOY_LEN, TOY_LEN + 1, 5 * TOY_LEN, 5 * TOY_LEN + 9] { let plaintext = message(len); @@ -216,14 +223,16 @@ fn check_counter_blocks(blocks: usize) { } let key = toy_key(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); let nonce: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(13).wrapping_add(5)); - let (mut e, got) = Ctr::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::::new(nonce), - ) - .unwrap(); + let (mut e, got) = + Ctr::::do_encrypt_init_rng( + &key, + &mut FixedSeedRNG::::new(nonce), + ) + .unwrap(); assert_eq!(got, nonce); let mut keystream = vec![0u8; blocks * TOY_LEN]; e.do_encrypt_inplace(&mut keystream).expect("the run must fit in the counter space"); @@ -391,11 +400,12 @@ fn the_counter_limit_is_enforced_at_two_bytes_too() { let nonce: [u8; NONCE] = core::array::from_fn(|i| 0x3C ^ (i as u8)); let encryptor = || { - let (e, got) = Ctr::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::::new(nonce), - ) - .unwrap(); + let (e, got) = + Ctr::::do_encrypt_init_rng( + &key, + &mut FixedSeedRNG::::new(nonce), + ) + .unwrap(); assert_eq!(got, nonce); e }; @@ -508,20 +518,19 @@ fn call_chunking_does_not_change_the_result() { /// modes, kept free of an AES dependency. #[test] fn chunking_matches_a_single_call_over_several_batches() { - fn check(name: &str) + fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + K: SymmetricCipherKey, + P: ElectronicCodeBook + ModeNames, { let key_bytes: [u8; KEY_LEN] = core::array::from_fn(|i| (i as u8).wrapping_mul(31).wrapping_add(7)); - let key = - KeyMaterial::::from_bytes_as_type(&key_bytes, KeyType::SymmetricCipherKey) - .expect("a valid key"); + let key = K::from_bytes(&key_bytes).expect("a valid key"); let nonce: [u8; 12] = core::array::from_fn(|i| 0xC3 ^ (i as u8)); let plaintext: Vec = (0..171).map(|i| (i * 7 + i / 16) as u8).collect(); let encryptor = || { - let (e, got) = Ctr::::do_encrypt_init_rng( + let (e, got) = Ctr::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::<12>::new(nonce), ) @@ -530,7 +539,7 @@ fn chunking_matches_a_single_call_over_several_batches() { e }; let decryptor = || { - Ctr::::do_decrypt_init(&key, &nonce) + Ctr::::do_decrypt_init(&key, &nonce) .expect("decrypt init") }; @@ -564,8 +573,8 @@ fn chunking_matches_a_single_call_over_several_batches() { } } - check::("Toy"); - check::("ForwardOnlyToy"); + check::("Toy"); + check::("ForwardOnlyToy"); } /// The pair path must be taken, **in both directions** -- unlike CBC and CFB, CTR encryption @@ -661,16 +670,6 @@ fn identical_plaintext_gives_different_ciphertext() { assert_ne!(first[..TOY_LEN], first[TOY_LEN..], "the counter should change the keystream"); } -// ---- key handling -------------------------------------------------------------------------- - -#[test] -fn a_key_of_the_wrong_type_is_rejected() { - let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8) + 1); - let seed = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::Seed).unwrap(); - assert!(ToyCtr::::do_encrypt_init(&seed).is_err()); - assert!(ToyCtr::::do_decrypt_init(&seed, &[0u8; NONCE_LEN]).is_err()); -} - // ---- every length -------------------------------------------------------------------------- /// CTR is a stream cipher: every length round-trips and the ciphertext is exactly as long as the @@ -705,18 +704,21 @@ fn every_permitted_nonce_length_works() { let nonce: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(11).wrapping_add(3)); let plaintext = (0..100u8).collect::>(); - let (mut e, got) = Ctr::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::::new(nonce), - ) - .unwrap(); + let (mut e, got) = + Ctr::::do_encrypt_init_rng( + &key, + &mut FixedSeedRNG::::new(nonce), + ) + .unwrap(); assert_eq!(got, nonce); let mut ct = plaintext.clone(); e.do_encrypt_inplace(&mut ct).unwrap(); assert_ne!(ct, plaintext, "nonce length {N}: must actually encrypt"); - Ctr::::decrypt_inplace(&key, &nonce, &mut ct) - .unwrap(); + Ctr::::decrypt_inplace( + &key, &nonce, &mut ct, + ) + .unwrap(); assert_eq!(ct, plaintext, "nonce length {N}: round trip"); } diff --git a/crypto/cipher/tests/modes/ecb_tests.rs b/crypto/cipher/tests/modes/ecb_tests.rs index 89e100f21..393ddd572 100644 --- a/crypto/cipher/tests/modes/ecb_tests.rs +++ b/crypto/cipher/tests/modes/ecb_tests.rs @@ -17,22 +17,22 @@ use bouncycastle_cipher::modes::hazmat::ecb::Ecb; use bouncycastle_cipher::padding::{PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; -use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyEcb = Ecb; -type SwappedEcb = Ecb; -type SwappedFourEcb = Ecb; +type ToyEcb = Ecb; +type SwappedEcb = Ecb; +type SwappedFourEcb = Ecb; /// The implementor hook `do_encrypt_blocks_inplace`, by value, for tests whose data is /// block-shaped. fn enc_blocks( - enc: &mut impl BlockCipherEncryptor, + enc: &mut impl BlockCipherEncryptor, plaintext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *plaintext; @@ -42,7 +42,7 @@ fn enc_blocks( /// The implementor hook `do_decrypt_blocks_inplace`, by value. fn dec_blocks( - dec: &mut impl BlockCipherDecryptor, + dec: &mut impl BlockCipherDecryptor, ciphertext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *ciphertext; @@ -63,7 +63,7 @@ fn decryptor() -> ToyEcb { #[test] fn ecb_conforms_to_the_block_cipher_framework() { TestFrameworkBlockCipher::new() - .test::, ToyEcb>(); + .test::, ToyEcb>(); } // ---- the spec equations ------------------------------------------------------------------- @@ -97,7 +97,8 @@ fn reference_ecb(perm: &Toy, input: &[[u8; TOY_LEN]], encrypt: bool) -> Vec<[u8; #[test] fn the_mode_matches_the_spec_equations() { let key = toy_key(); - let perm = >::new(&key).unwrap(); + let perm = + >::new(key.get_key()).unwrap(); let plaintext: [[u8; TOY_LEN]; 5] = core::array::from_fn(|i| core::array::from_fn(|j| (i * 31 + j * 7 + 1) as u8)); @@ -128,7 +129,7 @@ fn the_mode_matches_the_spec_equations() { // ...and ECB is not CBC: CBC computes CIPH_K(P1 XOR IV), ECB computes CIPH_K(P1). let iv: [u8; TOY_LEN] = core::array::from_fn(|i| 0xF0 ^ (i as u8)); - let (mut cbc, _) = Cbc::::do_encrypt_init_rng( + let (mut cbc, _) = Cbc::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::::new(iv), ) @@ -346,24 +347,16 @@ fn a_ciphertext_bit_error_affects_only_its_own_block() { } } -// ---- key handling ------------------------------------------------------------------------ - -#[test] -fn a_key_of_the_wrong_type_is_rejected() { - let bytes: [u8; TOY_LEN] = core::array::from_fn(|i| (i as u8) + 1); - let seed = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::Seed).unwrap(); - assert!(ToyEcb::::do_encrypt_init(&seed).is_err()); - assert!(ToyEcb::::do_decrypt_init(&seed, &[]).is_err()); -} - // ---- composition with the padding layer -------------------------------------------------- /// ECB is block-aligned by contract, so arbitrary-length data goes through `bouncycastle_cipher::padding` /// like the other modes; its `INIT_DATA_LEN` of 0 flows through the adapters as an empty array. #[test] fn the_padding_layer_round_trips_every_length() { - type Enc = PaddedBlockCipherEncryptor, PKCS7, TOY_LEN, 0, TOY_LEN>; - type Dec = PaddedBlockCipherDecryptor, PKCS7, TOY_LEN, 0, TOY_LEN>; + type Enc = + PaddedBlockCipherEncryptor, PKCS7, ToyCipherKey, TOY_LEN, 0, TOY_LEN>; + type Dec = + PaddedBlockCipherDecryptor, PKCS7, ToyCipherKey, TOY_LEN, 0, TOY_LEN>; for len in 0..=(3 * TOY_LEN + 1) { let plaintext: Vec = (0..len).map(|i| (i * 5 + 3) as u8).collect(); @@ -391,6 +384,6 @@ fn sizes_match_the_documented_memory_table() { // One block smaller than CBC, which stores a chaining value. assert_eq!( size_of::>() + TOY_LEN, - size_of::>() + size_of::>() ); } diff --git a/crypto/cipher/tests/modes/gcm_tests.rs b/crypto/cipher/tests/modes/gcm_tests.rs index fb7d6060d..57c5f592e 100644 --- a/crypto/cipher/tests/modes/gcm_tests.rs +++ b/crypto/cipher/tests/modes/gcm_tests.rs @@ -15,9 +15,9 @@ use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; -use common::{ForwardOnlyToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyGcm = Gcm; +type ToyGcm = Gcm; /// Encrypts `message` under `aad` through the detached one-shot, with the nonce driven by `seed` /// so repeated calls are comparable. Returns the nonce, the ciphertext and the tag. @@ -270,24 +270,26 @@ fn streaming_detached_final_zeroizes_its_buffer_on_forgery() { fn neither_direction_uses_the_inverse_cipher() { fn round_trip

() -> ([u8; 48], [u8; 16]) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + P: bouncycastle_core::hazmat::ElectronicCodeBook + + ModeNames, { let key = toy_key(); let aad = b"associated data of no particular length"; let message = b"a message that is not a whole number of blocks!!"; let mut ct = [0u8; 48]; - let (nonce, _, tag) = Gcm::::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::<12>::new([0x4Du8; 12]), - aad, - message, - &mut ct, - ) - .unwrap(); + let (nonce, _, tag) = + Gcm::::encrypt_detached_rng_out( + &key, + &mut FixedSeedRNG::<12>::new([0x4Du8; 12]), + aad, + message, + &mut ct, + ) + .unwrap(); assert_ne!(&ct[..], &message[..]); let mut pt = [0u8; 48]; - Gcm::::decrypt_detached_out( + Gcm::::decrypt_detached_out( &key, &nonce, aad, &ct, &tag, &mut pt, ) .unwrap(); @@ -310,10 +312,10 @@ fn neither_direction_uses_the_inverse_cipher() { fn aead_trait_framework() { use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::, ToyGcm>( + .test_encryptor_decryptor::, ToyGcm>( ); TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::, ToyGcm>( + .test_encryptor_decryptor::, ToyGcm>( ); } @@ -340,7 +342,7 @@ fn aead_trait_one_shots_release_nothing_on_forgery() { let tag: [u8; 16] = ct[32..48].try_into().unwrap(); let mut out = [0xEEu8; 32]; assert!(matches!( - >::decrypt_detached_out( + >::decrypt_detached_out( &key, &nonce, b"aad", diff --git a/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs b/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs index 24eb59870..cafa64615 100644 --- a/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs +++ b/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs @@ -20,17 +20,17 @@ mod common; use bouncycastle_cipher::modes::{Cfb, Cfb8, Ctr}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::SymmetricCipherKey; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSymmetricCipher; -use common::{TOY_LEN, Toy, toy_key}; +use common::{TOY_LEN, Toy, ToyCipherKey, toy_key}; -type ToyCfb

= Cfb; -type ToyCfb8 = Cfb8; -type ToyCtr = Ctr; +type ToyCfb = Cfb; +type ToyCfb8 = Cfb8; +type ToyCtr = Ctr; /// All three stream modes must satisfy the shared conformance suite for the symmetric-cipher /// traits -- the same suite the padded adapters run, with `required_alignment` left at 1 because a @@ -45,11 +45,11 @@ type ToyCtr = Ctr; fn the_stream_modes_conform_to_the_symmetric_cipher_suite() { let framework = TestFrameworkSymmetricCipher::new(); framework - .test_encryptor_decryptor::, ToyCfb>(); + .test_encryptor_decryptor::, ToyCfb>(); framework - .test_encryptor_decryptor::, ToyCfb8>( + .test_encryptor_decryptor::, ToyCfb8>( ); - framework.test_encryptor_decryptor::, ToyCtr>(); + framework.test_encryptor_decryptor::, ToyCtr>(); } /// The separate-output API must produce exactly what the in-place API produces, for the same key @@ -57,14 +57,13 @@ fn the_stream_modes_conform_to_the_symmetric_cipher_suite() { /// the check that the bridge adds nothing and loses nothing. #[test] fn the_two_apis_agree_byte_for_byte() { - fn check( - name: &str, - key: &KeyMaterial, - ) where - E: StreamCipherEncryptor - + SymmetricCipherEncryptor, - D: StreamCipherDecryptor - + SymmetricCipherDecryptor, + fn check(name: &str, key: &K) + where + K: SymmetricCipherKey, + E: StreamCipherEncryptor + + SymmetricCipherEncryptor, + D: StreamCipherDecryptor + + SymmetricCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 63, 64, 171] { let plaintext: Vec = (0..len).map(|i| (i * 7 + 1) as u8).collect(); @@ -76,7 +75,7 @@ fn the_two_apis_agree_byte_for_byte() { // The separate-output API, under the same init data. let mut dec_as_sym = - >::do_decrypt_init( + >::do_decrypt_init( key, &init, ) .unwrap(); @@ -90,9 +89,15 @@ fn the_two_apis_agree_byte_for_byte() { } } - check::, ToyCfb, TOY_LEN, TOY_LEN>("Cfb", &toy_key()); - check::, ToyCfb8, TOY_LEN, TOY_LEN>("Cfb8", &toy_key()); - check::, ToyCtr, TOY_LEN, 12>("Ctr", &toy_key()); + check::, ToyCfb, ToyCipherKey, TOY_LEN, TOY_LEN>( + "Cfb", + &toy_key(), + ); + check::, ToyCfb8, ToyCipherKey, TOY_LEN, TOY_LEN>( + "Cfb8", + &toy_key(), + ); + check::, ToyCtr, ToyCipherKey, TOY_LEN, 12>("Ctr", &toy_key()); } /// The separate-output API must leave the caller's input untouched. That is the whole reason a @@ -104,11 +109,13 @@ fn the_input_buffer_is_not_modified() { let plaintext: Vec = (0..100u8).collect(); let original = plaintext.clone(); - let (mut enc, _init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + let (mut enc, _init) = as SymmetricCipherEncryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::do_encrypt_init(&key) + .unwrap(); let mut ciphertext = vec![0u8; plaintext.len()]; enc.do_encrypt_out(&plaintext, &mut ciphertext).unwrap(); @@ -122,19 +129,23 @@ fn the_length_predictions_are_exact() { let key = toy_key(); for len in [0usize, 1, 15, 16, 17, 1000] { assert_eq!( - as SymmetricCipherEncryptor>::encrypt_out_len(len), + as SymmetricCipherEncryptor>::encrypt_out_len(len), len, "encrypt_out_len is the identity" ); assert_eq!( - as SymmetricCipherDecryptor>::decrypt_out_len(len), + as SymmetricCipherDecryptor>::decrypt_out_len(len), len, "decrypt_out_len is exact, not an upper bound" ); - let (enc, _) = - as SymmetricCipherEncryptor>::do_encrypt_init(&key) - .unwrap(); + let (enc, _) = as SymmetricCipherEncryptor< + ToyCipherKey, + TOY_LEN, + 12, + 0, + >>::do_encrypt_init(&key) + .unwrap(); assert_eq!(enc.do_encrypt_out_len(len), len, "update_out_len is the identity"); } } @@ -148,11 +159,13 @@ fn a_short_output_buffer_is_refused_without_consuming_anything() { let key = toy_key(); let plaintext: Vec = (0..32u8).collect(); - let (mut enc, init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + let (mut enc, init) = as SymmetricCipherEncryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::do_encrypt_init(&key) + .unwrap(); let mut too_small = vec![0u8; plaintext.len() - 1]; match enc.do_encrypt_out(&plaintext, &mut too_small) { @@ -194,11 +207,13 @@ fn a_short_output_buffer_is_refused_when_decrypting_too() { let mut ciphertext = plaintext.clone(); enc.do_encrypt_inplace(&mut ciphertext).unwrap(); - let mut dec = - as SymmetricCipherDecryptor>::do_decrypt_init( - &key, &init, - ) - .unwrap(); + let mut dec = as SymmetricCipherDecryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::do_decrypt_init(&key, &init) + .unwrap(); let mut too_small = vec![0u8; ciphertext.len() - 1]; match dec.do_decrypt_out(&ciphertext, &mut too_small) { @@ -217,11 +232,13 @@ fn a_short_output_buffer_is_refused_when_decrypting_too() { // An oversized buffer is fine, the data lands in the leading bytes and the rest is zeroed: the // check is "too short", not "not exactly equal". let mut oversized = vec![0xAAu8; ciphertext.len() + 8]; - let mut dec = - as SymmetricCipherDecryptor>::do_decrypt_init( - &key, &init, - ) - .unwrap(); + let mut dec = as SymmetricCipherDecryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::do_decrypt_init(&key, &init) + .unwrap(); let n = dec.do_decrypt_out(&ciphertext, &mut oversized).expect("an oversized buffer is fine"); assert_eq!(n, ciphertext.len()); assert_eq!(&oversized[..n], &plaintext[..], "the data lands in the leading bytes"); @@ -237,36 +254,51 @@ fn the_allocating_one_shots_round_trip() { let message = b"a message of no particular length at all"; // CFB128 - let (iv, ct) = as SymmetricCipherEncryptor>::encrypt( - &key, message, - ) + let (iv, ct) = as SymmetricCipherEncryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::encrypt(&key, message) .unwrap(); assert_eq!(ct.len(), message.len(), "a stream cipher does not change the length"); - let back = as SymmetricCipherDecryptor>::decrypt( - &key, &iv, &ct, - ) + let back = as SymmetricCipherDecryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::decrypt(&key, &iv, &ct) .unwrap(); assert_eq!(back, message); // CFB8 - let (iv, ct) = as SymmetricCipherEncryptor>::encrypt( - &key, message, - ) + let (iv, ct) = as SymmetricCipherEncryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::encrypt(&key, message) .unwrap(); - let back = as SymmetricCipherDecryptor>::decrypt( - &key, &iv, &ct, - ) + let back = as SymmetricCipherDecryptor< + ToyCipherKey, + TOY_LEN, + TOY_LEN, + 0, + >>::decrypt(&key, &iv, &ct) .unwrap(); assert_eq!(back, message); // CTR let (nonce, ct) = - as SymmetricCipherEncryptor>::encrypt(&key, message) - .unwrap(); + as SymmetricCipherEncryptor>::encrypt( + &key, message, + ) + .unwrap(); assert_eq!(nonce.len(), 12, "CTR's init data is its 12-byte nonce"); - let back = as SymmetricCipherDecryptor>::decrypt( - &key, &nonce, &ct, - ) - .unwrap(); + let back = + as SymmetricCipherDecryptor>::decrypt( + &key, &nonce, &ct, + ) + .unwrap(); assert_eq!(back, message); } diff --git a/crypto/cipher/tests/padding/padded_tests.rs b/crypto/cipher/tests/padding/padded_tests.rs index a0b2d2574..5ef9dd4e3 100644 --- a/crypto/cipher/tests/padding/padded_tests.rs +++ b/crypto/cipher/tests/padding/padded_tests.rs @@ -8,12 +8,12 @@ use bouncycastle_cipher::padding::{ NoPadding, PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor, }; -use bouncycastle_core::errors::{KeyMaterialError, PaddingError, SymmetricCipherError}; +use bouncycastle_core::errors::{KeyMaterialError, PaddingError, RNGError, SymmetricCipherError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; @@ -22,6 +22,25 @@ use bouncycastle_rng::hash_drbg80090a::{HashDRBG80090A, HashDRBG80090AParams_SHA const B: usize = 8; +/// The key type for [`ToyCbc`]. The test framework's `ToyCipherKey` is 16 bytes, so this +/// 8-byte toy needs its own; the type check is the toy's, in `check_key`. +#[derive(Clone, PartialEq, Eq)] +struct ToyKey(KeyMaterial); + +impl SymmetricCipherKey for ToyKey { + fn from_keymaterial(key: KeyMaterial) -> Result { + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut HashDRBG80090A::::new_from_os()) + } +} + /// c_j = p_j ^ c_{j-1} ^ key ; p_j = c_j ^ c_{j-1} ^ key struct ToyCbc { key: [u8; B], @@ -47,16 +66,16 @@ impl Algorithm for ToyCbc { const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; } -impl BlockCipherEncryptor for ToyCbc { - fn do_encrypt_init(key: &KeyMaterial) -> Result<(Self, [u8; B]), SymmetricCipherError> { +impl BlockCipherEncryptor for ToyCbc { + fn do_encrypt_init(key: &ToyKey) -> Result<(Self, [u8; B]), SymmetricCipherError> { let mut rng = HashDRBG80090A::::new_from_os(); Self::do_encrypt_init_rng(key, &mut rng) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &ToyKey, rng: &mut dyn RNG, ) -> Result<(Self, [u8; B]), SymmetricCipherError> { - let key = Self::check_key(key)?; + let key = Self::check_key(key.get_key())?; let mut iv = [0u8; B]; rng.next_bytes_out(&mut iv)?; Ok((Self { key, chain: iv }, iv)) @@ -75,9 +94,9 @@ impl BlockCipherEncryptor for ToyCbc { } } -impl BlockCipherDecryptor for ToyCbc { - fn do_decrypt_init(key: &KeyMaterial, iv: &[u8; B]) -> Result { - Ok(Self { key: Self::check_key(key)?, chain: *iv }) +impl BlockCipherDecryptor for ToyCbc { + fn do_decrypt_init(key: &ToyKey, iv: &[u8; B]) -> Result { + Ok(Self { key: Self::check_key(key.get_key())?, chain: *iv }) } fn do_decrypt_blocks_inplace( &mut self, @@ -95,14 +114,14 @@ impl BlockCipherDecryptor for ToyCbc { } } -type Enc = PaddedBlockCipherEncryptor; -type Dec = PaddedBlockCipherDecryptor; +type Enc = PaddedBlockCipherEncryptor; +type Dec = PaddedBlockCipherDecryptor; /// The same adapters over `NoPadding`: an alignment check rather than a padding scheme. -type EncNP = PaddedBlockCipherEncryptor; -type DecNP = PaddedBlockCipherDecryptor; +type EncNP = PaddedBlockCipherEncryptor; +type DecNP = PaddedBlockCipherDecryptor; -fn key() -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(&[0x5a; B], KeyType::SymmetricCipherKey).unwrap() +fn key() -> ToyKey { + ToyKey::from_bytes(&[0x5a; B]).unwrap() } fn msg(len: usize) -> Vec { @@ -111,14 +130,14 @@ fn msg(len: usize) -> Vec { #[test] fn toy_cipher_passes_core_test_framework() { - TestFrameworkBlockCipher::new().test::(); + TestFrameworkBlockCipher::new().test::(); } /// The padded adapters are the first implementors of `SymmetricCipherEncryptor` / /// `SymmetricCipherDecryptor`, so this is also what exercises those traits' provided one-shots. #[test] fn padded_adapters_pass_the_symmetric_cipher_framework() { - TestFrameworkSymmetricCipher::new().test_encryptor_decryptor::(); + TestFrameworkSymmetricCipher::new().test_encryptor_decryptor::(); } #[test] @@ -303,7 +322,10 @@ fn output_buffer_too_small_reports_required_length() { #[test] fn wrong_key_type_is_rejected_by_adapters() { - let mac_key = KeyMaterial::::from_bytes_as_type(&[1u8; B], KeyType::MACKey).unwrap(); + let mac_key = ToyKey::from_keymaterial( + KeyMaterial::::from_bytes_as_type(&[1u8; B], KeyType::MACKey).unwrap(), + ) + .unwrap(); assert!(matches!( Enc::do_encrypt_init(&mac_key), Err(SymmetricCipherError::KeyMaterialError(_)) @@ -323,7 +345,7 @@ fn wrong_key_type_is_rejected_by_adapters() { fn no_padding_adapters_pass_the_symmetric_cipher_framework() { let mut framework = TestFrameworkSymmetricCipher::new(); framework.required_alignment = B; - framework.test_encryptor_decryptor::(); + framework.test_encryptor_decryptor::(); } /// An aligned message passes through with its length unchanged -- no final block is added -- and the diff --git a/crypto/cipher/tests/suspend_tests.rs b/crypto/cipher/tests/suspend_tests.rs index 6f5412723..297e41d7a 100644 --- a/crypto/cipher/tests/suspend_tests.rs +++ b/crypto/cipher/tests/suspend_tests.rs @@ -12,30 +12,29 @@ use bouncycastle_cipher::modes::hazmat::ecb::Ecb; use bouncycastle_cipher::modes::{Cbc, CcmPacket, Cfb, Cfb8, Ctr, Gcm}; use bouncycastle_cipher::padding::{PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SymmetricCipherEncryptor, SymmetricCipherKey, }; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; #[path = "common/toy_block_cipher.rs"] mod toy_block_cipher; -use toy_block_cipher::ToyBlockCipher; +use toy_block_cipher::{ToyBlockCipher, ToyCipherKey}; -type ToyEcb = Ecb; -type ToyCbc = Cbc; -type ToyCfb = Cfb; -type ToyCfb8 = Cfb8; -type ToyCtr = Ctr; -type ToyGcm = Gcm; -type ToyCcm = CcmPacket; -type ToyPaddedEnc = PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>; -type ToyPaddedDec = PaddedBlockCipherDecryptor, PKCS7, 16, 16, 16>; +type ToyEcb = Ecb; +type ToyCbc = Cbc; +type ToyCfb = Cfb; +type ToyCfb8 = Cfb8; +type ToyCtr = Ctr; +type ToyGcm = Gcm; +type ToyCcm = CcmPacket; +type ToyPaddedEnc = PaddedBlockCipherEncryptor, PKCS7, ToyCipherKey, 16, 16, 16>; +type ToyPaddedDec = PaddedBlockCipherDecryptor, PKCS7, ToyCipherKey, 16, 16, 16>; -fn key() -> KeyMaterial<16> { - KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap() +fn key() -> ToyCipherKey { + ToyCipherKey::from_bytes(&[0x42; 16]).unwrap() } fn message(len: usize) -> Vec { @@ -46,7 +45,7 @@ fn message(len: usize) -> Vec { /// with `finish`. Whatever `finish` returns must be identical for the two. fn round_trip(cipher: C, finish: impl Fn(C) -> Vec) -> Vec where - C: SuspendableKeyed> + Clone, + C: SuspendableKeyed + Clone, { let key = key(); TestFrameworkSuspendableKeyedState::new().test(&cipher, &key); diff --git a/crypto/cipher/tests/toy_block_cipher_tests.rs b/crypto/cipher/tests/toy_block_cipher_tests.rs index 92b9aeb71..9310998e6 100644 --- a/crypto/cipher/tests/toy_block_cipher_tests.rs +++ b/crypto/cipher/tests/toy_block_cipher_tests.rs @@ -10,9 +10,10 @@ mod toy_block_cipher; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; -use toy_block_cipher::{TOY_BLOCK_LEN, ToyBlockCipher}; +use toy_block_cipher::{TOY_BLOCK_LEN, ToyBlockCipher, ToyCipherKey}; #[test] fn the_toy_block_cipher_conforms_to_the_trait() { - TestFrameworkElectronicCodeBook::new().test::(); + TestFrameworkElectronicCodeBook::new() + .test::(); } diff --git a/crypto/core-test-framework/src/aead.rs b/crypto/core-test-framework/src/aead.rs index 520c16d36..9d3fc2f82 100644 --- a/crypto/core-test-framework/src/aead.rs +++ b/crypto/core-test-framework/src/aead.rs @@ -18,8 +18,7 @@ use crate::symmetric_ciphers::TestFrameworkSymmetricCipher; use crate::{DUMMY_SEED, FixedSeedRNG}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherKey}; /// Instance of the test framework. pub struct TestFrameworkAEADCipher { @@ -81,8 +80,9 @@ impl TestFrameworkAEADCipher { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, >( &self, ) { @@ -93,13 +93,9 @@ impl TestFrameworkAEADCipher { // No AAD and the tag inline is the plain symmetric-cipher contract. let mut symmetric = TestFrameworkSymmetricCipher::new(); symmetric.fixed_message_len = self.fixed_message_len; - symmetric.test_encryptor_decryptor::(); + symmetric.test_encryptor_decryptor::(); - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); let aad: &[u8] = b"some associated data"; let pinned = [0xA5u8; NONCE_LEN]; @@ -803,23 +799,34 @@ impl TestFrameworkAEADTaggedLayout { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, >( &self, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - Self::round_trip_at_every_length_and_chunking::( - &key, - ); - Self::tampering_and_short_input_are_rejected::( + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); + Self::round_trip_at_every_length_and_chunking::< + KEY_LEN, + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + K, + E, + D, + >(&key); + Self::tampering_and_short_input_are_rejected::< + KEY_LEN, + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + K, + E, + D, + >(&key); + Self::undersized_buffers_are_rejected::( &key, ); - Self::undersized_buffers_are_rejected::(&key); } /// The pinned RNG every encryption draws its nonce from, so that all paths use one nonce. @@ -834,9 +841,10 @@ impl TestFrameworkAEADTaggedLayout { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, >( - key: &KeyMaterial, + key: &K, msg: &[u8], ) -> (Vec, [u8; NONCE_LEN]) { let mut ct = vec![0u8; E::encrypt_out_len(msg.len())]; @@ -853,15 +861,16 @@ impl TestFrameworkAEADTaggedLayout { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, >( - key: &KeyMaterial, + key: &K, ) { for len in 0..=(4 * TAG_LEN + 5) { let msg = &DUMMY_SEED[..len]; let (ct, nonce) = - Self::tagged_ct::(key, msg); + Self::tagged_ct::(key, msg); let mut pt = vec![0u8; D::decrypt_out_len(ct.len())]; let n = D::decrypt_with_aad_out(key, &nonce, AAD, &ct, &mut pt).unwrap(); @@ -950,13 +959,14 @@ impl TestFrameworkAEADTaggedLayout { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, >( - key: &KeyMaterial, + key: &K, ) { let msg = &DUMMY_SEED[..10]; - let (ct, nonce) = Self::tagged_ct::(key, msg); + let (ct, nonce) = Self::tagged_ct::(key, msg); let mut tampered = ct.clone(); tampered[0] ^= 0xFF; @@ -1006,13 +1016,14 @@ impl TestFrameworkAEADTaggedLayout { const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: SymmetricCipherKey, + E: AEADCipherEncryptor, + D: AEADCipherDecryptor, >( - key: &KeyMaterial, + key: &K, ) { let msg = &DUMMY_SEED[..8]; - let (ct, nonce) = Self::tagged_ct::(key, msg); + let (ct, nonce) = Self::tagged_ct::(key, msg); let needed = E::encrypt_out_len(msg.len()); assert_eq!(needed, msg.len() + TAG_LEN); diff --git a/crypto/core-test-framework/src/block_cipher.rs b/crypto/core-test-framework/src/block_cipher.rs index 9f303d024..5a8565a51 100644 --- a/crypto/core-test-framework/src/block_cipher.rs +++ b/crypto/core-test-framework/src/block_cipher.rs @@ -1,12 +1,12 @@ //! Shared conformance tests for [`BlockCipherEncryptor`] / [`BlockCipherDecryptor`] implementors: //! the whole-block refinement of the symmetric cipher traits. -use crate::{DUMMY_SEED, FixedSeedRNG}; +use crate::{DUMMY_SEED, FixedSeedRNG, with_key}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; /// Instance of the test framework. pub struct TestFrameworkBlockCipher { @@ -24,16 +24,13 @@ impl TestFrameworkBlockCipher { const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, - E: BlockCipherEncryptor, - D: BlockCipherDecryptor, + K: SymmetricCipherKey, + E: BlockCipherEncryptor, + D: BlockCipherDecryptor, >( &self, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); // multi-block (two at a time) through the implementor hook `do_*_blocks`: blocks encrypted together // must decrypt both together and one at a time, and blocks encrypted one at a time must @@ -153,7 +150,7 @@ impl TestFrameworkBlockCipher { let mac_key = KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) .unwrap(); - match E::do_encrypt_init(&mac_key) { + match with_key::(mac_key.clone(), |k| E::do_encrypt_init(k)) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("Unexpected error"), }; @@ -187,7 +184,7 @@ impl TestFrameworkBlockCipher { do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); strengths_tested += 1; - match E::do_encrypt_init(&key) { + match with_key::(key.clone(), |k| E::do_encrypt_init(k)) { Ok(_) => { if ss >= &E::MAX_SECURITY_STRENGTH { /* good */ } else { diff --git a/crypto/core-test-framework/src/electronic_code_book.rs b/crypto/core-test-framework/src/electronic_code_book.rs index d6ccfc49a..6ea80e26b 100644 --- a/crypto/core-test-framework/src/electronic_code_book.rs +++ b/crypto/core-test-framework/src/electronic_code_book.rs @@ -6,6 +6,7 @@ use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; /// Instance of the test framework. pub struct TestFrameworkElectronicCodeBook { @@ -43,7 +44,8 @@ impl TestFrameworkElectronicCodeBook { pub fn test< const KEY_LEN: usize, const BLOCK_LEN: usize, - P: ElectronicCodeBook, + K: SymmetricCipherKey, + P: ElectronicCodeBook, >( &self, ) { diff --git a/crypto/core-test-framework/src/key_stream.rs b/crypto/core-test-framework/src/key_stream.rs index 4d9019106..e6e6f048c 100644 --- a/crypto/core-test-framework/src/key_stream.rs +++ b/crypto/core-test-framework/src/key_stream.rs @@ -1,11 +1,12 @@ //! Shared conformance tests for [`KeyStream`] implementors. -use crate::DUMMY_SEED; +use crate::{DUMMY_SEED, with_key}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::KeyStream; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::SymmetricCipherKey; /// Instance of the test framework. pub struct TestFrameworkKeyStream { @@ -43,15 +44,12 @@ impl TestFrameworkKeyStream { const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, - KS: KeyStream, + K: SymmetricCipherKey, + KS: KeyStream, >( &self, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); let init_data = [0xA5u8; INIT_DATA_LEN]; let blocks = DUMMY_SEED.as_chunks::().0; let n = blocks.len(); @@ -115,7 +113,7 @@ impl TestFrameworkKeyStream { let mac_key = KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) .unwrap(); - match KS::new(&mac_key, &init_data) { + match with_key::(mac_key.clone(), |k| KS::new(k, &init_data)) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("A key that is not a SymmetricCipherKey should have been rejected"), }; @@ -145,7 +143,7 @@ impl TestFrameworkKeyStream { // Tag the key at an arbitrary strength for the purpose of this test. do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); - match KS::new(&key, &init_data) { + match with_key::(key.clone(), |k| KS::new(k, &init_data)) { Ok(_) => assert!( ss >= &KS::MAX_SECURITY_STRENGTH, "should have required a key at least as strong as the algorithm" diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 5f7ffb288..37b0761cd 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -31,5 +31,20 @@ pub mod xof; mod fixed_seed_rng; pub use fixed_seed_rng::FixedSeedRNG; +use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::SymmetricCipherKey; + +/// Wraps `key` as a `K` and runs `f` on it. A key type that refuses the material and a cipher +/// that refuses the key both report a `KeyMaterialError`, so a framework's key-policy check +/// holds whichever layer enforces the policy. +pub(crate) fn with_key, const KEY_LEN: usize, T>( + key: KeyMaterial, + f: impl FnOnce(&K) -> Result, +) -> Result { + let key = K::from_keymaterial(key)?; + f(&key) +} + /// A dummy seed for use in tests which is \x00..\xFF repeated for 1024 bytes pub const DUMMY_SEED: &[u8; 1024] = 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\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"; diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs index 1f0c6336d..8762e4ac0 100644 --- a/crypto/core-test-framework/src/symmetric_ciphers.rs +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -2,14 +2,14 @@ //! [`SymmetricCipherDecryptor`] and their stream-cipher refinement. The block-cipher and AEAD //! refinements have their own runners in [`crate::block_cipher`] and [`crate::aead`]. -use crate::{DUMMY_SEED, FixedSeedRNG}; +use crate::{DUMMY_SEED, FixedSeedRNG, with_key}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SymmetricCipherEncryptor, SymmetricCipherKey, }; /// Instance of the test framework. @@ -63,16 +63,13 @@ impl TestFrameworkSymmetricCipher { const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + K: SymmetricCipherKey, + E: SymmetricCipherEncryptor, + D: SymmetricCipherDecryptor, >( &self, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); // Enough plaintext lengths to cross several final-chunk boundaries (a block, for padding). let align = self.required_alignment.max(1); let max_len = (3 * FINAL_LEN.max(1) + 5).next_multiple_of(align); @@ -440,11 +437,11 @@ impl TestFrameworkSymmetricCipher { let mac_key = KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) .unwrap(); - match E::do_encrypt_init(&mac_key) { + match with_key::(mac_key.clone(), |k| E::do_encrypt_init(k)) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("A key that is not a SymmetricCipherKey should have been rejected"), }; - match D::do_decrypt_init(&mac_key, &init_data) { + match with_key::(mac_key.clone(), |k| D::do_decrypt_init(k, &init_data)) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("A key that is not a SymmetricCipherKey should have been rejected"), }; @@ -469,7 +466,7 @@ impl TestFrameworkSymmetricCipher { } do_hazardous_operations(&mut key, |key| key.set_security_strength(*ss)).unwrap(); - match E::do_encrypt_init(&key) { + match with_key::(key.clone(), |k| E::do_encrypt_init(k)) { Ok(_) => assert!( ss >= &E::MAX_SECURITY_STRENGTH, "should have required a key at least as strong as the algorithm" @@ -480,7 +477,7 @@ impl TestFrameworkSymmetricCipher { ), _ => panic!("Unexpected error"), }; - match D::do_decrypt_init(&key, &init_data) { + match with_key::(key.clone(), |k| D::do_decrypt_init(k, &init_data)) { Ok(_) => assert!(ss >= &D::MAX_SECURITY_STRENGTH), Err(SymmetricCipherError::KeyMaterialError(_)) => { assert!(ss < &D::MAX_SECURITY_STRENGTH) @@ -510,16 +507,13 @@ impl TestFrameworkStreamCipher { pub fn test< const KEY_LEN: usize, const INIT_DATA_LEN: usize, - E: StreamCipherEncryptor, - D: StreamCipherDecryptor, + K: SymmetricCipherKey, + E: StreamCipherEncryptor, + D: StreamCipherDecryptor, >( &self, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let key = K::from_bytes(DUMMY_SEED[..KEY_LEN].try_into().unwrap()).unwrap(); // one-shot, in place: must round-trip, and report every byte as written. let mut buf = *DUMMY_SEED; @@ -629,11 +623,13 @@ impl TestFrameworkStreamCipher { let mac_key = KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) .unwrap(); - match E::do_encrypt_init(&mac_key) { + match with_key::(mac_key.clone(), |k| E::do_encrypt_init(k)) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("Unexpected error"), }; - match D::do_decrypt_init(&mac_key, &[0u8; INIT_DATA_LEN]) { + match with_key::(mac_key.clone(), |k| { + D::do_decrypt_init(k, &[0u8; INIT_DATA_LEN]) + }) { Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } _ => panic!("Unexpected error"), }; @@ -680,9 +676,15 @@ impl TestFrameworkStreamCipher { } _ => panic!("Unexpected error"), }; - check(E::do_encrypt_init(&key).map(|_| ()), &E::MAX_SECURITY_STRENGTH); check( - D::do_decrypt_init(&key, &[0u8; INIT_DATA_LEN]).map(|_| ()), + with_key::(key.clone(), |k| E::do_encrypt_init(k)).map(|_| ()), + &E::MAX_SECURITY_STRENGTH, + ); + check( + with_key::(key.clone(), |k| { + D::do_decrypt_init(k, &[0u8; INIT_DATA_LEN]) + }) + .map(|_| ()), &D::MAX_SECURITY_STRENGTH, ); } diff --git a/crypto/core/src/hazmat/electronic_code_book.rs b/crypto/core/src/hazmat/electronic_code_book.rs index 0bcea7b64..88ce5ff5a 100644 --- a/crypto/core/src/hazmat/electronic_code_book.rs +++ b/crypto/core/src/hazmat/electronic_code_book.rs @@ -2,7 +2,7 @@ use crate::errors::SymmetricCipherError; use crate::key_material::KeyMaterial; -use crate::traits::Algorithm; +use crate::traits::{Algorithm, SymmetricCipherKey}; // Imports needed for docs #[allow(unused_imports)] @@ -25,8 +25,11 @@ use crate::key_material::KeyType; /// Every length here is fixed by a type, and a constructed value is always ready to use, so there /// is nothing a caller can get wrong once [`ElectronicCodeBook::new`] has returned. Only `new` can /// fail, and only because of the key. -pub trait ElectronicCodeBook: - Algorithm + Sized +pub trait ElectronicCodeBook< + K: SymmetricCipherKey, + const KEY_LEN: usize, + const BLOCK_LEN: usize, +>: Algorithm + Sized { /// Expands the key. /// diff --git a/crypto/core/src/hazmat/key_stream.rs b/crypto/core/src/hazmat/key_stream.rs index fccb2f4e4..ba4951f9a 100644 --- a/crypto/core/src/hazmat/key_stream.rs +++ b/crypto/core/src/hazmat/key_stream.rs @@ -2,8 +2,7 @@ //! [`ElectronicCodeBook`] is the raw primitive under a block cipher mode. use crate::errors::SymmetricCipherError; -use crate::key_material::KeyMaterial; -use crate::traits::Algorithm; +use crate::traits::{Algorithm, SymmetricCipherKey}; // Imports needed for docs #[allow(unused_imports)] @@ -36,8 +35,12 @@ use crate::traits::{BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEnc /// Implementors hold the key in a zeroize-on-drop wrapper, as for [`ElectronicCodeBook`]. Any /// keystream they produce into scratch space of their own is live key material until it has been /// XORed in, and gets the same treatment. -pub trait KeyStream: - Algorithm + Sized +pub trait KeyStream< + K: SymmetricCipherKey, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, +>: Algorithm + Sized { /// Expands the key and positions the keystream at its first block for `init_data`. /// @@ -45,10 +48,7 @@ pub trait KeyStream, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result; + fn new(key: &K, init_data: &[u8; INIT_DATA_LEN]) -> Result; /// How many more keystream blocks this value can produce before its keystream would repeat. /// A keystream with no practical limit returns `u64::MAX`. diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index bba2c91a6..7368ab2e6 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -305,7 +305,7 @@ impl KeyMaterial { } /// Creates a new instance of KeyMaterial containing random bytes from the provided random number generator. - pub fn from_rng(rng: &mut impl RNG) -> Result { + pub fn from_rng(rng: &mut (impl RNG + ?Sized)) -> Result { let mut key = Self::new(); do_hazardous_operations(&mut key, |key| { diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 4a68f9a45..c9ccc00f0 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -8,12 +8,7 @@ use crate::security_strength::SecurityStrength; use core::fmt::{Debug, Display}; use core::marker::Sized; -// Imports needed for docs -#[allow(unused_imports)] -use crate::key_material::KeyMaterial; -#[allow(unused_imports)] -use crate::key_material::KeyType; -// end of imports needed for docs +use crate::key_material::{KeyMaterial, KeyType}; /// What the allocating one-shot [`AEADCipherEncryptor::encrypt_detached`] hands back: /// `(nonce, ciphertext, tag)` @@ -49,11 +44,12 @@ pub type AEADEncryptedTuple = /// no such caveat: each owns the whole message, so it zeroizes the buffer itself before returning /// the error. pub trait AEADCipherDecryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, ->: SymmetricCipherDecryptor +>: SymmetricCipherDecryptor { /// Absorbs additional authenticated data; see [`AEADCipherEncryptor::do_update_aad`] for the /// rules, which are the same on both sides. The concatenation of what a decryptor absorbs must @@ -124,7 +120,7 @@ pub trait AEADCipherDecryptor< /// before any work is done; otherwise whatever the streaming methods return, including /// [`do_decrypt_final_detached_out`](Self::do_decrypt_final_detached_out)'s. fn decrypt_detached_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -174,7 +170,7 @@ pub trait AEADCipherDecryptor< /// shorter than the tag it is supposed to end with; /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify. fn decrypt_with_aad_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -208,7 +204,7 @@ pub trait AEADCipherDecryptor< /// [`decrypt_detached_out`](Self::decrypt_detached_out), returning the plaintext as a /// `Vec` of exactly the recovered length. Only available with the `std` feature. fn decrypt_detached( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -226,7 +222,7 @@ pub trait AEADCipherDecryptor< /// `Vec` of exactly the recovered length. This is [`SymmetricCipherDecryptor::decrypt`] /// with an `aad`. Only available with the `std` feature. fn decrypt_with_aad( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], @@ -294,11 +290,12 @@ pub trait AEADCipherDecryptor< /// the advantage of being able to correctly size the output buffer for a subsequent /// [`do_encrypt_out`](SymmetricCipherEncryptor::do_encrypt_out) call. pub trait AEADCipherEncryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, ->: SymmetricCipherEncryptor +>: SymmetricCipherEncryptor { /// Absorbs `aad`: data that is authenticated by the tag but not encrypted. May be called /// repeatedly before the first [`SymmetricCipherEncryptor::do_encrypt_out`]; a sequence of calls @@ -362,7 +359,7 @@ pub trait AEADCipherEncryptor< /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, checked /// before any work is done; otherwise whatever the streaming methods return. fn encrypt_detached_out( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], @@ -389,7 +386,7 @@ pub trait AEADCipherEncryptor< /// [`encrypt_detached_out`](Self::encrypt_detached_out), returning the ciphertext as a /// `Vec`. Only available with the `std` feature. fn encrypt_detached( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ) -> Result, SymmetricCipherError> { @@ -403,7 +400,7 @@ pub trait AEADCipherEncryptor< /// As [`encrypt_detached_out`](Self::encrypt_detached_out), but sources randomness from the /// provided RNG. fn encrypt_detached_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, aad: &[u8], plaintext: &[u8], @@ -435,7 +432,7 @@ pub trait AEADCipherEncryptor< /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, checked /// before any work is done; otherwise whatever the streaming methods return. fn encrypt_with_aad_out( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ciphertext: &mut [u8], @@ -460,7 +457,7 @@ pub trait AEADCipherEncryptor< /// included, as a `Vec`. This is [`SymmetricCipherEncryptor::encrypt`] with an `aad`. Only /// available with the `std` feature. fn encrypt_with_aad( - key: &KeyMaterial, + key: &K, aad: &[u8], plaintext: &[u8], ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> { @@ -473,7 +470,7 @@ pub trait AEADCipherEncryptor< /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out), but sources randomness from the /// provided RNG: [`SymmetricCipherEncryptor::encrypt_rng_out`] with an `aad`. fn encrypt_with_aad_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, aad: &[u8], plaintext: &[u8], @@ -501,7 +498,7 @@ pub trait AEADCipherEncryptor< /// # Errors /// As [`do_encrypt_init`](SymmetricCipherEncryptor::do_encrypt_init). fn do_encrypt_init_nonce( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], ) -> Result; @@ -512,7 +509,7 @@ pub trait AEADCipherEncryptor< /// # Errors /// As [`encrypt_detached_out`](Self::encrypt_detached_out). fn encrypt_detached_nonce_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -538,7 +535,7 @@ pub trait AEADCipherEncryptor< /// [`encrypt_detached_nonce_out`](Self::encrypt_detached_nonce_out), returning the ciphertext /// as a `Vec` with the tag. Only available with the `std` feature. fn encrypt_detached_nonce( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -557,7 +554,7 @@ pub trait AEADCipherEncryptor< /// # Errors /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). fn encrypt_with_aad_nonce_out( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -582,7 +579,7 @@ pub trait AEADCipherEncryptor< /// supplies: as [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out), returning /// the ciphertext, tag included, as a `Vec`. Only available with the `std` feature. fn encrypt_with_aad_nonce( - key: &KeyMaterial, + key: &K, nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], @@ -616,6 +613,7 @@ pub trait AlgorithmOID { /// The decryption half of a block cipher's streaming API; see [`BlockCipherEncryptor`], whose /// notes on in-place operation, lengths and the `Result` all apply here too. pub trait BlockCipherDecryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, @@ -623,7 +621,7 @@ pub trait BlockCipherDecryptor< { /// Begins a streaming decryption flow from the init data returned by [`BlockCipherEncryptor::do_encrypt_init`]. fn do_decrypt_init( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ) -> Result; /// The implementor hook: decrypts consecutive whole blocks in place. See @@ -643,7 +641,7 @@ pub trait BlockCipherDecryptor< /// [`SymmetricCipherError::DataNotBlockAligned`] if `data` is not a whole number of blocks, /// checked before any work is done, so `data` is left untouched. fn decrypt_inplace( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], data: &mut [u8], ) -> Result { @@ -695,6 +693,7 @@ pub trait BlockCipherDecryptor< /// data limit -- a counter-based mode must refuse to encrypt past the point where its counter would /// repeat -- which a streaming API cannot check any earlier than the call that would cross it. pub trait BlockCipherEncryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, @@ -702,9 +701,7 @@ pub trait BlockCipherEncryptor< { /// Begins a streaming encryption flow, returning the generated init data (e.g. IV). /// Sources randomness from the library's default OS-backed RNG. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; /// As [`BlockCipherEncryptor::do_encrypt_init`], but sources randomness from the provided RNG. /// /// # Panics @@ -716,7 +713,7 @@ pub trait BlockCipherEncryptor< /// [`SymmetricCipherError`]. Implementations with `INIT_DATA_LEN > 0` must draw their init /// data from `rng` and must not panic. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; /// The implementor hook: encrypts consecutive whole blocks in place. A sequence of calls is @@ -743,7 +740,7 @@ pub trait BlockCipherEncryptor< /// [`SymmetricCipherError::DataNotBlockAligned`] if `data` is not a whole number of blocks, /// checked before any work is done, so `data` is left untouched. fn encrypt_inplace( - key: &KeyMaterial, + key: &K, data: &mut [u8], ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { let (blocks, []) = data.as_chunks_mut::() else { @@ -761,7 +758,7 @@ pub trait BlockCipherEncryptor< /// the cases that does: an implementation with `INIT_DATA_LEN == 0`, which has no randomness /// to consume. See that method for why. fn encrypt_rng_inplace( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, data: &mut [u8], ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { @@ -1479,8 +1476,11 @@ pub trait Signer, const SK_LEN: usize, const SIG /// The decryption half of a stream cipher's streaming API; see [`StreamCipherEncryptor`], whose /// notes on in-place operation, arbitrary lengths and the `Result` all apply here too. -pub trait StreamCipherDecryptor: - SymmetricCipherDecryptor +pub trait StreamCipherDecryptor< + K: SymmetricCipherKey, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, +>: SymmetricCipherDecryptor { /// Streaming: decrypts `data`, of any length, in place. A sequence of calls is equivalent to /// one call over the concatenation, whatever the chunking, exactly as for @@ -1492,7 +1492,7 @@ pub trait StreamCipherDecryptor, + key: &K, init_data: &[u8; INIT_DATA_LEN], data: &mut [u8], ) -> Result { @@ -1525,8 +1525,11 @@ pub trait StreamCipherDecryptor: - SymmetricCipherEncryptor +pub trait StreamCipherEncryptor< + K: SymmetricCipherKey, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, +>: SymmetricCipherEncryptor { /// Streaming: encrypts `data`, of any length, in place; on top of the generic APIs offered by /// [`SymmetricCipherEncryptor`], a stream cipher can offer `_inplace()` versions since a stream @@ -1552,7 +1555,7 @@ pub trait StreamCipherEncryptor, + key: &K, data: &mut [u8], ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { let (mut enc, init_data) = Self::do_encrypt_init(key)?; @@ -1570,7 +1573,7 @@ pub trait StreamCipherEncryptor, + key: &K, rng: &mut dyn RNG, data: &mut [u8], ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { @@ -1643,6 +1646,39 @@ pub trait SuspendableKeyed: Sized { ) -> Result; } +/// A key for a specific symmetric cipher algorithm, which is a shallow wrapper for a KeyMateriol, +/// allowing for binding a specific key to a specific algorithm. +pub trait SymmetricCipherKey: Clone + PartialEq + Eq + Sized { + /// Instantiate a new SymmetricCipherKey from raw bytes: a passthrough to + /// [`KeyMaterial::from_bytes_as_type`] with [`KeyType::SymmetricCipherKey`], then + /// [`from_keymaterial`](Self::from_keymaterial). + fn from_bytes(bytes: &[u8; KEY_LEN]) -> Result { + Self::from_keymaterial(KeyMaterial::::from_bytes_as_type( + bytes, + KeyType::SymmetricCipherKey, + )?) + } + /// Instantiate a new SymmetricCipherKey from a KeyMaterial. + /// Must be [`KeyType::SymmetricCipherKey`] and have sufficient entropy. + fn from_keymaterial(key: KeyMaterial) -> Result; + /// Get a ref to the inner key material. + fn get_key(&self) -> &KeyMaterial; + + /// A fresh key drawn from `rng`: `KEY_LEN` bytes, tagged [`KeyType::SymmetricCipherKey`] at + /// the RNG's security strength, then [`from_keymaterial`](Self::from_keymaterial). An RNG + /// instantiated below the strength the key type requires is therefore refused. + fn new_from_rng(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::from_rng(rng)?; + key.set_key_type(KeyType::SymmetricCipherKey)?; + Ok(Self::from_keymaterial(key)?) + } + + /// A fresh key from an OS-seeded DRBG of the strength the key needs: + /// [`new_from_rng`](Self::new_from_rng) over HashDRBG-SHA256 for a 128-bit key, HashDRBG-SHA512 + /// for 192- and 256-bit keys. + fn new_from_os() -> Result; +} + /// The decryption half of a symmetric cipher's arbitrary-length API. See /// [`SymmetricCipherEncryptor`] for the shape of the API and the meaning of `FINAL_LEN`; this is /// its mirror image, and the two are implemented by paired types. @@ -1660,6 +1696,7 @@ pub trait SuspendableKeyed: Sized { /// [`do_update_out`](Self::do_decrypt_out), [`do_decrypt_final`](Self::do_decrypt_final) and /// [`decrypt_out_len`](Self::decrypt_out_len). pub trait SymmetricCipherDecryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, @@ -1673,7 +1710,7 @@ pub trait SymmetricCipherDecryptor< /// security strength is below [`Algorithm::MAX_SECURITY_STRENGTH`], both as a /// [`SymmetricCipherError::KeyMaterialError`]. fn do_decrypt_init( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ) -> Result; @@ -1787,7 +1824,7 @@ pub trait SymmetricCipherDecryptor< /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, checked /// before any work is done; otherwise whatever the streaming methods return. fn decrypt_out( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ciphertext: &[u8], plaintext: &mut [u8], @@ -1820,7 +1857,7 @@ pub trait SymmetricCipherDecryptor< /// `Vec` of exactly the recovered length. Only available with the `std` feature. #[cfg(feature = "std")] fn decrypt( - key: &KeyMaterial, + key: &K, init_data: &[u8; INIT_DATA_LEN], ciphertext: &[u8], ) -> Result, SymmetricCipherError> { @@ -1854,6 +1891,7 @@ pub trait SymmetricCipherDecryptor< /// constructors, [`update_out_len`](Self::do_encrypt_out_len), [`do_update_out`](Self::do_encrypt_out), /// [`do_encrypt_final`](Self::do_encrypt_final) and [`encrypt_out_len`](Self::encrypt_out_len). pub trait SymmetricCipherEncryptor< + K: SymmetricCipherKey, const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, @@ -1867,9 +1905,7 @@ pub trait SymmetricCipherEncryptor< /// Rejects a key whose [`KeyType`] is not [`KeyType::SymmetricCipherKey`], and one whose /// security strength is below [`Algorithm::MAX_SECURITY_STRENGTH`], both as a /// [`SymmetricCipherError::KeyMaterialError`]. - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + fn do_encrypt_init(key: &K) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; /// As [`do_encrypt_init`](Self::do_encrypt_init), but sources randomness from the provided RNG. /// @@ -1882,7 +1918,7 @@ pub trait SymmetricCipherEncryptor< /// [`SymmetricCipherError`]. Implementations with `INIT_DATA_LEN > 0` must draw their init /// data from `rng` and must not panic. fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; @@ -1987,7 +2023,7 @@ pub trait SymmetricCipherEncryptor< /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, checked /// before any work is done; otherwise whatever the streaming methods return. fn encrypt_out( - key: &KeyMaterial, + key: &K, plaintext: &[u8], ciphertext: &mut [u8], ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError> { @@ -2014,7 +2050,7 @@ pub trait SymmetricCipherEncryptor< /// the cases that does: an implementation with `INIT_DATA_LEN == 0`, which has no randomness /// to consume. See that method for why. fn encrypt_rng_out( - key: &KeyMaterial, + key: &K, rng: &mut dyn RNG, plaintext: &[u8], ciphertext: &mut [u8], @@ -2035,7 +2071,7 @@ pub trait SymmetricCipherEncryptor< /// One-shot, allocating: as [`encrypt_out`](Self::encrypt_out), returning the ciphertext as a /// `Vec`. Only available with the `std` feature. fn encrypt( - key: &KeyMaterial, + key: &K, plaintext: &[u8], ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError> { let mut ciphertext = vec![0u8; Self::encrypt_out_len(plaintext.len())]; diff --git a/crypto/core/tests/aead_buffering_toy_tests.rs b/crypto/core/tests/aead_buffering_toy_tests.rs index 9180a2612..2e3ccfd24 100644 --- a/crypto/core/tests/aead_buffering_toy_tests.rs +++ b/crypto/core/tests/aead_buffering_toy_tests.rs @@ -26,12 +26,12 @@ /// data back observable. #[test] fn a_buffering_pair_is_handled_by_every_default_method() { - use bouncycastle_core::errors::SymmetricCipherError; - use bouncycastle_core::key_material::{KeyMaterial, KeyType}; + use bouncycastle_core::errors::{KeyMaterialError, RNGError, SymmetricCipherError}; + use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SymmetricCipherEncryptor, SymmetricCipherKey, }; const HOLD_BACK: usize = 3; @@ -42,6 +42,24 @@ fn a_buffering_pair_is_handled_by_every_default_method() { // after them, or everything the decryptor held back. const FINAL_LEN: usize = HOLD_BACK + TAG_LEN; + /// The toy's key type. It checks nothing: the key is never used. + #[derive(Clone, PartialEq, Eq)] + struct ToyKey(KeyMaterial); + + impl SymmetricCipherKey for ToyKey { + fn from_keymaterial(key: KeyMaterial) -> Result { + Ok(Self(key)) + } + + fn get_key(&self) -> &KeyMaterial { + &self.0 + } + + fn new_from_os() -> Result { + Self::new_from_rng(&mut bouncycastle_rng::HashDRBG_SHA256::new_from_os()) + } + } + struct Buffered { hold: usize, pos: u8, @@ -113,14 +131,12 @@ fn a_buffering_pair_is_handled_by_every_default_method() { const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; } - impl SymmetricCipherEncryptor for Enc { - fn do_encrypt_init( - _key: &KeyMaterial, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { + impl SymmetricCipherEncryptor for Enc { + fn do_encrypt_init(_key: &ToyKey) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { Ok((Self(Buffered::new(HOLD_BACK)), [0u8; NONCE_LEN])) } fn do_encrypt_init_rng( - key: &KeyMaterial, + key: &ToyKey, _rng: &mut dyn RNG, ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { Self::do_encrypt_init(key) @@ -147,12 +163,12 @@ fn a_buffering_pair_is_handled_by_every_default_method() { } } - impl AEADCipherEncryptor for Enc { + impl AEADCipherEncryptor for Enc { fn do_update_aad(&mut self, _aad: &[u8]) -> Result<(), SymmetricCipherError> { Ok(()) } fn do_encrypt_init_nonce( - key: &KeyMaterial, + key: &ToyKey, _nonce: &[u8; NONCE_LEN], ) -> Result { Ok(Self::do_encrypt_init(key)?.0) @@ -168,9 +184,9 @@ fn a_buffering_pair_is_handled_by_every_default_method() { } } - impl SymmetricCipherDecryptor for Dec { + impl SymmetricCipherDecryptor for Dec { fn do_decrypt_init( - _key: &KeyMaterial, + _key: &ToyKey, _nonce: &[u8; NONCE_LEN], ) -> Result { Ok(Self(Buffered::new(FINAL_LEN))) @@ -203,7 +219,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { } } - impl AEADCipherDecryptor for Dec { + impl AEADCipherDecryptor for Dec { fn do_update_aad(&mut self, _aad: &[u8]) -> Result<(), SymmetricCipherError> { Ok(()) } @@ -225,9 +241,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { // The bytes every key and message is cut from: `0x00, 0x01, ...`, long enough for the longest // message below. let seed: [u8; 64] = core::array::from_fn(|i| i as u8); - let key = - KeyMaterial::::from_bytes_as_type(&seed[..KEY_LEN], KeyType::SymmetricCipherKey) - .unwrap(); + let key = ToyKey::from_bytes(seed[..KEY_LEN].try_into().unwrap()).unwrap(); for len in 0..=(3 * FINAL_LEN + 5) { let msg = &seed[..len]; diff --git a/mem_usage_benches/src/bench_aes_mem_usage.rs b/mem_usage_benches/src/bench_aes_mem_usage.rs index 40cd2b2e4..c0d17a660 100644 --- a/mem_usage_benches/src/bench_aes_mem_usage.rs +++ b/mem_usage_benches/src/bench_aes_mem_usage.rs @@ -40,15 +40,28 @@ //! non-returning closure so nothing crosses back across the boundary. The persistent cost, the //! engine itself, is what `print_struct_sizes` prints. //! +//! Everything runs through [`Ecb`], the mode that is exactly the engine: `size_of::>()` +//! is `size_of::

()`, and each entry point is one call into the engine's matching block method, +//! so the engine is never driven directly. +//! //! The point of comparison is that a table-driven AES adds 256 B (`AESLightEngine`) to 8 KiB //! (T-tables) of static data on top of these numbers; this implementation adds zero. #![allow(dead_code)] #![allow(unused_imports)] -use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle::core::hazmat::ElectronicCodeBook; -use bouncycastle::core::key_material::{KeyMaterial, KeyType}; +use bouncycastle::aes::hazmat::{ + AES_ECB_128_Key, AES_ECB_192_Key, AES_ECB_256_Key, AES128Internal, AES192Internal, + AES256Internal, +}; +use bouncycastle::cipher::modes::hazmat::ecb::Ecb; +use bouncycastle::cipher::{Decrypting, Encrypting}; +use bouncycastle::core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherKey}; +use std::hint::black_box; + +type Ecb128

= Ecb; +type Ecb192 = Ecb; +type Ecb256 = Ecb; /// This exists so /usr/bin/time can measure the base memory footprint of the harness itself. fn bench_do_nothing() { @@ -93,51 +106,63 @@ const KEY_256: [u8; 32] = [ /// Wraps a hard-coded key. `#[inline(never)]` so the wrapping is a sibling frame of whatever /// uses the key, not part of it. #[inline(never)] -fn key(bytes: &[u8; N]) -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(bytes, KeyType::SymmetricCipherKey).unwrap() +fn key, const N: usize>(bytes: &[u8; N]) -> K { + K::from_bytes(bytes).unwrap() } /// Expands the key into an engine, in its own frame, so the expansion's temporaries are popped /// before an operation on the engine runs. #[inline(never)] -fn load_aes128() -> AES128Internal { - AES128Internal::new(&key(&KEY_128)).unwrap() +fn load_enc128() -> Ecb128 { + Ecb128::::do_encrypt_init(&key::(&KEY_128)).unwrap().0 +} +#[inline(never)] +fn load_dec128() -> Ecb128 { + Ecb128::::do_decrypt_init(&key::(&KEY_128), &[]).unwrap() +} +#[inline(never)] +fn load_enc192() -> Ecb192 { + Ecb192::::do_encrypt_init(&key::(&KEY_192)).unwrap().0 +} +#[inline(never)] +fn load_dec192() -> Ecb192 { + Ecb192::::do_decrypt_init(&key::(&KEY_192), &[]).unwrap() } #[inline(never)] -fn load_aes192() -> AES192Internal { - AES192Internal::new(&key(&KEY_192)).unwrap() +fn load_enc256() -> Ecb256 { + Ecb256::::do_encrypt_init(&key::(&KEY_256)).unwrap().0 } #[inline(never)] -fn load_aes256() -> AES256Internal { - AES256Internal::new(&key(&KEY_256)).unwrap() +fn load_dec256() -> Ecb256 { + Ecb256::::do_decrypt_init(&key::(&KEY_256), &[]).unwrap() } // ---- key expansion: the expansion is the operation, so it runs inside `measure` ------------ fn bench_aes128_key_expansion() { - eprintln!("AES128Internal::new (key expansion)"); - let key = key(&KEY_128); + eprintln!("Ecb do_encrypt_init (key expansion)"); + let key = key::(&KEY_128); measure(|| { - let aes = AES128Internal::new(&key).unwrap(); - print!("{aes:?}"); + let (aes, _) = Ecb128::::do_encrypt_init(&key).unwrap(); + black_box(aes); }); } fn bench_aes192_key_expansion() { - eprintln!("AES192Internal::new (key expansion)"); - let key = key(&KEY_192); + eprintln!("Ecb do_encrypt_init (key expansion)"); + let key = key::(&KEY_192); measure(|| { - let aes = AES192Internal::new(&key).unwrap(); - print!("{aes:?}"); + let (aes, _) = Ecb192::::do_encrypt_init(&key).unwrap(); + black_box(aes); }); } fn bench_aes256_key_expansion() { - eprintln!("AES256Internal::new (key expansion)"); - let key = key(&KEY_256); + eprintln!("Ecb do_encrypt_init (key expansion)"); + let key = key::(&KEY_256); measure(|| { - let aes = AES256Internal::new(&key).unwrap(); - print!("{aes:?}"); + let (aes, _) = Ecb256::::do_encrypt_init(&key).unwrap(); + black_box(aes); }); } @@ -148,181 +173,181 @@ fn bench_aes256_key_expansion() { // stack arrays in the closure, never a heap buffer, so massif's --heap=no does not hide them. fn bench_aes128_encrypt_block() { - eprintln!("AES128Internal::encrypt_block"); - let aes = load_aes128(); + eprintln!("Ecb do_encrypt_inplace, 1 block"); + let mut aes = load_enc128(); measure(|| { let mut block = [0x11u8; 16]; - aes.encrypt_block(&mut block); + aes.do_encrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes128_decrypt_block() { - eprintln!("AES128Internal::decrypt_block"); - let aes = load_aes128(); + eprintln!("Ecb do_decrypt_inplace, 1 block"); + let mut aes = load_dec128(); measure(|| { let mut block = [0x11u8; 16]; - aes.decrypt_block(&mut block); + aes.do_decrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes128_encrypt_2blocks() { - eprintln!("AES128Internal::encrypt_2blocks"); - let aes = load_aes128(); + eprintln!("Ecb do_encrypt_blocks_inplace, 2 blocks"); + let mut aes = load_enc128(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.encrypt_2blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes128_decrypt_2blocks() { - eprintln!("AES128Internal::decrypt_2blocks"); - let aes = load_aes128(); + eprintln!("Ecb do_decrypt_blocks_inplace, 2 blocks"); + let mut aes = load_dec128(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.decrypt_2blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes128_encrypt_4blocks() { - eprintln!("AES128Internal::encrypt_4blocks"); - let aes = load_aes128(); + eprintln!("Ecb do_encrypt_blocks_inplace, 4 blocks"); + let mut aes = load_enc128(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.encrypt_4blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes128_decrypt_4blocks() { - eprintln!("AES128Internal::decrypt_4blocks"); - let aes = load_aes128(); + eprintln!("Ecb do_decrypt_blocks_inplace, 4 blocks"); + let mut aes = load_dec128(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.decrypt_4blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes192_encrypt_block() { - eprintln!("AES192Internal::encrypt_block"); - let aes = load_aes192(); + eprintln!("Ecb do_encrypt_inplace, 1 block"); + let mut aes = load_enc192(); measure(|| { let mut block = [0x11u8; 16]; - aes.encrypt_block(&mut block); + aes.do_encrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes192_decrypt_block() { - eprintln!("AES192Internal::decrypt_block"); - let aes = load_aes192(); + eprintln!("Ecb do_decrypt_inplace, 1 block"); + let mut aes = load_dec192(); measure(|| { let mut block = [0x11u8; 16]; - aes.decrypt_block(&mut block); + aes.do_decrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes192_encrypt_2blocks() { - eprintln!("AES192Internal::encrypt_2blocks"); - let aes = load_aes192(); + eprintln!("Ecb do_encrypt_blocks_inplace, 2 blocks"); + let mut aes = load_enc192(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.encrypt_2blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes192_decrypt_2blocks() { - eprintln!("AES192Internal::decrypt_2blocks"); - let aes = load_aes192(); + eprintln!("Ecb do_decrypt_blocks_inplace, 2 blocks"); + let mut aes = load_dec192(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.decrypt_2blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes192_encrypt_4blocks() { - eprintln!("AES192Internal::encrypt_4blocks"); - let aes = load_aes192(); + eprintln!("Ecb do_encrypt_blocks_inplace, 4 blocks"); + let mut aes = load_enc192(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.encrypt_4blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes192_decrypt_4blocks() { - eprintln!("AES192Internal::decrypt_4blocks"); - let aes = load_aes192(); + eprintln!("Ecb do_decrypt_blocks_inplace, 4 blocks"); + let mut aes = load_dec192(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.decrypt_4blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes256_encrypt_block() { - eprintln!("AES256Internal::encrypt_block"); - let aes = load_aes256(); + eprintln!("Ecb do_encrypt_inplace, 1 block"); + let mut aes = load_enc256(); measure(|| { let mut block = [0x11u8; 16]; - aes.encrypt_block(&mut block); + aes.do_encrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes256_decrypt_block() { - eprintln!("AES256Internal::decrypt_block"); - let aes = load_aes256(); + eprintln!("Ecb do_decrypt_inplace, 1 block"); + let mut aes = load_dec256(); measure(|| { let mut block = [0x11u8; 16]; - aes.decrypt_block(&mut block); + aes.do_decrypt_blocks_inplace(core::array::from_mut(&mut block)).unwrap(); print!("{block:x?}"); }); } fn bench_aes256_encrypt_2blocks() { - eprintln!("AES256Internal::encrypt_2blocks"); - let aes = load_aes256(); + eprintln!("Ecb do_encrypt_blocks_inplace, 2 blocks"); + let mut aes = load_enc256(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.encrypt_2blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes256_decrypt_2blocks() { - eprintln!("AES256Internal::decrypt_2blocks"); - let aes = load_aes256(); + eprintln!("Ecb do_decrypt_blocks_inplace, 2 blocks"); + let mut aes = load_dec256(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.decrypt_2blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes256_encrypt_4blocks() { - eprintln!("AES256Internal::encrypt_4blocks"); - let aes = load_aes256(); + eprintln!("Ecb do_encrypt_blocks_inplace, 4 blocks"); + let mut aes = load_enc256(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.encrypt_4blocks(&mut blocks); + aes.do_encrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } fn bench_aes256_decrypt_4blocks() { - eprintln!("AES256Internal::decrypt_4blocks"); - let aes = load_aes256(); + eprintln!("Ecb do_decrypt_blocks_inplace, 4 blocks"); + let mut aes = load_dec256(); measure(|| { let mut blocks = [[0x11u8; 16], [0x22u8; 16], [0x33u8; 16], [0x44u8; 16]]; - aes.decrypt_4blocks(&mut blocks); + aes.do_decrypt_blocks_inplace(&mut blocks).unwrap(); print!("{blocks:x?}"); }); } diff --git a/mem_usage_benches/src/bench_ascon_mem_usage.rs b/mem_usage_benches/src/bench_ascon_mem_usage.rs index bb13906b0..abcf16ced 100644 --- a/mem_usage_benches/src/bench_ascon_mem_usage.rs +++ b/mem_usage_benches/src/bench_ascon_mem_usage.rs @@ -56,7 +56,7 @@ use core::hint::black_box; -use bouncycastle::ascon::Ascon_AEAD128; +use bouncycastle::ascon::{Ascon_AEAD128, Ascon_AEAD128_Key}; use bouncycastle::ascon::{AsconCXof128, AsconCXof128Squeezer, SUSPENDED_ASCON_CXOF128_STATE_LEN}; use bouncycastle::ascon::{AsconHash256, SUSPENDED_ASCON_HASH256_STATE_LEN}; use bouncycastle::ascon::{AsconXof128, AsconXof128Squeezer, SUSPENDED_ASCON_XOF128_STATE_LEN}; @@ -66,11 +66,11 @@ use bouncycastle::ascon::{ }; use bouncycastle::cipher::{Decrypting, Encrypting}; use bouncycastle::core::errors::{HashError, RNGError, SymmetricCipherError}; -use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle::core::key_material::KeyMaterialTrait; use bouncycastle::core::security_strength::SecurityStrength; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Hash, RNG, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, XOF, XOFSqueezer, + SymmetricCipherEncryptor, SymmetricCipherKey, XOF, XOFSqueezer, }; type Enc = Ascon_AEAD128; @@ -177,8 +177,8 @@ fn measure(f: impl FnOnce()) { /// Wraps the hard-coded key. `#[inline(never)]` so the wrapping is a sibling frame of whatever /// uses the key, not part of it. #[inline(never)] -fn key() -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(&KEY, KeyType::SymmetricCipherKey).unwrap() +fn key() -> Ascon_AEAD128_Key { + Ascon_AEAD128_Key::from_bytes(&KEY).unwrap() } /// A message filled at run time, so that its contents are not a constant the compiler could fold @@ -283,9 +283,9 @@ fn bench_aead_encrypt_init() { let k = key(); measure(|| { let op = black_box( - >::do_encrypt_init_rng + >::do_encrypt_init_rng as fn( - &KeyMaterial, + &Ascon_AEAD128_Key, &mut dyn RNG, ) -> Result<(Enc, [u8; NONCE_LEN]), CipherErr>, ); @@ -302,10 +302,16 @@ fn bench_aead_encrypt_update_aad() { let aad = black_box(message::(0x5a)); measure(move || { let mut enc = enc; - let op = black_box( - >::do_update_aad - as fn(&mut Enc, &[u8]) -> Result<(), CipherErr>, - ); + let op = + black_box( + >::do_update_aad as fn(&mut Enc, &[u8]) -> Result<(), CipherErr>, + ); op(&mut enc, &aad).unwrap(); black_box(&enc); }); @@ -320,7 +326,7 @@ fn bench_aead_encrypt_out() { let mut enc = enc; let mut ct = [0u8; MSG_LEN]; let op = black_box( - >::do_encrypt_out + >::do_encrypt_out as fn(&mut Enc, &[u8], &mut [u8]) -> Result, ); let n = op(&mut enc, &pt, &mut ct).unwrap(); @@ -335,7 +341,7 @@ fn bench_aead_encrypt_final() { let enc = black_box(encryptor_after_data()); measure(move || { let op = black_box( - >::do_encrypt_final + >::do_encrypt_final as fn(Enc) -> Result<([u8; TAG_LEN], usize), CipherErr>, ); let (tag, n) = op(enc).unwrap(); @@ -349,10 +355,17 @@ fn bench_aead_encrypt_final_detached() { let enc = black_box(encryptor_after_data()); measure(move || { let mut last = [0u8; TAG_LEN]; - let op = black_box( - >::do_encrypt_final_detached_out - as fn(Enc, &mut [u8; TAG_LEN]) -> Result<(usize, [u8; TAG_LEN]), CipherErr>, - ); + let op = + black_box( + >::do_encrypt_final_detached_out + as fn(Enc, &mut [u8; TAG_LEN]) -> Result<(usize, [u8; TAG_LEN]), CipherErr>, + ); let (_, tag) = op(enc, &mut last).unwrap(); print!("{tag:x?}"); }); @@ -367,16 +380,23 @@ fn bench_aead_encrypt_oneshot() { let pt = black_box(message::(0xa5)); measure(move || { let mut ct = [0u8; SEALED_LEN]; - let op = black_box( - >::encrypt_with_aad_rng_out - as fn( - &KeyMaterial, - &mut dyn RNG, - &[u8], - &[u8], - &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), CipherErr>, - ); + let op = + black_box( + >::encrypt_with_aad_rng_out + as fn( + &Ascon_AEAD128_Key, + &mut dyn RNG, + &[u8], + &[u8], + &mut [u8], + ) -> Result<([u8; NONCE_LEN], usize), CipherErr>, + ); let (_, n) = op(&k, &mut FixedNonce, &aad, &pt, &mut ct).unwrap(); print!("{:x?}", &ct[..n]); }); @@ -390,8 +410,8 @@ fn bench_aead_decrypt_init() { let k = key(); measure(|| { let op = black_box( - >::do_decrypt_init - as fn(&KeyMaterial, &[u8; NONCE_LEN]) -> Result, + >::do_decrypt_init + as fn(&Ascon_AEAD128_Key, &[u8; NONCE_LEN]) -> Result, ); let dec = op(&k, &NONCE).unwrap(); black_box(&dec); @@ -405,10 +425,16 @@ fn bench_aead_decrypt_update_aad() { let aad = black_box(message::(0x5a)); measure(move || { let mut dec = dec; - let op = black_box( - >::do_update_aad - as fn(&mut Dec, &[u8]) -> Result<(), CipherErr>, - ); + let op = + black_box( + >::do_update_aad as fn(&mut Dec, &[u8]) -> Result<(), CipherErr>, + ); op(&mut dec, &aad).unwrap(); black_box(&dec); }); @@ -425,7 +451,7 @@ fn bench_aead_decrypt_out() { let mut dec = dec; let mut pt = [0u8; SEALED_LEN]; let op = black_box( - >::do_decrypt_out + >::do_decrypt_out as fn(&mut Dec, &[u8], &mut [u8]) -> Result, ); let n = op(&mut dec, &ct, &mut pt).unwrap(); @@ -440,7 +466,7 @@ fn bench_aead_decrypt_final() { let dec = black_box(decryptor_holding_tag()); measure(move || { let op = black_box( - >::do_decrypt_final + >::do_decrypt_final as fn(Dec) -> Result<([u8; TAG_LEN], usize), CipherErr>, ); let (_, n) = op(dec).unwrap(); @@ -456,10 +482,17 @@ fn bench_aead_decrypt_final_detached() { let tag: [u8; TAG_LEN] = black_box(sealed()[MSG_LEN..].try_into().unwrap()); measure(move || { let mut last = [0u8; TAG_LEN]; - let op = black_box( - >::do_decrypt_final_detached_out - as fn(Dec, &[u8; TAG_LEN], &mut [u8; TAG_LEN]) -> Result, - ); + let op = + black_box( + >::do_decrypt_final_detached_out + as fn(Dec, &[u8; TAG_LEN], &mut [u8; TAG_LEN]) -> Result, + ); let n = op(dec, &tag, &mut last).unwrap(); print!("{:x?}", &last[..n]); }); @@ -474,16 +507,23 @@ fn bench_aead_decrypt_oneshot() { let ct = black_box(sealed()); measure(move || { let mut pt = [0u8; MSG_LEN]; - let op = black_box( - >::decrypt_with_aad_out - as fn( - &KeyMaterial, - &[u8; NONCE_LEN], - &[u8], - &[u8], - &mut [u8], - ) -> Result, - ); + let op = + black_box( + >::decrypt_with_aad_out + as fn( + &Ascon_AEAD128_Key, + &[u8; NONCE_LEN], + &[u8], + &[u8], + &mut [u8], + ) -> Result, + ); let n = op(&k, &NONCE, &aad, &ct, &mut pt).unwrap(); print!("{:x?}", &pt[..n]); }); diff --git a/mem_usage_benches/src/bench_ccm_mem_usage.rs b/mem_usage_benches/src/bench_ccm_mem_usage.rs index 76ba7321a..a9fecb1a6 100644 --- a/mem_usage_benches/src/bench_ccm_mem_usage.rs +++ b/mem_usage_benches/src/bench_ccm_mem_usage.rs @@ -29,9 +29,10 @@ #![allow(unused_imports)] use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle::aes::{AES_CCM_128_Key, AES_CCM_192_Key, AES_CCM_256_Key}; use bouncycastle::cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::{KeyMaterial, KeyType}; +use bouncycastle::core::traits::SymmetricCipherKey; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; @@ -49,14 +50,14 @@ const DATA_LEN: usize = 16384; const AAD_LEN: usize = 64; const MESSAGE_LEN: usize = DATA_LEN; -type Aes128Ccm = CcmPacket; +type Aes128Ccm = CcmPacket; type Aes128CcmEncryptor = - CcmEncryptor; + CcmEncryptor; type Aes128CcmDecryptor = - CcmDecryptor; + CcmDecryptor; -fn key() -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(&[0x42u8; N], KeyType::SymmetricCipherKey).unwrap() +fn key() -> AES_CCM_128_Key { + AES_CCM_128_Key::from_bytes(&[0x42u8; 16]).unwrap() } /// The message every bench processes, filled at run time and then only ever reached through a @@ -93,19 +94,19 @@ fn print_struct_sizes() { eprintln!("CcmPacket {:>7} B", size_of::>()); eprintln!( "CcmPacket {:>7} B", - size_of::>() + size_of::>() ); eprintln!( "CcmPacket {:>7} B", - size_of::>() + size_of::>() ); eprintln!( "CcmPacket {:>7} B", - size_of::>() + size_of::>() ); eprintln!( "CcmPacket {:>7} B", - size_of::>() + size_of::>() ); eprintln!("Decrypting is the same size:"); eprintln!("CcmPacket {:>7} B", size_of::>()); @@ -117,7 +118,8 @@ fn print_struct_sizes() { eprintln!("CcmDecryptor<.., {AAD_LEN}, {DATA_LEN}> {:>7} B", size_of::()); eprintln!( "CcmEncryptor<.., 64, 240> {:>7} B", - size_of::>() + size_of::>( + ) ); print!("{}", size_of::>()); @@ -130,7 +132,7 @@ fn print_struct_sizes() { fn bench_direct_encrypt_detached() { eprintln!("CcmPacket::encrypt_detached_out, {MESSAGE_LEN} B"); - let k = key::<16>(); + let k = key(); let nonce = [0x24u8; NONCE_LEN]; let plaintext = message(); let plaintext = core::hint::black_box(&plaintext); @@ -150,7 +152,7 @@ fn bench_streaming_encrypt() { "CcmEncryptor do_encrypt_init/do_update_out/do_encrypt_final_detached_out, {MESSAGE_LEN} B in 1 KiB chunks" ); - let k = key::<16>(); + let k = key(); let plaintext = message(); let plaintext = core::hint::black_box(&plaintext); let mut ciphertext = [0u8; MESSAGE_LEN]; @@ -176,7 +178,7 @@ fn bench_streaming_decrypt() { "CcmDecryptor do_decrypt_init/do_update_out/do_decrypt_final, {MESSAGE_LEN} B in 1 KiB chunks" ); - let k = key::<16>(); + let k = key(); let nonce = [0x24u8; NONCE_LEN]; let mut sealed = [0u8; MESSAGE_LEN + TAG_LEN]; sealed[..MESSAGE_LEN].fill(core::hint::black_box(0xA5)); @@ -203,7 +205,7 @@ fn bench_streaming_decrypt() { fn bench_oneshot_encrypt_out_detached() { eprintln!("CcmEncryptor::encrypt_detached_out, {MESSAGE_LEN} B"); - let k = key::<16>(); + let k = key(); let plaintext = message(); let plaintext = core::hint::black_box(&plaintext); let mut ciphertext = [0u8; MESSAGE_LEN]; @@ -219,7 +221,7 @@ fn bench_oneshot_encrypt_out_detached() { fn bench_direct_streaming() { eprintln!("CcmPacket::do_encrypt_inplace, {MESSAGE_LEN} B in 1 KiB chunks"); - let k = key::<16>(); + let k = key(); let nonce = [0x24u8; NONCE_LEN]; let mut data = message(); let data = core::hint::black_box(&mut data);