diff --git a/CLAUDE.md b/CLAUDE.md index 4d26b7b15..a5583b749 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,7 +106,7 @@ The workspace has four top-level kinds of member: - `crypto/core` defines the abstract traits (`Hash`, `KDF`, `MAC`, `KEM`, `Signature`, `RNG`, `Algorithm`, `HashAlgParams`, …), error enums (`HashError`, `KDFError`, `KEMError`, `MACError`, `RNGError`, `SignatureError`), and the `KeyMaterial` / `KeyType` wrapper that all sensitive byte buffers are required to use (see `Secret` super-trait requirement in QUALITY_AND_STYLE.md). - `crypto/core-test-framework` contains the shared per-trait test suite (`hash.rs`, `kdf.rs`, `kem.rs`, `mac.rs`, `signature.rs`). New implementations of a core trait must be exercised through this framework — it's how trait conformance and error-condition coverage stay consistent across implementations. -- `crypto/factory` provides enum-based string-name factories (`HashFactory`, `KDFFactory`, `MACFactory`, `RNGFactory`, `XOFFactory`). Each factory enum impls the underlying trait so it can be used transparently as that primitive, and each impls `AlgorithmFactory` (`new(name)`, `default_128_bit()`, `default_256_bit()`). When adding a new primitive that fits an existing trait, register it in the corresponding factory. +- `crypto/factory` provides enum-based string-name factories (`HashFactory`, `KDFFactory`, `MACFactory`, `RNGFactory`, `XOFFactory`). Each factory enum impls the underlying trait so it can be used transparently as that primitive, except `MACFactory`, which is a key enum implementing the MAC role traits and handing out a `MACFactoryWorker` that impls `MAC`; and each impls `AlgorithmFactory` (`new(name)`, `default_128_bit()`, `default_256_bit()`). When adding a new primitive that fits an existing trait, register it in the corresponding factory. Trait/factory/CLI is the standard layering: a new algorithm typically requires (a) the primitive crate, (b) implementing the relevant `core` trait, (c) wiring it into the matching factory, (d) a CLI subcommand in `cli/src/*_cmd.rs` registered in `cli/src/main.rs`. @@ -173,10 +173,12 @@ taught, as mechanics to apply next time a cipher type is shaped or reshaped: struct under the spec-spelled name (`Ascon_AEAD128`, `#[allow(non_camel_case_types)]`) is the whole public type, with no CamelCase twin and no alias to it. - **The trait pair is the API.** Do not keep an inherent streaming or one-shot API beside the - `AEADCipher*` / `SymmetricCipher*` impls; everything a caller needs, including a supplied nonce - (`do_encrypt_init_nonce`, `encrypt_with_aad_nonce_out`), is on the traits. The only inherent - methods a cipher adds are for what the traits lack, in-place streaming (`do_*_inplace`), and - their decrypt form keeps the same tag hold-back as `do_decrypt_out` so the two interleave. + `AEADCipher*` / `SymmetricCipher*` impls; everything a caller needs is on the traits, and + everything that takes a key or draws a nonce, including a supplied nonce + (`create_encryptor_nonce`, `encrypt_with_aad_nonce_out`), is on the key object. The only + inherent methods a cipher adds are for what the traits lack, in-place streaming + (`do_*_inplace`), and their decrypt form keeps the same tag hold-back as `do_decrypt_out` so + the two interleave. - **Private helpers must not share a trait method's name.** An inherent `do_encrypt_final` with a different signature from the trait's is what forced the wrapper newtypes; name the core helpers for what they do (`absorb_aad`, `encrypt_bytes`, `finish_tag`, `verify_tag`). @@ -194,9 +196,57 @@ taught, as mechanics to apply next time a cipher type is shaped or reshaped: a CLI path, run it against a known-answer vector (`--nonce` on `ascon-aead128` reproduces the LWC KAT tag). - **A rename stays in the crate it was asked for.** `do_*_update` to `do_*_inplace` was done for - Ascon; the CCM packet type still has its namesake. Note the follow-up rather than widening the + Ascon; the CCM type still has its namesake. Note the follow-up rather than widening the change. +## Lessons from the key-object rework (October 2026) + +The symmetric cipher and MAC traits lost their constructors and key-taking one-shots; keys took them. +What that taught, as mechanics for the next trait that sheds a constructor: + +- **The trait describes a worker that exists.** `SymmetricCipherEncryptor`, `AEADCipherEncryptor`, + `BlockCipherEncryptor`, `StreamCipherEncryptor`, their decrypting halves and `MAC` carry only + streaming methods and length helpers. Construction is a key object's (`create_encryptor(rng)`, + `create_decryptor(init)`, `create_generator()`), and the one-shots are provided on the role + traits in `core::symmetric_key` over those. `KEY_LEN` is on the role and mode traits, never on + a worker trait. +- **Mode types carry the role.** A key is `AESKey`; which role pairs it implements is + decided by `M`'s mode-trait impls in `core::hazmat::mode` (`SymmetricEncryptMode`, + `BlockEncryptMode`, `EcbEncryptMode`, `AeadEncryptMode`, `AeadDeclaredEncryptMode`, `MacMode`). + Padding and frame parameters live in the mode type (`CBC`, `CBC`, + `ECB`, `CTR<12>`, `CCM_Packet<12, 16, 64, 1024>`), not on a worker alias. +- **The key length stops at the key.** `BlockPermutation` carries the block methods + and `ElectronicCodeBook` adds only `new`, so a worker and a mode's + `Mode

` impl bound on the former and never name a key length; the GCM, GMAC and CMAC impls + are generic in `cipher` for that reason. `KEY_LEN` stays on the mode traits' worker-producing + halves as the tie between a key's length and its permutation's, through + `P: ElectronicCodeBook`, which is what keeps a key object's role impls to the one + length. A mode that must know the block length (`CMAC`, `CTR`) takes + it as a parameter, since stable Rust cannot read it off the permutation inside the impl. +- **Test the key, not the worker pair.** The framework runners are `test_key` over a role pair + and the construction suites in `core_test_framework::from_key_material`; a test of "a key of the + wrong type is rejected" belongs to the construction suite, once, not to each mode's tests. + Known-answer suites keep pinning their IV through `create_encryptor` and a `FixedSeedRNG`. +- **Workers borrow the key.** The role and mode traits hand out workers through a generic + associated type with a lifetime, `type Encryptor<'k>`, and the cipher-crate workers hold + `&'k` to the mode's key state, so `create_encryptor` copies no schedule and a worker cannot + outlive its key. Spell the mode traits' GAT bound `where P: 'k`, not `where Self::KeyState: 'k`: + rustc rejects a generic impl whose key state is a struct over `P` (E0276) under the latter. A + function handed `key: &'k K` that names the worker is generic over `'k`; one that builds its + key locally bounds `for<'k> K::Encryptor<'k>: ..` with `K: 'static`. A test binds the key + before the worker, since `key_fn().create_encryptor(..)` borrows a temporary. +- **Resume takes the key object.** `SuspendableKeyed<'k, N>` and `SuspendableComponent<'k>` take + the key by `&'k`: a cipher-mode worker's `Key` is the mode's `KeyState`, reached through + `hazmat::ModeKey::state`, HMAC's is `HmacKey`, Ascon's is `AsconAEAD128Key`, and HKDF's stays + the salt material, since HKDF is not a key object. The suspended layouts did not change. A + worker that owns a copy of its key (Ascon, KMAC) implements the traits for every `'k` and + ignores it. A struct cannot hold a worker that borrows a key it also owns: HKDF runs its + extract-phase HMAC as the two halves `HmacKey::inner_state` / `finish_out`, and the MAC + factory is a key enum that hands out a worker enum. +- **What has no key-path twin is dropped, and said so.** `new_allow_weak_key` on a cipher-based + MAC, a wrong-length `KeyMaterial512` into a fixed-length key, GCM's verify-before-decrypt + one-shot override: each went with a line in the commit message rather than a shim. + ## Lessons from the signature key rework (November 2026) The signature traits moved construction from the algorithm type onto the key objects, as the @@ -276,4 +326,16 @@ external vector suites — is specified in QUALITY_AND_STYLE.md and CONTRIBUTING ## CI -The only workflow is `.github/workflows/publish_doc_benches_to_ghpages.yaml`: on every PR it builds rustdoc and runs `quality_stats.sh`; on `main` it additionally runs `cargo bench --all` and publishes docs, code stats, and benchmark results to GitHub Pages (`https://bcgit.github.io/bc-rust/`). There is no separate CI test/lint job — local `cargo test --workspace` is the gate, and nothing but a developer running it stands between a broken test and `main`. \ No newline at end of file +Five workflows under `.github/workflows/`, all triggered by pull requests: + +- `rust-build.yml`: `cargo build --workspace --all-targets --all-features` on stable. +- `rust-test.yml`: `cargo test --all` on stable. +- `rust-style.yml`: `cargo fmt --all --check` on **nightly** rustfmt, so format before every commit. +- `rust-docs.yml`: `cargo doc --all` on stable. +- `publish_doc_benches_to_ghpages.yaml`: builds rustdoc and runs `quality_stats.sh`; on `main` it also + publishes docs and code stats to GitHub Pages (`https://bcgit.github.io/bc-rust/`). Its bench step is + commented out because the benches are too slow on the hosted runner, so benchmark figures come from a + developer's machine, not CI. + +`cargo test --all` is the workspace run, so a failing test does block a PR; what CI does not run is +`cargo mutants`, the CLI shell tests under `cli/tests/`, or rustdoc with warnings denied. \ No newline at end of file diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 007c761fe..6d3591a51 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -12,9 +12,13 @@ lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]` All primitives must be accompanied by a CLI in `/cli`. -All algorithms with a state that is exercised across multiple API calls -- typically through a `do_update()`, -`do_final()` pattern -- should implement SerializableState so that the user can pause the execution of this algorithm to -a cache and resume later. +Hashes, XOFs and the MACs and KDFs built on them (KMAC, HMAC, HKDF) implement `Suspendable` or `SuspendableKeyed`, so +that a computation in progress can be written to a cache and resumed later. Cipher and MAC workers -- the encryptors, +decryptors, generators and verifiers a key object hands out -- do not: a suspended or cloned worker is a second copy of +a nonce, counter or keystream position, and two copies that both go on to encrypt reuse keystream (#173). Those workers +implement neither suspend trait nor `Clone`; a message is paused by holding its worker, and the worker traits are kept +`dyn`-compatible so it can be held as `&mut dyn` or, where allocation is acceptable, `Box`. The suspend and `Clone` +impls the cipher workers still carry come out in the #173 sweep. `Clone` belongs on the types, not on the `core` traits. Every hash and XOF type is `Clone`, so a computation can be forked mid-stream and each copy finished on its own; `Hash` itself carries no `Clone` (and no `Default`), because both @@ -96,7 +100,13 @@ In addition, some library-specific naming conventions: ## APIs Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member -function that does not require object instantiation. +function that does not require object instantiation. The exception is anything keyed: a symmetric cipher or MAC is +reached through a key object (`AES128Key`, `HmacKey`, `KMAC128Key`, `AsconAEAD128Key`), built once from +`KeyMaterial` by `FromKeyMaterial` / `FromAnyKeyMaterial`, and its one-shots and `create_*` constructors are methods on +that key. The cipher and MAC traits in `core::traits` describe a worker that already exists and carry no constructor +and no key-taking method, so a key's validation and expansion happen once, where the key is made, rather than per call. +A worker borrows the key it came from, for the lifetime on the role trait's associated worker type, so making one +copies no schedule and a worker cannot outlive its key. Other version of Bouncy Castle have a design pattern where stateful objects follow a pattern of new () -> init () -> do_update () -> do_final (), and then optionally reset () that sets the object back to an unitialized state. Instead, @@ -131,7 +141,10 @@ Take, for example a one-shot API `fn encrypt(plaintext: &[u8]) -> Result The following prefixes can be applied: * `do_`: this implies that it is part of a stateful streaming API, will typically take `&mut self`, and is likely - accompanied by a `do_encrypt_init()` and `do_encrypt_final()`. + accompanied by a `do_encrypt_final()`; the object comes from a key's `create_encryptor()`. +* `create_`: a key object's constructor for a worker -- `create_encryptor(&self, rng)`, `create_decryptor(&self, + init_data)`, `create_generator(&self)`, `create_verifier(&self)`. Whatever draws randomness takes a `&mut dyn RNG` + explicitly, so a key has no `_rng` twins and no method that reaches for a default RNG. The following suffixes can be applied @@ -142,8 +155,10 @@ The following suffixes can be applied `_final` indicates that this function call renders the object unusable afterwards by consuming `self` via a move: `_final(self, ..)`. * `_rng`: indicates that this version of the function sources its random numbers from a provided `&mut dyn RNG` instead - of - from the default library RNG. `_rng(.., &mut dyn RNG)`. + of from the default library RNG. `_rng(.., &mut dyn RNG)`. Keyed primitives do not have it: their constructors and + one-shots take the RNG, full stop. +* `_nonce`: on an AEAD key, the twin that takes the nonce from the caller instead of drawing it from the RNG, and so + does not return one. * `_out / _inplace`: indicates that the function works in the provided buffer. `_out` indicates that the function takes an output buffer, which may be oversized, and returns the number of bytes written to it: `_out(.., out: &mut [u8] -> Result`. `_inplace` indicates that the input and output are diff --git a/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs index 8f2d8bd2e..f5026eb86 100644 --- a/cli/src/aes_cbc_cmd.rs +++ b/cli/src/aes_cbc_cmd.rs @@ -10,13 +10,13 @@ //! separately. use crate::helpers::block_mode_helpers::{ - BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key, + BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key_object, + start_encryptor, }; -use bouncycastle::aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::CBC; use bouncycastle::cipher::padding::PKCS7; -use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; -use bouncycastle::core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; pub(crate) fn aes128_cbc_cmd( action: &CipherDirection, @@ -26,9 +26,9 @@ pub(crate) fn aes128_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_128, 16>( + run::<_, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::>, 16>(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -42,9 +42,9 @@ pub(crate) fn aes192_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_192, 24>( + run::<_, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::>, 24>(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -58,30 +58,31 @@ pub(crate) fn aes256_cbc_cmd( iv_file: &Option, output_hex: bool, ) { - run::, AES_CBC_256, 32>( + run::<_, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::>, 32>(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); } -/// Dispatches to the shared streaming loops. -fn run( +/// Dispatches to the shared streaming loops under the key's padded CBC mode. +fn run( action: &CipherDirection, - key: &KeyMaterial, + key: &K, iv: Option<[u8; BLOCK_LEN]>, output_hex: bool, ) where - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, { match action { CipherDirection::Encrypt => { - encrypt_stream::(key, iv, output_hex, None) + let (enc, generated_iv) = start_encryptor(iv, |rng| key.create_encryptor(rng)); + encrypt_stream::<_, BLOCK_LEN>(enc, generated_iv.as_ref(), output_hex, None) } CipherDirection::Decrypt => { - decrypt_stream::(key, iv, output_hex, None) + decrypt_stream::<_, BLOCK_LEN>(iv, |iv| key.create_decryptor(iv), output_hex, None) } } } diff --git a/cli/src/aes_ccm_cmd.rs b/cli/src/aes_ccm_cmd.rs index 94a140a90..d2592dc08 100644 --- a/cli/src/aes_ccm_cmd.rs +++ b/cli/src/aes_ccm_cmd.rs @@ -20,8 +20,8 @@ //! `ascon-aead128`, are the streaming alternatives. //! //! The AAD is read whole too: the key's one-shot takes it as a slice. (A.2.2 needs its length -//! before its first byte, so streaming it would need the declared-length constructor the packet -//! type keeps for itself.) +//! before its first byte, so streaming it would need the declared-length constructor the type +//! keeps for itself.) //! //! # The nonce is supplied, not generated //! @@ -53,7 +53,7 @@ use bouncycastle::cipher::modes::CCM; use bouncycastle::core::errors::SymmetricCipherError; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::symmetric_key::{ - AeadPacketDecryptionKey, AeadPacketEncryptionKey, FromKeyMaterial, + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial, }; use bouncycastle::hex; @@ -68,8 +68,8 @@ use crate::helpers::block_mode_helpers::{CipherDirection, load_key}; trait CcmKeys { /// The key type at nonce length `N` and tag length `T`. type Key: FromKeyMaterial - + AeadPacketEncryptionKey - + AeadPacketDecryptionKey; + + AeadDeclaredEncryptionKey + + AeadDeclaredDecryptionKey; } struct Aes128; diff --git a/cli/src/aes_cfb8_cmd.rs b/cli/src/aes_cfb8_cmd.rs index ee6690cdb..450b8eb1c 100644 --- a/cli/src/aes_cfb8_cmd.rs +++ b/cli/src/aes_cfb8_cmd.rs @@ -25,10 +25,12 @@ //! plaintext byte, corrupts the following 16 bytes, and then decryption resynchronises. Do not //! decrypt data you have not authenticated separately. -use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key}; +use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key_object}; use crate::helpers::stream_mode_helpers::run_stream_mode; -use bouncycastle::aes::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; -use bouncycastle::cipher::{Decrypting, Encrypting}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::CFB8; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; +use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; pub(crate) fn aes128_cfb8_cmd( action: &CipherDirection, @@ -38,9 +40,9 @@ pub(crate) fn aes128_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_128, 16, BLOCK_LEN>( + run::<_, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::, 16>(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -54,9 +56,9 @@ pub(crate) fn aes192_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_192, 24, BLOCK_LEN>( + run::<_, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::, 24>(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -70,10 +72,25 @@ pub(crate) fn aes256_cfb8_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB8_256, 32, BLOCK_LEN>( + run::<_, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::, 32>(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); } + +/// Dispatches to the shared streaming loops with `Cfb8` filled in as the mode. +fn run<'k, K, const KEY_LEN: usize>( + action: &CipherDirection, + key: &'k K, + iv: Option<[u8; BLOCK_LEN]>, + output_hex: bool, +) where + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: StreamCipherDecryptor, +{ + run_stream_mode::(action, key, iv, output_hex) +} diff --git a/cli/src/aes_cfb_cmd.rs b/cli/src/aes_cfb_cmd.rs index 9cc88a6e2..d59398684 100644 --- a/cli/src/aes_cfb_cmd.rs +++ b/cli/src/aes_cfb_cmd.rs @@ -26,10 +26,12 @@ //! of the plaintext in the *same* block, so an attacker edits the block they aimed at, at the cost //! of randomising the next one. Do not decrypt data you have not authenticated separately. -use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key}; +use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_iv, load_key_object}; use crate::helpers::stream_mode_helpers::run_stream_mode; -use bouncycastle::aes::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; -use bouncycastle::cipher::{Decrypting, Encrypting}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::CFB; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; +use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; pub(crate) fn aes128_cfb_cmd( action: &CipherDirection, @@ -39,9 +41,9 @@ pub(crate) fn aes128_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_128, 16, BLOCK_LEN>( + run::<_, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::, 16>(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -55,9 +57,9 @@ pub(crate) fn aes192_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_192, 24, BLOCK_LEN>( + run::<_, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::, 24>(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -71,10 +73,25 @@ pub(crate) fn aes256_cfb_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CFB_256, 32, BLOCK_LEN>( + run::<_, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::, 32>(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); } + +/// Dispatches to the shared streaming loops with `Cfb` filled in as the mode. +fn run<'k, K, const KEY_LEN: usize>( + action: &CipherDirection, + key: &'k K, + iv: Option<[u8; BLOCK_LEN]>, + output_hex: bool, +) where + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: StreamCipherDecryptor, +{ + run_stream_mode::(action, key, iv, output_hex) +} diff --git a/cli/src/aes_ctr_cmd.rs b/cli/src/aes_ctr_cmd.rs index 742f333fc..66ac5be35 100644 --- a/cli/src/aes_ctr_cmd.rs +++ b/cli/src/aes_ctr_cmd.rs @@ -30,13 +30,17 @@ //! //! A repeated nonce is fatal here rather than merely unwise: the same nonce under the same key //! gives the same keystream, and two messages XORed with the same keystream leak their XOR. The -//! nonce is drawn from the OS-backed DRBG for exactly that reason, and there is no way to supply -//! one. +//! nonce is drawn from the OS-backed DRBG for exactly that reason. `--iv` / `--iv-file` exist for +//! reproducing published vectors and for formats that carry the nonce themselves; with them, its +//! uniqueness under the key is the caller's to keep. -use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key}; +use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object}; 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::cipher::{Decrypting, Encrypting}; +use bouncycastle::aes::CTR_NONCE_LEN; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::CTR; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; +use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; pub(crate) fn aes128_ctr_cmd( action: &CipherDirection, @@ -46,9 +50,9 @@ pub(crate) fn aes128_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_128, 16, CTR_NONCE_LEN>( + run::<_, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::>, 16>(key, key_file, "AES-128"), load_iv::(iv, iv_file), output_hex, ); @@ -62,9 +66,9 @@ pub(crate) fn aes192_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_192, 24, CTR_NONCE_LEN>( + run::<_, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::>, 24>(key, key_file, "AES-192"), load_iv::(iv, iv_file), output_hex, ); @@ -78,10 +82,25 @@ pub(crate) fn aes256_ctr_cmd( iv_file: &Option, output_hex: bool, ) { - run_stream_mode::, AES_CTR_256, 32, CTR_NONCE_LEN>( + run::<_, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::>, 32>(key, key_file, "AES-256"), load_iv::(iv, iv_file), output_hex, ); } + +/// Dispatches to the shared streaming loops with `Ctr` filled in as the mode. +fn run<'k, K, const KEY_LEN: usize>( + action: &CipherDirection, + key: &'k K, + iv: Option<[u8; CTR_NONCE_LEN]>, + output_hex: bool, +) where + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: StreamCipherDecryptor, +{ + run_stream_mode::(action, key, iv, output_hex) +} diff --git a/cli/src/aes_ecb_cmd.rs b/cli/src/aes_ecb_cmd.rs index 63b7d43f5..4451abc35 100644 --- a/cli/src/aes_ecb_cmd.rs +++ b/cli/src/aes_ecb_cmd.rs @@ -16,13 +16,12 @@ //! `aes*-cbc` or `aes*-cfb` under separate authentication, or better an AEAD. use crate::helpers::block_mode_helpers::{ - BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_key, + BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_key_object, start_encryptor, }; -use bouncycastle::aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::hazmat::ecb::ECB; use bouncycastle::cipher::padding::NoPadding; -use bouncycastle::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; -use bouncycastle::core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; /// Names the mode in error messages. const MODE: &str = "ECB"; @@ -33,9 +32,9 @@ pub(crate) fn aes128_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_128, 16>( + run::<_, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::>, 16>(key, key_file, "AES-128"), output_hex, ); } @@ -46,9 +45,9 @@ pub(crate) fn aes192_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_192, 24>( + run::<_, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::>, 24>(key, key_file, "AES-192"), output_hex, ); } @@ -59,29 +58,31 @@ pub(crate) fn aes256_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::, AES_ECB_256, 32>( + run::<_, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::>, 32>(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, +/// Dispatches to the shared streaming loops under the key's ECB mode. `INIT_DATA_LEN` is 0, so +/// the loops write and read no IV, and `NoPadding` makes them refuse a partial final block. +fn run(action: &CipherDirection, key: &K, output_hex: bool) +where + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, { match action { CipherDirection::Encrypt => { - encrypt_stream::(key, None, output_hex, Some(MODE)) - } - CipherDirection::Decrypt => { - decrypt_stream::(key, None, output_hex, Some(MODE)) + // ECB has no IV: the constructor's RNG draws nothing, and nothing is written. + let (enc, _) = start_encryptor(None, |rng| key.create_encryptor(rng)); + encrypt_stream::<_, 0>(enc, None, output_hex, Some(MODE)) } + CipherDirection::Decrypt => decrypt_stream::<_, 0>( + None, + |no_iv| key.create_decryptor(no_iv), + output_hex, + Some(MODE), + ), } } diff --git a/cli/src/aes_gcm_cmd.rs b/cli/src/aes_gcm_cmd.rs index c6907aebd..5575c8d6d 100644 --- a/cli/src/aes_gcm_cmd.rs +++ b/cli/src/aes_gcm_cmd.rs @@ -14,11 +14,10 @@ //! recover the hash subkey (Appendix A). 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::cipher::{Decrypting, Encrypting}; -use bouncycastle::core::key_material::KeyMaterial; -use bouncycastle::core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::GCM; +use bouncycastle::core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey}; pub(crate) fn aes128_gcm_cmd( action: &CipherDirection, @@ -30,9 +29,9 @@ pub(crate) fn aes128_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_128, 16>( + run::, 16>( action, - &load_key::<16>(key, key_file, "AES-128"), + &load_key_object::<_, 16>(key, key_file, "AES-128"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, @@ -49,9 +48,9 @@ pub(crate) fn aes192_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_192, 24>( + run::, 24>( action, - &load_key::<24>(key, key_file, "AES-192"), + &load_key_object::<_, 24>(key, key_file, "AES-192"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, @@ -68,28 +67,27 @@ pub(crate) fn aes256_gcm_cmd( aad_file: &Option, output_hex: bool, ) { - run::, AES_GCM_256, 32>( + run::, 32>( action, - &load_key::<32>(key, key_file, "AES-256"), + &load_key_object::<_, 32>(key, key_file, "AES-256"), load_iv::<12>(iv, iv_file), &load_aad(aad, aad_file), output_hex, ); } -/// Dispatches to the shared AEAD streaming loops with GCM's 128-bit tag. -fn run( +/// Dispatches to the shared AEAD streaming loops with `Gcm`'s 128-bit tag. +fn run( action: &CipherDirection, - key: &KeyMaterial, + key: &K, nonce: Option<[u8; 12]>, aad: &[u8], output_hex: bool, ) where - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, + K: AeadEncryptionKey + AeadDecryptionKey, { 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..a51ba01b5 100644 --- a/cli/src/ascon_cmd.rs +++ b/cli/src/ascon_cmd.rs @@ -1,15 +1,15 @@ use std::io::{self, Read}; use std::process::exit; -use bouncycastle::ascon::Ascon_AEAD128; +use bouncycastle::ascon::AsconAEAD128Key; use bouncycastle::ascon::AsconCXof128; use bouncycastle::ascon::AsconHash256; use bouncycastle::ascon::AsconXof128; -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::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; @@ -56,9 +56,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 -/// 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> { +/// Build the AEAD key, warning (and forcing usable metadata on the material) only if the key +/// turns out to be low-entropy (e.g. all-zero), the same way `helpers::parse_seed` does. +fn load_aead_key(key_bytes: &[u8; 16]) -> AsconAEAD128Key { 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 +71,10 @@ fn load_key_material(key_bytes: &[u8; 16]) -> KeyMaterial<16> { }) .unwrap(); } - key + AsconAEAD128Key::from_key_material(&key).unwrap_or_else(|e| { + eprintln!("Error: couldn't load the key: {e:?}"); + exit(-1); + }) } /// Ascon-Hash256 of stdin. Streaming update; 256-bit digest. @@ -123,7 +126,7 @@ pub(crate) fn aead128_cmd( decrypt: bool, output_hex: bool, ) { - let key = load_key_material(&require_16(load_bytes(key, key_file, "key"), "key")); + let key = load_aead_key(&require_16(load_bytes(key, key_file, "key"), "key")); let nonce = load_optional_bytes(nonce, nonce_file, "nonce").map(|bytes| require_16(bytes, "nonce")); let ad_bytes = match ad { @@ -142,25 +145,26 @@ pub(crate) fn aead128_cmd( } } -/// Drives [`Ascon_AEAD128`] in the inline `ciphertext || tag` layout (the inherited -/// [`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. +/// Drives the key's encryptor in the inline `ciphertext || tag` layout (the inherited +/// [`SymmetricCipherEncryptor::do_encrypt_final_out`]). With no nonce given one is drawn from the +/// OS-backed DRBG 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: &AsconAEAD128Key, nonce: Option<&[u8; 16]>, ad_opt: Option<&[u8]>, output_hex: bool, ) { - let start = match nonce { - Some(nonce) => { - Ascon_AEAD128::::do_encrypt_init_nonce(key, nonce).map(|c| (c, None)) - } - None => Ascon_AEAD128::::do_encrypt_init(key).map(|(c, n)| (c, Some(n))), + let (mut cipher, generated_nonce) = match nonce { + Some(nonce) => (key.create_encryptor_nonce(nonce), None), + None => key + .create_encryptor(&mut bouncycastle::rng::HashDRBG_SHA512::new_from_os()) + .map(|(c, n)| (c, Some(n))) + .unwrap_or_else(|e| { + eprintln!("Error: couldn't start encryption: {e:?}"); + exit(-1); + }), }; - let (mut cipher, generated_nonce) = start.unwrap_or_else(|e| { - eprintln!("Error: couldn't start encryption: {e:?}"); - exit(-1); - }); if let Some(ad) = ad_opt { // infallible: `do_update_aad` only refuses AAD once plaintext has been fed in, and none // has been yet. @@ -193,11 +197,11 @@ fn aead128_encrypt_stream( } /// Decrypts a stream whose final 16 bytes are the tag, which is only known once EOF is reached. -/// [`Ascon_AEAD128`] holds the last 16 bytes it has seen back itself, releasing everything +/// The key's decryptor holds the last 16 bytes it has seen back itself, releasing everything /// 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: &AsconAEAD128Key, nonce: Option<&[u8; 16]>, ad_opt: Option<&[u8]>, output_hex: bool, @@ -218,11 +222,7 @@ fn aead128_decrypt_stream( } }; - let mut cipher = - Ascon_AEAD128::::do_decrypt_init(key, &nonce).unwrap_or_else(|e| { - eprintln!("Error: couldn't start decryption: {e:?}"); - exit(-1); - }); + let mut cipher = key.create_decryptor(&nonce); if let Some(ad) = ad_opt { // infallible: as on the encrypt side, no ciphertext has been fed in yet. cipher.do_update_aad(ad).unwrap(); diff --git a/cli/src/helpers/aead_cipher_helpers.rs b/cli/src/helpers/aead_cipher_helpers.rs index e54b36fb8..3574f6aa4 100644 --- a/cli/src/helpers/aead_cipher_helpers.rs +++ b/cli/src/helpers/aead_cipher_helpers.rs @@ -1,10 +1,10 @@ //! Shared plumbing for the AEAD subcommands: `aes{128,192,256}-gcm`. //! -//! Parallel to [`crate::helpers::stream_mode_helpers`], but for [`bouncycastle::cipher::modes::Gcm`] rather than a -//! [`StreamCipherEncryptor`](bouncycastle::core::traits::StreamCipherEncryptor) mode: GCM carries -//! additional authenticated data and a tag, neither of which that trait has room for, so this -//! module drives it through [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] instead, which add -//! `do_update_aad` to the symmetric-cipher streaming methods. +//! Parallel to [`crate::helpers::stream_mode_helpers`], but for a key bound to +//! [`bouncycastle::cipher::modes::GCM`] rather than a stream mode: GCM carries additional +//! authenticated data and a tag, neither of which the stream traits have room for, so this module +//! drives the key's AEAD workers through [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], which +//! add `do_update_aad` to the symmetric-cipher streaming methods. //! //! # On-the-wire format: `IV || ciphertext || tag` //! @@ -13,7 +13,7 @@ //! rest of stdin through the inline decryptor. With `--iv`, the IV is the caller's and is //! neither written nor read: the format is `ciphertext || tag` (see //! [`crate::helpers::block_mode_helpers`] for the flag). The decryptor, per -//! [`SymmetricCipherDecryptor`](bouncycastle::core::traits::SymmetricCipherDecryptor)'s contract, +//! [`SymmetricCipherDecryptor`]'s contract, //! holds back the last 16 bytes it has seen because they might be the tag -- and checks the tag on //! `do_decrypt_final`. //! @@ -39,8 +39,10 @@ //! 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::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle::core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey}; +use bouncycastle::core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; use bouncycastle::hex; use std::io; use std::io::{Read, Write}; @@ -72,25 +74,27 @@ 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: AeadEncryptionKey, { let mut enc = match nonce { - Some(nonce) => E::do_encrypt_init_nonce(key, &nonce), - None => E::do_encrypt_init(key).map(|(enc, nonce)| { + Some(nonce) => key.create_encryptor_nonce(&nonce), + None => { + let (enc, nonce) = key + .create_encryptor(&mut bouncycastle::rng::HashDRBG_SHA512::new_from_os()) + .unwrap_or_else(|e| { + eprintln!("Error: couldn't start encryption: {e:?}"); + exit(-1); + }); write_bytes_or_hex(&nonce, output_hex); enc - }), - } - .unwrap_or_else(|e| { - eprintln!("Error: couldn't start encryption: {e:?}"); - exit(-1); - }); + } + }; enc.do_update_aad(aad).unwrap_or_else(|e| { eprintln!("Error: couldn't absorb the additional authenticated data: {e:?}"); @@ -116,7 +120,7 @@ pub fn encrypt_gcm( } // The detached final flushes nothing for GCM and returns the tag, written last. - let (_, _, tag) = enc.do_encrypt_final_detached().unwrap_or_else(|e| { + let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; TAG_LEN]).unwrap_or_else(|e| { eprintln!("Error: encryption failed: {e:?}"); exit(-1); }); @@ -128,13 +132,13 @@ 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: AeadDecryptionKey, { let nonce = nonce.unwrap_or_else(|| { let mut nonce = [0u8; GCM_NONCE_LEN]; @@ -148,10 +152,7 @@ pub fn decrypt_gcm( nonce }); - let mut dec = D::do_decrypt_init(key, &nonce).unwrap_or_else(|e| { - eprintln!("Error: couldn't start decryption: {e:?}"); - exit(-1); - }); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap_or_else(|e| { eprintln!("Error: couldn't absorb the additional authenticated data: {e:?}"); exit(-1); diff --git a/cli/src/helpers/block_mode_helpers.rs b/cli/src/helpers/block_mode_helpers.rs index c68dcc85b..cc3b9ec71 100644 --- a/cli/src/helpers/block_mode_helpers.rs +++ b/cli/src/helpers/block_mode_helpers.rs @@ -3,8 +3,8 @@ //! Everything here is mode-independent -- key loading, stdin framing, block-alignment enforcement, //! output formatting -- and is generic over the mode via [`SymmetricCipherEncryptor`] / //! [`SymmetricCipherDecryptor`]. `aes_cbc_cmd` and `aes_ecb_cmd` are thin dispatchers over it, -//! instantiating it with the `AES_CBC_*` types under `PKCS7` and the `AES_ECB_*` types under -//! `NoPadding`, so the commands cannot drift apart on the parts that matter for correctness. +//! driving it with the workers their keys hand out, `CBC` and `ECB`, so the +//! commands cannot drift apart on the parts that matter for correctness. //! //! The CFB and CTR commands are stream ciphers and live in [`crate::helpers::stream_mode_helpers`] instead; //! they share @@ -28,9 +28,9 @@ //! Every mode with an IV or nonce (CBC, CFB, CFB8, CTR, GCM) also takes `--iv` / `--iv-file`, //! loaded by [`load_iv`], for a format that carries the IV itself and for reproducing published //! vectors. With it, `encrypt` uses the supplied value and writes **only the ciphertext**, and -//! `decrypt` takes it from the flag and reads **only ciphertext** from stdin. The modes have no -//! IV-taking constructor, so it reaches them through `do_encrypt_init_rng` and a [`FixedIv`] -//! that hands out exactly those bytes (GCM has `do_encrypt_init_nonce` and uses that). +//! `decrypt` takes it from the flag and reads **only ciphertext** from stdin. The keys' constructors +//! draw the IV from an RNG, so it reaches them through [`start_encryptor`] and a [`FixedIv`] that +//! hands out exactly those bytes (GCM keys have `create_encryptor_nonce` and use that). //! Unpredictability, and for CTR and GCM uniqueness, is then the caller's responsibility. CCM //! keeps its own `--nonce`, which is variable-length and was never generated. //! @@ -54,16 +54,18 @@ //! cat cipher.hex | bc-rust hex-decode | bc-rust aes256-cbc decrypt --key-file k.bin //! ``` -use crate::helpers; use crate::helpers::{ - flush_stdout, read_from_file, strip_trailing_newline, write_bytes_or_hex, write_stdout, + flush_stdout, read_from_file, read_from_file_raw, strip_trailing_newline, write_bytes_or_hex, + write_stdout, }; -use bouncycastle::core::errors::RNGError; +use bouncycastle::core::errors::{RNGError, 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::symmetric_key::FromKeyMaterial; use bouncycastle::core::traits::{RNG, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle::hex; +use bouncycastle::rng::HashDRBG_SHA512; use clap::ValueEnum; use std::io; use std::io::{Read, Write}; @@ -147,8 +149,21 @@ pub(crate) fn load_key( key } -/// An [`RNG`] that yields the caller's IV once, so `--iv` can reach a mode whose only -/// constructors are `do_encrypt_init` and `do_encrypt_init_rng`. A second draw, or one of the +/// The key object for a command: the material from `--key` / `--key-file`, checked and expanded +/// once. `K` is the key type the command's mode wants, `AES128Key>` and the like. +pub(crate) fn load_key_object, const KEY_LEN: usize>( + key: &Option, + key_file: &Option, + alg: &str, +) -> K { + K::from_key_material(&load_key::(key, key_file, alg)).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 key whose only +/// constructor draws the IV from an RNG. A second draw, or one of the /// wrong length, is an error rather than a repeat: the IV must be consumed exactly as given. pub(crate) struct FixedIv { iv: [u8; N], @@ -205,7 +220,7 @@ pub(crate) fn load_iv( iv_file: &Option, ) -> Option<[u8; INIT_DATA_LEN]> { let bytes = if let Some(file) = iv_file { - let bytes = helpers::read_from_file_raw(file); + let bytes = read_from_file_raw(file); if bytes.last() == Some(&b'\n') { eprintln!( "Warning: IV file '{file}' ends with a newline byte (0x0a), which is used as part \ @@ -230,33 +245,44 @@ pub(crate) fn load_iv( })) } -/// Encrypts stdin to stdout under the mode `E`. With no `iv`, the init data (the IV) is generated -/// and written first; with one, it is used and the output is ciphertext only. -/// -/// `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, +/// Starts an encryptor from `create`, the key's constructor: under `iv` when one was supplied, +/// through a [`FixedIv`], and otherwise under a fresh IV from the OS-backed DRBG. A generated IV +/// comes back for the caller to write ahead of the ciphertext; a supplied one does not, since it +/// is the caller's to carry. +pub(crate) fn start_encryptor( iv: Option<[u8; INIT_DATA_LEN]>, - output_hex: bool, - aligned_mode: Option<&str>, -) where - E: SymmetricCipherEncryptor, -{ - let init = match iv { - Some(iv) => E::do_encrypt_init_rng(key, &mut FixedIv::new(iv)), - None => E::do_encrypt_init(key), + create: impl FnOnce(&mut dyn RNG) -> Result<(E, [u8; INIT_DATA_LEN]), SymmetricCipherError>, +) -> (E, Option<[u8; INIT_DATA_LEN]>) { + let started = match iv { + Some(iv) => create(&mut FixedIv::new(iv)).map(|(enc, _)| (enc, None)), + None => create(&mut HashDRBG_SHA512::new_from_os()).map(|(enc, iv)| (enc, Some(iv))), }; - let (mut enc, generated_iv) = init.unwrap_or_else(|e| { + started.unwrap_or_else(|e| { eprintln!("Error: couldn't start encryption: {e:?}"); exit(-1); - }); + }) +} - // A generated IV goes out ahead of the ciphertext, so `decrypt` can pick it up. (Empty for - // ECB.) A supplied one is the caller's to carry. - if INIT_DATA_LEN > 0 && iv.is_none() { - write_bytes_or_hex(&generated_iv, output_hex); +/// Encrypts stdin to stdout through `enc`, a worker the command's key has already made, writing +/// `generated_iv` first when there is one. +/// +/// `INIT_DATA_LEN` is the mode's: one block for CBC, 0 for ECB. `generated_iv` is `None` when the +/// IV was supplied with `--iv`, or when the mode has none, and then nothing is written ahead of +/// the ciphertext; see [`start_encryptor`]. `aligned_mode` names a mode whose input must be a +/// whole number of blocks, for the error that reports a partial one (ECB under `NoPadding`); it is +/// `None` for a padded mode, which takes any length. +pub(crate) fn encrypt_stream( + mut enc: E, + generated_iv: Option<&[u8; INIT_DATA_LEN]>, + output_hex: bool, + aligned_mode: Option<&str>, +) where + E: SymmetricCipherEncryptor, +{ + // A generated IV goes out ahead of the ciphertext, so `decrypt` can pick it up. A supplied + // one is the caller's to carry. (Nothing for ECB.) + if let Some(iv) = generated_iv { + write_bytes_or_hex(iv, output_hex); } let mut out = [0u8; CHUNK_LEN]; @@ -281,16 +307,16 @@ pub(crate) fn encrypt_stream( - key: &KeyMaterial, +/// Decrypts stdin to stdout through the worker `make_decryptor` gives for the init data (the IV): +/// `iv` if it was supplied, else the first `INIT_DATA_LEN` bytes of input -- one block for CBC, +/// nothing for ECB. `aligned_mode` is as for [`encrypt_stream`]. +pub(crate) fn decrypt_stream( iv: Option<[u8; INIT_DATA_LEN]>, + make_decryptor: impl FnOnce(&[u8; INIT_DATA_LEN]) -> D, output_hex: bool, aligned_mode: Option<&str>, ) where - D: SymmetricCipherDecryptor, + D: SymmetricCipherDecryptor, { let iv = iv.unwrap_or_else(|| { // The leading bytes are the IV, not ciphertext. (None for ECB: the read is skipped.) @@ -307,10 +333,7 @@ pub(crate) fn decrypt_stream( - key: &KeyMaterial, +pub(crate) fn encrypt_stream<'k, K, const KEY_LEN: usize, const INIT_DATA_LEN: usize>( + key: &'k K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - E: StreamCipherEncryptor, + K: SymmetricEncryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, { - let init = match iv { - Some(iv) => E::do_encrypt_init_rng(key, &mut FixedIv::new(iv)), - None => E::do_encrypt_init(key), - }; - let (mut enc, generated_iv) = init.unwrap_or_else(|e| { - eprintln!("Error: couldn't start encryption: {e:?}"); - exit(-1); - }); + let (mut enc, generated_iv) = start_encryptor(iv, |rng| key.create_encryptor(rng)); // A generated IV goes out ahead of the ciphertext, so `decrypt` can pick it up. A supplied // one is the caller's to carry. - if iv.is_none() { - write_bytes_or_hex(&generated_iv, output_hex); + if let Some(iv) = generated_iv { + write_bytes_or_hex(&iv, output_hex); } // The cipher works in place: `data` holds plaintext on the way in and ciphertext on the way out. @@ -77,14 +71,15 @@ pub(crate) fn encrypt_stream( - key: &KeyMaterial, +pub(crate) fn decrypt_stream<'k, K, const KEY_LEN: usize, const INIT_DATA_LEN: usize>( + key: &'k K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - D: StreamCipherDecryptor, + K: SymmetricDecryptionKey, + K::Decryptor<'k>: StreamCipherDecryptor, { let iv = iv.unwrap_or_else(|| { // The leading bytes are the IV, not ciphertext. @@ -99,10 +94,7 @@ pub(crate) fn decrypt_stream( +/// Runs one direction of a stream mode under its key. The `run` dispatchers in `aes_cfb_cmd`, +/// `aes_cfb8_cmd` and `aes_ctr_cmd` differ only in which key type they load, so the match lives +/// here. +pub(crate) fn run_stream_mode<'k, K, const KEY_LEN: usize, const INIT_DATA_LEN: usize>( action: &CipherDirection, - key: &KeyMaterial, + key: &'k K, iv: Option<[u8; INIT_DATA_LEN]>, output_hex: bool, ) where - E: StreamCipherEncryptor, - D: StreamCipherDecryptor, + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: 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/cli/src/mac_cmd.rs b/cli/src/mac_cmd.rs index f5b207c06..6cd942a39 100644 --- a/cli/src/mac_cmd.rs +++ b/cli/src/mac_cmd.rs @@ -1,14 +1,18 @@ +use std::io; use std::io::Read; use std::process::exit; -use std::{fs, io}; +use crate::helpers::read_from_file_raw; +use bouncycastle::core::errors::MACError; use bouncycastle::core::hazmat::do_hazardous_operations; use bouncycastle::core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; use bouncycastle::core::traits::MAC; use bouncycastle::hex; -use bouncycastle::sha2::hmac::{HMAC_SHA256, HMAC_SHA512, HMAC_SHA512_224, HMAC_SHA512_256}; -use bouncycastle::sha3::kmac::{KMAC128, KMAC256}; -use bouncycastle::sm3::hmac::HMAC_SM3; +use bouncycastle::hmac::HmacKey; +use bouncycastle::sha2::{SHA256, SHA512, SHA512_224, SHA512_256}; +use bouncycastle::sha3::kmac::{KMAC128Key, KMAC256Key}; +use bouncycastle::sm3::SM3; #[allow(non_camel_case_types)] pub(crate) enum HMACVariant { @@ -22,10 +26,13 @@ pub(crate) enum HMACVariant { /// Loads a MAC key from `--key-file` (raw) or `--key` (hex), tagged as a MAC key. The file wins /// when both are given, as the help text says and the other commands' loaders do. fn load_mac_key(key: &Option, key_file: &Option) -> KeyMaterial512 { - let key_bytes: Vec = if let Some(file) = key_file { - fs::read(file).unwrap() - } else if let Some(hex_str) = key { - hex::decode(hex_str).unwrap() + let key_bytes: Vec = if let Some(key_file) = key_file { + read_from_file_raw(key_file) + } else if let Some(key) = key { + hex::decode(key).unwrap_or_else(|_| { + eprintln!("Error: `--key` must be hex. Use `--key-file` for raw bytes."); + exit(-1); + }) } else { eprintln!("Error: either `key` or `key-file` must be supplied."); exit(-1) @@ -35,8 +42,16 @@ fn load_mac_key(key: &Option, key_file: &Option) -> KeyMaterial5 eprintln!("Error: CLI only supports MAC keys 64 bytes."); exit(-1); } - let mut key = KeyMaterial512::from_bytes(&key_bytes).unwrap(); - do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap(); + let mut key = KeyMaterial512::from_bytes(&key_bytes).unwrap_or_else(|e| { + eprintln!("Error: couldn't load the key: {e:?}"); + exit(-1); + }); + do_hazardous_operations(&mut key, |key| key.set_key_type(KeyType::MACKey)).unwrap_or_else( + |e| { + eprintln!("Error: couldn't load the key: {e:?}"); + exit(-1); + }, + ); key } @@ -52,24 +67,24 @@ pub(crate) fn mac_cmd( // instantiate the MAC object and call do_mac() match hmac_variant { HMACVariant::SHA256 => { - let mac = HMAC_SHA256::new_allow_weak_key(&key).unwrap(); - do_mac(mac, verify_val, output_hex); + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + do_mac(hmac_key.create_generator(), verify_val, output_hex); } HMACVariant::SHA512 => { - let mac = HMAC_SHA512::new_allow_weak_key(&key).unwrap(); - do_mac(mac, verify_val, output_hex); + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + do_mac(hmac_key.create_generator(), verify_val, output_hex); } HMACVariant::SHA512_224 => { - let mac = HMAC_SHA512_224::new_allow_weak_key(&key).unwrap(); - do_mac(mac, verify_val, output_hex); + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + do_mac(hmac_key.create_generator(), verify_val, output_hex); } HMACVariant::SHA512_256 => { - let mac = HMAC_SHA512_256::new_allow_weak_key(&key).unwrap(); - do_mac(mac, verify_val, output_hex); + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + do_mac(hmac_key.create_generator(), verify_val, output_hex); } HMACVariant::SM3 => { - let mac = HMAC_SM3::new_allow_weak_key(&key).unwrap(); - do_mac(mac, verify_val, output_hex); + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + do_mac(hmac_key.create_generator(), verify_val, output_hex); } } } @@ -87,23 +102,30 @@ pub(crate) fn kmac_cmd( ) { let key = load_mac_key(key, key_file); let s = customization.as_deref().unwrap_or("").as_bytes(); - // new_allow_weak_key, as the HMAC commands do: a CLI is used for test vectors and scripting, - // where a short or all-zero key is a legitimate thing to want. + // The allow-weak form, as the HMAC commands use: a CLI is used for test vectors and + // scripting, where a short or all-zero key is a legitimate thing to want. match bit_len { - 128 => do_mac( - KMAC128::new_with_params(&key, s, length, true).expect("a valid MAC key"), - verify_val, - output_hex, - ), - 256 => do_mac( - KMAC256::new_with_params(&key, s, length, true).expect("a valid MAC key"), - verify_val, - output_hex, - ), + 128 => { + let kmac_key = KMAC128Key::with_params_allow_weak(&key, s, length) + .unwrap_or_else(|e| kmac_key_error(e)); + do_mac(kmac_key.create_generator(), verify_val, output_hex) + } + 256 => { + let kmac_key = KMAC256Key::with_params_allow_weak(&key, s, length) + .unwrap_or_else(|e| kmac_key_error(e)); + do_mac(kmac_key.create_generator(), verify_val, output_hex) + } _ => panic!("Unsupported algorithm: KMAC-{bit_len}"), } } +/// The key is already a MAC key, so what fails here is the tag length, which came from the +/// command line: report it the way the key errors are reported rather than through a panic. +fn kmac_key_error(e: MACError) -> ! { + eprintln!("Error: couldn't build the KMAC key: {e:?}"); + exit(-1) +} + fn do_mac(mut mac: impl MAC, verify_val: &Option, output_hex: bool) { // read the content to be MAC'd from stdin let mut buf: [u8; 1024] = [0u8; 1024]; @@ -121,10 +143,10 @@ fn do_mac(mut mac: impl MAC, verify_val: &Option, output_hex: bool) { crate::helpers::write_stdout(b"\n"); } else { // verify a MAC - if mac.do_verify_final(&hex::decode(verify_val.as_ref().unwrap()).unwrap()) { - exit(0) - } else { - exit(-1) - } + let expected = hex::decode(verify_val.as_ref().unwrap()).unwrap_or_else(|_| { + eprintln!("Error: the MAC to verify must be hex."); + exit(-1); + }); + if mac.do_verify_final(&expected) { exit(0) } else { exit(-1) } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 5ab6c44a2..c443f6fd3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1347,7 +1347,7 @@ enum Subcommands { /// The tag length and customization string are bound into the computation, so the verifier /// must use the same values. KMAC128 { - /// Length of the tag in bytes. + /// Length of the tag in bytes, at least 4 (SP 800-185 Sec 8.4.2). length: usize, #[arg(short = 's', long)] @@ -1374,7 +1374,7 @@ enum Subcommands { /// Compute or verify a KMAC256 (NIST SP 800-185 Sec 4) over the content provided on stdin. /// See kmac128. KMAC256 { - /// Length of the tag in bytes. + /// Length of the tag in bytes, at least 4 (SP 800-185 Sec 8.4.2). length: usize, #[arg(short = 's', long)] diff --git a/cli/tests/test_kmac.sh b/cli/tests/test_kmac.sh new file mode 100755 index 000000000..4c95e26c9 --- /dev/null +++ b/cli/tests/test_kmac.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# The kmac128 / kmac256 subcommands, end to end through the binary: a MAC computed over random +# data under a random key must verify with `-v`, must stop verifying when the tag, the message, +# the key, the customization string or the tag length changes, and a tag length the key refuses +# must be reported as an error rather than through a panic. + +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +# `kmac_round_trip SUBCOMMAND` runs the round trip for one variant at a 32-byte tag. +kmac_round_trip() { + local sub=$1 tag + rng 32 >"$TMP/key" + rng 32 >"$TMP/other_key" + rng 100 >"$TMP/msg" + cp "$TMP/msg" "$TMP/other_msg" + flip_byte "$TMP/other_msg" 50 + + tag=$(hex_out "$BC_RUST" "$sub" 32 --key-file "$TMP/key" -x <"$TMP/msg") + assert_eq "${#tag}" 64 "$sub tag length" + + expect_ok "the tag must verify" "$BC_RUST" "$sub" 32 --key-file "$TMP/key" -v "$tag" <"$TMP/msg" + expect_fail "a modified tag must not verify" \ + "$BC_RUST" "$sub" 32 --key-file "$TMP/key" -v "$(flip_hex "$tag" 0 1)" <"$TMP/msg" + expect_fail "a modified message must not verify" \ + "$BC_RUST" "$sub" 32 --key-file "$TMP/key" -v "$tag" <"$TMP/other_msg" + expect_fail "another key must not verify" \ + "$BC_RUST" "$sub" 32 --key-file "$TMP/other_key" -v "$tag" <"$TMP/msg" + expect_fail "another customization string must not verify" \ + "$BC_RUST" "$sub" 32 -s other --key-file "$TMP/key" -v "$tag" <"$TMP/msg" + expect_fail "another tag length must not verify" \ + "$BC_RUST" "$sub" 16 --key-file "$TMP/key" -v "${tag:0:32}" <"$TMP/msg" +} + +# `kmac_refused_length SUBCOMMAND LENGTH` checks that LENGTH is refused with an error message, +# not a panic trace, whether computing or verifying. +kmac_refused_length() { + local sub=$1 length=$2 + rng 32 >"$TMP/key" + rng 100 >"$TMP/msg" + + expect_fail "tag length $length must be refused" \ + "$BC_RUST" "$sub" "$length" --key-file "$TMP/key" -x <"$TMP/msg" + assert_stderr_has "Error: couldn't build the KMAC key" + ! grep -q panicked "$LAST_STDERR" || fail "a refused tag length must not panic" + + expect_fail "tag length $length must be refused on verify" \ + "$BC_RUST" "$sub" "$length" --key-file "$TMP/key" -v "" <"$TMP/msg" + assert_stderr_has "Error: couldn't build the KMAC key" + ! grep -q panicked "$LAST_STDERR" || fail "a refused tag length must not panic" +} + +test_kmac128_round_trip() { + kmac_round_trip kmac128 +} + +test_kmac256_round_trip() { + kmac_round_trip kmac256 +} + +test_kmac128_refuses_a_short_tag() { + kmac_refused_length kmac128 0 +} + +test_kmac256_refuses_a_short_tag() { + kmac_refused_length kmac256 3 +} + +test_kmac128_refuses_a_tag_that_does_not_fit() { + kmac_refused_length kmac128 9223372036854775808 +} + +test_kmac256_refuses_a_tag_that_does_not_fit() { + kmac_refused_length kmac256 9223372036854775808 +} + +run_all diff --git a/crypto/aes/benches/aes_gcm_packet_benches.rs b/crypto/aes/benches/aes_gcm_packet_benches.rs index 9c8aab7f5..ed7e0e113 100644 --- a/crypto/aes/benches/aes_gcm_packet_benches.rs +++ b/crypto/aes/benches/aes_gcm_packet_benches.rs @@ -1,15 +1,15 @@ -//! AES-256-GCM over 16 KiB packets: the per-packet cost of starting from raw key material, -//! which validates and expands the key for every packet, against starting from an -//! `AES256Key`, which did that once (and derived GCM's hash subkey once) and hands the -//! stored state to each packet's encryptor. +//! AES-256-GCM over 16 KiB packets: the per-packet cost of building the `AES256Key` from raw +//! key material for every packet, which validates and expands the key and derives GCM's hash +//! subkey each time, against building it once and handing the stored state to each packet's +//! encryptor. //! -//! Both arms stream one packet through the same `Gcm` type -- init, one `do_encrypt_out` / -//! `do_decrypt_out` over the whole packet, final -- so the only difference between them is the -//! key expansion, validation and `H`. The nonce comes from a fixed test RNG so that the RNG is not -//! part of the comparison, except in the `OS DRBG per packet` arms, which instantiate a fresh -//! `HashDRBG_SHA512` from the OS for every packet: the cost of the default-RNG constructors the -//! key path does not offer. The `init` group isolates the construction cost with no data at all, -//! and includes the one-off cost of building the key object. +//! Both arms stream one packet through the same `Gcm` type -- `create_encryptor` / +//! `create_decryptor`, one `do_encrypt_out` / `do_decrypt_out` over the whole packet, final -- so +//! the only difference between them is where `from_key_material` is paid. The nonce comes from a +//! fixed test RNG so that the RNG is not part of the comparison, except in the `OS DRBG per +//! packet` arms, which instantiate a fresh `HashDRBG_SHA512` from the OS for every packet: what a +//! per-packet default RNG would add. The `init` group isolates the construction cost with no data +//! at all. //! //! The cipher works in place, so each measurement runs on a fresh output buffer made in //! criterion's untimed setup (`iter_batched_ref`); the copy is not part of the timing. @@ -32,8 +32,8 @@ const PACKET_LEN: usize = 16 * 1024; const NONCE_LEN: usize = 12; const TAG_LEN: usize = 16; -type Enc = AES_GCM_256; -type Dec = AES_GCM_256; +type Enc<'k> = AES_GCM_256<'k, Encrypting>; +type Dec<'k> = AES_GCM_256<'k, Decrypting>; type Key = AES256Key; fn material() -> KeyMaterial<32> { @@ -77,11 +77,12 @@ fn bench_encrypt_packets(c: &mut Criterion) { let mut group = c.benchmark_group("gcm::AES_256 16KiB packet encrypt"); group.throughput(Throughput::Bytes(PACKET_LEN as u64)); - group.bench_function("KeyMaterial: do_encrypt_init_rng (expand per packet)", |b| { + group.bench_function("AES256Key: from_key_material per packet (expand per packet)", |b| { b.iter_batched_ref( || [0u8; PACKET_LEN], |out| { - let (enc, nonce) = Enc::do_encrypt_init_rng(black_box(&km), &mut rng).unwrap(); + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let (enc, nonce) = per_packet.create_encryptor(&mut rng).unwrap(); black_box((nonce, encrypt_packet(enc, black_box(&packet), out))) }, BatchSize::LargeInput, @@ -98,12 +99,14 @@ fn bench_encrypt_packets(c: &mut Criterion) { ) }); group.bench_function( - "KeyMaterial: do_encrypt_init (OS DRBG per packet, expand per packet)", + "AES256Key: from_key_material per packet (OS DRBG per packet, expand per packet)", |b| { b.iter_batched_ref( || [0u8; PACKET_LEN], |out| { - let (enc, nonce) = Enc::do_encrypt_init(black_box(&km)).unwrap(); + let mut os_rng = HashDRBG_SHA512::new_from_os(); + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let (enc, nonce) = per_packet.create_encryptor(&mut os_rng).unwrap(); black_box((nonce, encrypt_packet(enc, black_box(&packet), out))) }, BatchSize::LargeInput, @@ -132,17 +135,18 @@ fn bench_decrypt_packets(c: &mut Criterion) { // One valid packet to decrypt, made outside the timing. let mut ct = [0u8; PACKET_LEN]; - let (enc, nonce) = Enc::do_encrypt_init_rng(&km, &mut rng()).unwrap(); + let (enc, nonce) = key.create_encryptor(&mut rng()).unwrap(); let tag = encrypt_packet(enc, &packet, &mut ct); let mut group = c.benchmark_group("gcm::AES_256 16KiB packet decrypt"); group.throughput(Throughput::Bytes(PACKET_LEN as u64)); - group.bench_function("KeyMaterial: do_decrypt_init (expand per packet)", |b| { + group.bench_function("AES256Key: from_key_material per packet (expand per packet)", |b| { b.iter_batched_ref( || [0u8; PACKET_LEN], |out| { - let dec = Dec::do_decrypt_init(black_box(&km), &nonce).unwrap(); + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let dec = per_packet.create_decryptor(&nonce); black_box(decrypt_packet(dec, black_box(&ct), &tag, out)) }, BatchSize::LargeInput, @@ -174,21 +178,45 @@ fn bench_init(c: &mut Criterion) { group.bench_function("AES256Key::from_key_material (once per key)", |b| { b.iter(|| black_box(Key::from_key_material(black_box(&km)).unwrap())) }); - group.bench_function("KeyMaterial: do_encrypt_init_rng", |b| { - b.iter(|| black_box(Enc::do_encrypt_init_rng(black_box(&km), &mut rng).unwrap())) - }); + group.bench_function( + "AES256Key: from_key_material + create_encryptor (per packet)", + |b| { + b.iter(|| { + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let (enc, nonce) = per_packet.create_encryptor(&mut rng).unwrap(); + // The worker borrows the key, so what a per-packet key costs is what it takes + // to make the worker and finish it. + black_box((nonce, enc.do_encrypt_final().unwrap())) + }) + }, + ); group.bench_function("AES256Key: create_encryptor", |b| { b.iter(|| black_box(black_box(&key).create_encryptor(&mut rng).unwrap())) }); - group.bench_function("KeyMaterial: do_decrypt_init", |b| { - b.iter(|| black_box(Dec::do_decrypt_init(black_box(&km), &nonce).unwrap())) - }); + group.bench_function( + "AES256Key: from_key_material + create_decryptor (per packet)", + |b| { + b.iter(|| { + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let dec = per_packet.create_decryptor(&nonce); + black_box(dec.do_decrypt_final().is_ok()) + }) + }, + ); group.bench_function("AES256Key: create_decryptor", |b| { b.iter(|| black_box(black_box(&key).create_decryptor(&nonce))) }); - group.bench_function("KeyMaterial: do_encrypt_init (OS DRBG per call)", |b| { - b.iter(|| black_box(Enc::do_encrypt_init(black_box(&km)).unwrap())) - }); + group.bench_function( + "AES256Key: from_key_material + create_encryptor (OS DRBG per call)", + |b| { + b.iter(|| { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + let per_packet = Key::from_key_material(black_box(&km)).unwrap(); + let (enc, nonce) = per_packet.create_encryptor(&mut os_rng).unwrap(); + black_box((nonce, enc.do_encrypt_final().unwrap())) + }) + }, + ); group.bench_function("AES256Key: create_encryptor (OS DRBG per call)", |b| { b.iter(|| { let mut os_rng = HashDRBG_SHA512::new_from_os(); diff --git a/crypto/aes/benches/aes_modes_benches.rs b/crypto/aes/benches/aes_modes_benches.rs index 38d088acd..ec0cd1ad0 100644 --- a/crypto/aes/benches/aes_modes_benches.rs +++ b/crypto/aes/benches/aes_modes_benches.rs @@ -23,21 +23,21 @@ //! 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::{ - AES_CMAC_128, AES_CMAC_256, AES_GMAC_128, AES_GMAC_256, AES128Key, AES256Key, -}; -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; +use bouncycastle_aes::hazmat::AES128Internal; +use bouncycastle_aes::{AES128Key, AES256Key}; +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; use bouncycastle_cipher::modes::{ - CCM, CMAC, Cbc, CcmEncryptor, Cfb, Cfb8, Ctr, GCM_NONCE_LEN, GMAC, Gcm, + Blocks, CBC, CCM, CCM_Packet, CFB, CFB8, CMAC, CTR, Cfb, Cfb8, Ctr, GCM, GCM_NONCE_LEN, GMAC, }; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::{AeadPacketEncryptMode, ElectronicCodeBook}; +use bouncycastle_core::hazmat::{AeadDeclaredEncryptMode, EcbEncryptionKey, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey}; +use bouncycastle_core::symmetric_key::{ + AeadEncryptionKey, BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, MacGenerationKey, + SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ - AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, MAC, StreamCipherDecryptor, - StreamCipherEncryptor, SymmetricCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; use bouncycastle_rng::FixedSeedRNG; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; @@ -48,9 +48,8 @@ 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 Aes128Cfb<'k, Dir> = Cfb<'k, AES128Internal, Dir, BLOCK_LEN>; +type Aes128Cfb8<'k, Dir> = Cfb8<'k, AES128Internal, Dir, BLOCK_LEN>; /// CCM at the parameters the ACVP vectors and most protocols use: a 12-byte nonce and a full /// 16-byte tag. The mode's in-place one-shots over the keyed permutation are the direct path; @@ -59,32 +58,31 @@ const CCM_NONCE_LEN: usize = 12; const CCM_TAG_LEN: usize = 16; type Aes128CcmMode = CCM; -/// The trait adapter takes its frame size at compile time. Its one-shots are not bound by it, -/// but using the same 4 KiB message keeps this comparison representative of the public alias a +/// The frame key takes its frame size at compile time. Its one-shots are not bound by it, but +/// using the same 4 KiB message keeps this comparison representative of the public alias a /// packet protocol would choose. const CCM_BUFFER_LEN: usize = 4096; const CCM_AAD_LEN: usize = 64; -type Aes128CcmEncryptor = CcmEncryptor< - AES128Internal, - 16, - BLOCK_LEN, - CCM_NONCE_LEN, - CCM_TAG_LEN, - CCM_AAD_LEN, - CCM_BUFFER_LEN, ->; +type Aes128CcmFrameKey = + AES128Key>; /// 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 Aes128GcmKey = AES128Key>; +type Aes256GcmKey = AES256Key>; +type Aes128Ctr<'k, Dir> = Ctr<'k, AES128Internal, Dir, BLOCK_LEN, 12>; +type Aes128Ecb<'k, Dir> = Ecb<'k, AES128Internal, Dir, BLOCK_LEN>; fn key() -> KeyMaterial { 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() } +/// IVs and nonces for the throughput measurements come from a cheap deterministic RNG created +/// outside the timed loop, so they measure the mode and not OS entropy or DRBG construction. +fn iv_rng() -> FixedSeedRNG<[u8; BLOCK_LEN]> { + FixedSeedRNG::new([0x24u8; BLOCK_LEN]) +} + /// [`key`]'s bytes as a `KeyType::MACKey`, which is what CMAC takes. fn mac_key() -> KeyMaterial { let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); @@ -98,22 +96,20 @@ fn data() -> Vec<[u8; BLOCK_LEN]> { } fn bench_aes128cbc(c: &mut Criterion) { - let k = key::<16>(); + let key = AES128Key::>::from_key_material(&key::<16>()).unwrap(); + let mut rng = iv_rng(); let blocks = data(); let mut group = c.benchmark_group("modes::cbc::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); - // Serial: one block at a time is all it can do, whatever the call length. group.bench_function("16KiB encrypt -- N=8", |b| { b.iter_batched( || blocks.clone(), |mut scratch| { - let (mut enc, _) = Aes128Cbc::::do_encrypt_init(&k).unwrap(); + let (mut enc, _) = key.create_encryptor(&mut rng).unwrap(); for chunk in scratch.chunks_exact_mut(8) { - let arr: &mut [u8; 8 * BLOCK_LEN] = - chunk.as_flattened_mut().try_into().unwrap(); - enc.do_encrypt_blocks_inplace(arr.as_chunks_mut().0).unwrap(); + enc.do_encrypt_blocks_inplace(chunk).unwrap(); } black_box(&scratch); }, @@ -121,22 +117,20 @@ fn bench_aes128cbc(c: &mut Criterion) { ) }); - let (mut enc, iv) = Aes128Cbc::::do_encrypt_init(&k).unwrap(); + // ---- decryption: parallel, uses decrypt_4blocks for every four ---- + let (mut enc, iv) = key.create_encryptor(&mut rng).unwrap(); let mut ciphertext = blocks.clone(); for chunk in ciphertext.chunks_exact_mut(8) { enc.do_encrypt_blocks_inplace(chunk).unwrap(); } - // Parallel: N=8 is two fours, so every block goes through decrypt_4blocks. group.bench_function("16KiB decrypt -- N=8 (all fours)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { - let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); for chunk in scratch.chunks_exact_mut(8) { - let arr: &mut [u8; 8 * BLOCK_LEN] = - chunk.as_flattened_mut().try_into().unwrap(); - dec.do_decrypt_blocks_inplace(arr.as_chunks_mut().0).unwrap(); + dec.do_decrypt_blocks_inplace(chunk).unwrap(); } black_box(&scratch); }, @@ -147,78 +141,65 @@ fn bench_aes128cbc(c: &mut Criterion) { group.finish(); } -/// 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, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, ->( - k: &KeyMaterial, +/// Runs the 16 KiB through a freshly made 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, and the +/// caller's key (or mode constructor) supplies the worker. +fn stream_encrypt_in_calls, const INIT_DATA_LEN: usize>( + mut enc: E, scratch: &mut [u8], call_len: usize, ) { - let (mut enc, _) = E::do_encrypt_init(k).unwrap(); for piece in scratch.chunks_mut(call_len) { enc.do_encrypt_inplace(piece).unwrap(); } } -/// Runs the 16 KiB through a stream-cipher decryptor in `call_len`-byte calls. Shared as above. -fn cfb_decrypt_in_calls< - D: StreamCipherDecryptor, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, ->( - k: &KeyMaterial, - iv: &[u8; INIT_DATA_LEN], +/// Runs the 16 KiB through a freshly made stream-cipher decryptor in `call_len`-byte calls. +/// Shared as above. +fn stream_decrypt_in_calls, const INIT_DATA_LEN: usize>( + mut dec: D, scratch: &mut [u8], call_len: usize, ) { - let mut dec = D::do_decrypt_init(k, iv).unwrap(); for piece in scratch.chunks_mut(call_len) { dec.do_decrypt_inplace(piece).unwrap(); } } fn bench_cfb_aes128(c: &mut Criterion) { - let k = key::<16>(); - let flat: Vec = data().as_flattened().to_vec(); + let key = AES128Key::::from_key_material(&key::<16>()).unwrap(); + let mut rng = iv_rng(); + let blocks = data(); + let flat: Vec = blocks.as_flattened().to_vec(); let mut group = c.benchmark_group("modes::cfb::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); - // Serial: Oj+1 = CIPH_K(Cj), and Cj is the previous block's output. + // ---- encryption: serial. Oj+1 = CIPH_K(Cj), and Cj is the previous call's output ---- group.bench_function("16KiB encrypt -- N=8", |b| { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, BLOCK_LEN>( - &k, - &mut scratch, - 8 * BLOCK_LEN, - ); + let (enc, _): (Aes128Cfb, _) = key.create_encryptor(&mut rng).unwrap(); + stream_encrypt_in_calls(enc, &mut scratch, 8 * BLOCK_LEN); black_box(&scratch); }, BatchSize::LargeInput, ) }); - let (mut enc, iv) = Aes128Cfb::::do_encrypt_init(&k).unwrap(); + // ---- decryption: parallel, and uses `encrypt_4blocks` / `encrypt_2blocks` -- the FORWARD + // batch methods ---- + let (mut enc, iv) = key.create_encryptor(&mut rng).unwrap(); let mut ciphertext = flat.clone(); enc.do_encrypt_inplace(&mut ciphertext).unwrap(); - // Parallel, through the FORWARD four-block path. group.bench_function("16KiB decrypt -- N=8 (all fours)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { - cfb_decrypt_in_calls::, 16, BLOCK_LEN>( - &k, - &iv, - &mut scratch, - 8 * BLOCK_LEN, - ); + let dec: Aes128Cfb = key.create_decryptor(&iv); + stream_decrypt_in_calls(dec, &mut scratch, 8 * BLOCK_LEN); black_box(&scratch); }, BatchSize::LargeInput, @@ -234,36 +215,37 @@ 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 key = AES128Key::::from_key_material(&key::<16>()).unwrap(); + let mut rng = iv_rng(); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::cfb8::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); + // Serial by construction: I_{j+1} needs Cj, which this call just produced. group.bench_function("16KiB encrypt -- whole message in one call", |b| { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, BLOCK_LEN>( - &k, &mut scratch, DATA_LEN, - ); + let (enc, _): (Aes128Cfb8, _) = key.create_encryptor(&mut rng).unwrap(); + stream_encrypt_in_calls(enc, &mut scratch, DATA_LEN); black_box(&scratch); }, BatchSize::LargeInput, ) }); - let (mut enc, iv) = Aes128Cfb8::::do_encrypt_init(&k).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut rng).unwrap(); let mut ciphertext = flat.clone(); enc.do_encrypt_inplace(&mut ciphertext).unwrap(); + // One call: fours, then pairs, then the tail. This is the batched path. group.bench_function("16KiB decrypt -- whole message in one call (batched)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { - cfb_decrypt_in_calls::, 16, BLOCK_LEN>( - &k, &iv, &mut scratch, DATA_LEN, - ); + let dec: Aes128Cfb8 = key.create_decryptor(&iv); + stream_decrypt_in_calls(dec, &mut scratch, DATA_LEN); black_box(&scratch); }, BatchSize::LargeInput, @@ -277,7 +259,8 @@ 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 key = AES128Key::::from_key_material(&key::<16>()).unwrap(); + let mut rng = FixedSeedRNG::new([0x24u8; 12]); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::ctr::AES_128"); @@ -287,11 +270,8 @@ fn bench_ctr_aes128(c: &mut Criterion) { b.iter_batched( || flat.clone(), |mut scratch| { - cfb_encrypt_in_calls::, 16, 12>( - &k, - &mut scratch, - 8 * BLOCK_LEN, - ); + let (enc, _): (Aes128Ctr, _) = key.create_encryptor(&mut rng).unwrap(); + stream_encrypt_in_calls(enc, &mut scratch, 8 * BLOCK_LEN); black_box(&scratch); }, BatchSize::LargeInput, @@ -304,7 +284,7 @@ fn bench_ctr_aes128(c: &mut Criterion) { /// 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. fn bench_ecb_aes128(c: &mut Criterion) { - let k = key::<16>(); + let key = AES128Key::>::from_key_material(&key::<16>()).unwrap(); let blocks = data(); let mut group = c.benchmark_group("modes::ecb::AES_128"); @@ -314,11 +294,9 @@ fn bench_ecb_aes128(c: &mut Criterion) { b.iter_batched( || blocks.clone(), |mut scratch| { - let (mut enc, _) = Aes128Ecb::::do_encrypt_init(&k).unwrap(); + let mut enc: Aes128Ecb = key.create_encryptor(); for chunk in scratch.chunks_exact_mut(8) { - let arr: &mut [u8; 8 * BLOCK_LEN] = - chunk.as_flattened_mut().try_into().unwrap(); - enc.do_encrypt_blocks_inplace(arr.as_chunks_mut().0).unwrap(); + enc.do_encrypt_blocks_inplace(chunk).unwrap(); } black_box(&scratch); }, @@ -329,16 +307,45 @@ fn bench_ecb_aes128(c: &mut Criterion) { group.finish(); } -/// `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. +/// What a message pays before its first byte, split as the key path splits it: `from_key_material` +/// is the key expansion, paid once per key; `create_encryptor` borrows the key and draws an IV, +/// and `create_decryptor` only borrows it, paid per message. The IV comes from the cheap +/// deterministic RNG, so the draw's cost is the copy and not a DRBG. Worth its own measurement, +/// because for short messages construction dominates. fn bench_init(c: &mut Criterion) { let k128 = key::<16>(); + let k256 = key::<32>(); + let iv = [0u8; BLOCK_LEN]; + let mut rng = iv_rng(); + let cbc128 = AES128Key::>::from_key_material(&k128).unwrap(); + let cfb128 = AES128Key::::from_key_material(&k128).unwrap(); let mut group = c.benchmark_group("modes::init"); - group.bench_function("AES_128 do_encrypt_init (key schedule + IV)", |b| { - b.iter(|| black_box(Aes128Cbc::::do_encrypt_init(black_box(&k128)).unwrap().1)) + group.bench_function("AES_128 from_key_material, CBC (key schedule)", |b| { + b.iter(|| black_box(AES128Key::>::from_key_material(black_box(&k128)).unwrap())) + }); + group.bench_function("AES_256 from_key_material, CBC (key schedule)", |b| { + b.iter(|| black_box(AES256Key::>::from_key_material(black_box(&k256)).unwrap())) + }); + group.bench_function("AES_128 create_encryptor, CBC (borrow + IV draw)", |b| { + b.iter(|| black_box(black_box(&cbc128).create_encryptor(&mut rng).unwrap().1)) + }); + group.bench_function("AES_128 create_decryptor, CBC (borrow)", |b| { + b.iter(|| black_box(black_box(&cbc128).create_decryptor(&iv))) + }); + + // CFB does exactly the same work here -- one key expansion per key, one borrow plus an IV + // draw per encryptor -- so these should match the CBC numbers. A divergence would mean one + // mode is doing something extra at construction time. + group.bench_function("AES_128 from_key_material, CFB (key schedule)", |b| { + b.iter(|| black_box(AES128Key::::from_key_material(black_box(&k128)).unwrap())) + }); + group.bench_function("AES_128 create_encryptor, CFB (borrow + IV draw)", |b| { + b.iter(|| black_box(black_box(&cfb128).create_encryptor(&mut rng).unwrap().1)) + }); + group.bench_function("AES_128 create_decryptor, CFB (borrow)", |b| { + b.iter(|| black_box(black_box(&cfb128).create_decryptor(&iv))) }); group.finish(); @@ -383,7 +390,7 @@ fn bench_ccm_aes128(c: &mut Criterion) { }); // AAD only: CCM as a pure authentication mode, which Sec 5.3's footnote calls out as the - // empty-payload degenerate case. + // empty-payload degenerate case. One cipher call per block, so this is the CTR-comparable half. group.bench_function("authenticate 16KiB AAD, empty payload", |b| { b.iter(|| { let mut out: [u8; 0] = []; @@ -402,14 +409,16 @@ fn bench_ccm_aes128(c: &mut Criterion) { group.finish(); } -/// The [`AEADCipherEncryptor`] one-shot against the mode's in-place one-shot on the same message. +/// The frame key's [`AeadEncryptionKey`] one-shot against the mode's in-place one-shot on the +/// same message. /// -/// Both end in the same packet implementation; the trait form copies the message into the -/// caller's output buffer and draws the nonce. A cheap deterministic RNG, created once outside -/// the timed loop, isolates that draw from OS entropy and DRBG construction. +/// Both end in the same `Ccm` implementation; the key form copies the message into the caller's +/// output buffer and draws the nonce. A cheap deterministic RNG, created once outside the timed +/// loop, isolates that draw from OS entropy and DRBG construction. fn bench_ccm_one_shot_pair(c: &mut Criterion) { - let key = key::<16>(); - let perm = AES128Internal::new(&key).unwrap(); + let material = key::<16>(); + let key = Aes128CcmFrameKey::from_key_material(&material).unwrap(); + let perm = AES128Internal::new(&material).unwrap(); let data = [0xA5u8; CCM_BUFFER_LEN]; let no_aad: [u8; 0] = []; let nonce = [0x24u8; CCM_NONCE_LEN]; @@ -418,19 +427,14 @@ fn bench_ccm_one_shot_pair(c: &mut Criterion) { let mut group = c.benchmark_group("modes::ccm::one_shot"); group.throughput(Throughput::Bytes(CCM_BUFFER_LEN as u64)); - group.bench_function("AEADCipherEncryptor::encrypt_detached_rng_out 4KiB", |b| { + group.bench_function("AeadEncryptionKey::encrypt_detached_out 4KiB", |b| { b.iter_batched_ref( || [0u8; CCM_BUFFER_LEN], |out| { black_box( - Aes128CcmEncryptor::encrypt_detached_rng_out( - black_box(&key), - &mut rng, - &no_aad, - black_box(&data), - out, - ) - .unwrap(), + black_box(&key) + .encrypt_detached_out(&mut rng, &no_aad, black_box(&data), out) + .unwrap(), ) }, BatchSize::LargeInput, @@ -466,7 +470,7 @@ fn bench_ccm_one_shot_pair(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 = Aes128GcmKey::from_key_material(&key::<16>()).unwrap(); let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; let mut rng = FixedSeedRNG::new([0x24u8; GCM_NONCE_LEN]); @@ -479,14 +483,9 @@ fn bench_gcm_aes128(c: &mut Criterion) { || [0u8; DATA_LEN], |out| { black_box( - Aes128Gcm::::encrypt_detached_rng_out( - black_box(&key), - &mut rng, - &no_aad, - black_box(&data), - out, - ) - .unwrap(), + black_box(&key) + .encrypt_detached_out(&mut rng, &no_aad, black_box(&data), out) + .unwrap(), ) }, BatchSize::LargeInput, @@ -499,14 +498,9 @@ fn bench_gcm_aes128(c: &mut Criterion) { b.iter(|| { let mut out: [u8; 0] = []; black_box( - Aes128Gcm::::encrypt_detached_rng_out( - black_box(&key), - &mut rng, - black_box(&data), - &no_aad, - &mut out, - ) - .unwrap(), + black_box(&key) + .encrypt_detached_out(&mut rng, black_box(&data), &no_aad, &mut out) + .unwrap(), ) }) }); @@ -517,7 +511,7 @@ 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. fn bench_gcm_aes256(c: &mut Criterion) { - let key = key::<32>(); + let key = Aes256GcmKey::from_key_material(&key::<32>()).unwrap(); let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; let mut rng = FixedSeedRNG::new([0x24u8; GCM_NONCE_LEN]); @@ -530,14 +524,9 @@ fn bench_gcm_aes256(c: &mut Criterion) { || [0u8; DATA_LEN], |out| { black_box( - Aes256Gcm::::encrypt_detached_rng_out( - black_box(&key), - &mut rng, - &no_aad, - black_box(&data), - out, - ) - .unwrap(), + black_box(&key) + .encrypt_detached_out(&mut rng, &no_aad, black_box(&data), out) + .unwrap(), ) }, BatchSize::LargeInput, @@ -548,24 +537,29 @@ fn bench_gcm_aes256(c: &mut Criterion) { } /// CMAC is serial like CBC encryption (SP 800-38B Sec 6.2 step 6 chains every block), so it -/// should come out close to `modes::cbc::AES_128` encryption. The key-object route skips the -/// per-message subkey derivation, one block encryption, which is invisible at 16 KiB. +/// should come out close to `modes::cbc::AES_128` encryption. Building the key once skips the +/// per-tag key expansion and subkey derivation, one block encryption, which is invisible at 16 +/// KiB; the `from_key_material per tag` arms pay it every time, for the comparison. fn bench_cmac(c: &mut Criterion) { let data: Vec = data().concat(); let mut group = c.benchmark_group("modes::cmac"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); let key128 = mac_key::<16>(); - group.bench_function("AES_128 MAC::new 16KiB", |b| { - b.iter(|| black_box(AES_CMAC_128::new(&key128).unwrap().mac(black_box(&data)))) + group.bench_function("AES_128 from_key_material per tag 16KiB", |b| { + b.iter(|| { + black_box(AES128Key::::from_key_material(&key128).unwrap().mac(black_box(&data))) + }) }); let key_object_128 = AES128Key::::from_key_material(&key128).unwrap(); group.bench_function("AES_128 key object 16KiB", |b| { b.iter(|| black_box(key_object_128.mac(black_box(&data)))) }); let key256 = mac_key::<32>(); - group.bench_function("AES_256 MAC::new 16KiB", |b| { - b.iter(|| black_box(AES_CMAC_256::new(&key256).unwrap().mac(black_box(&data)))) + group.bench_function("AES_256 from_key_material per tag 16KiB", |b| { + b.iter(|| { + black_box(AES256Key::::from_key_material(&key256).unwrap().mac(black_box(&data))) + }) }); let key_object_256 = AES256Key::::from_key_material(&key256).unwrap(); group.bench_function("AES_256 key object 16KiB", |b| { @@ -576,7 +570,7 @@ fn bench_cmac(c: &mut Criterion) { } /// GMAC is GHASH over the message plus two cipher calls, so it is several times faster than CMAC -/// and the same as GCM's AAD-only measurement; the key object saves the schedule and `H` per tag. +/// and the same as GCM's AAD-only measurement. fn bench_gmac(c: &mut Criterion) { let data: Vec = data().concat(); let mut group = c.benchmark_group("modes::gmac"); @@ -584,12 +578,6 @@ fn bench_gmac(c: &mut Criterion) { let mut rng = FixedSeedRNG::new([0x24u8; GCM_NONCE_LEN]); let key128 = mac_key::<16>(); - group.bench_function("AES_128 Gmac::new 16KiB", |b| { - b.iter(|| { - let (gen_, _) = AES_GMAC_128::::new(&key128, &mut rng).unwrap(); - black_box(gen_.mac(black_box(&data))) - }) - }); let key_object_128 = AES128Key::::from_key_material(&key128).unwrap(); group.bench_function("AES_128 key object 16KiB", |b| { b.iter(|| { @@ -598,12 +586,6 @@ fn bench_gmac(c: &mut Criterion) { }) }); let key256 = mac_key::<32>(); - group.bench_function("AES_256 Gmac::new 16KiB", |b| { - b.iter(|| { - let (gen_, _) = AES_GMAC_256::::new(&key256, &mut rng).unwrap(); - black_box(gen_.mac(black_box(&data))) - }) - }); let key_object_256 = AES256Key::::from_key_material(&key256).unwrap(); group.bench_function("AES_256 key object 16KiB", |b| { b.iter(|| { diff --git a/crypto/aes/src/cbc.rs b/crypto/aes/src/cbc.rs index f853644e4..b31d82ef4 100644 --- a/crypto/aes/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -1,147 +1,5 @@ -//! Type aliases for AES in CBC mode (NIST SP 800-38A §6.2), with padding. -//! -//! See [`bouncycastle_cipher::modes::cbc`] for details on the CipherBlockChaining construction. -//! -//! The aliases here are padded block ciphers that accept input of any size; `NoPadding` accepts -//! only whole blocks but goes through the same adapter. The unpadded mode underneath them, which -//! implements the block-cipher traits directly, is [`Cbc`] and is not re-exported from this crate. -//! -//! # Usage Examples -//! -//! ## One-shot API -//! -//! 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_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_cipher::padding::PKCS7; -//! -//! // Define ourselves convenience types. -//! 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"); -//! -//! // An arbitrary plaintext to encrypt -//! // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. -//! let plaintext = [0x5Au8; 50]; -//! -//! // The IV is generated for you and returned; there is no API for supplying one. -//! let (iv, ciphertext) = AESEnc::encrypt(&key, &plaintext).expect("encryption"); -//! assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); -//! -//! let recovered = AESDec::decrypt(&key, &iv, &ciphertext).expect("decryption"); -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! ## Streaming API -//! -//! 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_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_cipher::padding::PKCS7; -//! -//! // Define ourselves convenience types. -//! type AESEnc = AES_CBC_128; -//! type AESDec = AES_CBC_128; -//! -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); -//! -//! // An arbitrary plaintext to encrypt -//! let plaintext = [0x5Au8; 50]; -//! -//! // The streaming (chunked) API allows for data to be handed to the cipher as it arrives, in chunks -//! // of any length, but it will only be processed once a full block has been received. -//! // Here, we will use 7-byte chunks -//! let (mut encryptor, iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); -//! -//! let mut ciphertext = Vec::new(); -//! -//! for piece in plaintext.chunks(7) { -//! let mut out = [0u8; AES_BLOCK_LEN]; -//! let bytes_written = encryptor.do_encrypt_out(piece, &mut out).expect("encryption"); -//! -//! // If that doesn't complete a block, then nothing is written. -//! if bytes_written != 0 { -//! ciphertext.extend_from_slice(&out[..bytes_written]); -//! } -//! } -//! let (last_block, last_len) = encryptor.do_encrypt_final().expect("padding the final block"); -//! ciphertext.extend_from_slice(&last_block[..last_len]); -//! assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); -//! -//! // Decrypt the ciphertext in 19-byte chunks. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &iv).expect("decrypt init"); -//! let mut recovered = Vec::new(); -//! for piece in ciphertext.chunks(19) { -//! let mut out = [0u8; AES_BLOCK_LEN]; -//! let bytes_written = decryptor.do_decrypt_out(piece, &mut out).expect("decryption"); -//! if bytes_written != 0 { -//! recovered.extend_from_slice(&out[..bytes_written]); -//! } -//! } -//! let (last_block, last_len) = decryptor.do_decrypt_final().expect("a valid final block"); -//! recovered.extend_from_slice(&last_block[..last_len]); -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! ## With no padding scheme -//! -//! With [`NoPadding`] nothing is added, and a message that is not a whole number of blocks is an -//! 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_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(); -//! -//! // A whole block is fine, and comes out the same length. -//! let mut out = [0u8; 16]; -//! let (_iv, written) = Enc::encrypt_out(&key, &[0u8; 16], &mut out).expect("aligned"); -//! assert_eq!(written, 16); -//! -//! // Five bytes is not, and is refused rather than padded. -//! let mut out = [0u8; 16]; -//! assert!(Enc::encrypt_out(&key, b"hello", &mut out).is_err()); -//! ``` -//! -//! The padding scheme is part of the type, so the two schemes are different types and cannot be -//! 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_cipher::Encrypting; -//! use bouncycastle_cipher::padding::{NoPadding, PKCS7}; -//! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! -//! // Built as NoPadding, annotated as PKCS7: mismatched types. -//! let (enc, _iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); -//! let _mismatched: AES_CBC_128 = enc; -//! ``` -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::cbc`] apply. +//! Type aliases for AES in CBC mode (NIST SP 800-38A §6.2), with padding. The docs are on +//! [`AES_CBC_128`], which the crate root re-exports. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; @@ -156,23 +14,161 @@ use bouncycastle_cipher::modes::cbc; #[allow(unused_imports)] use bouncycastle_cipher::padding::{NoPadding, PKCS7}; #[allow(unused_imports)] -use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; // end of imports needed for docs /// AES-128 in CBC mode with a padding scheme. +/// +/// See [`bouncycastle_cipher::modes::cbc`] for details on the CipherBlockChaining construction. +/// +/// The aliases here name the padded workers, which accept input of any size; `NoPadding` accepts +/// only whole blocks but goes through the same adapter. The unpadded mode underneath them, which +/// implements the block-cipher traits directly, is [`Cbc`] and is not re-exported from this crate. +/// The workers come from a key: [`AES128Key>`](crate::AES128Key) (or `CBC`) +/// hands them out through the [`SymmetricEncryptionKey`] / [`SymmetricDecryptionKey`] pair, and +/// `AES128Key>` hands out [`Cbc`] itself through the block pair. +/// +/// # Usage Examples +/// +/// ## One-shot API +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::CBC; +/// use bouncycastle_cipher::padding::PKCS7; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::>::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. +/// let plaintext = [0x5Au8; 50]; +/// +/// // The IV is drawn from `rng` and returned; there is no API for supplying one. +/// let (iv, ciphertext) = key.encrypt(&mut rng, &plaintext).expect("encryption"); +/// assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); +/// +/// let recovered = key.decrypt(&iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// ## Streaming API +/// +/// For data that arrives in pieces, the workers' streaming methods can be used: +/// +/// ``` +/// use bouncycastle_aes::{AES128Key, AES_BLOCK_LEN}; +/// use bouncycastle_cipher::modes::CBC; +/// use bouncycastle_cipher::padding::PKCS7; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::>::from_key_material(&material).expect("a valid AES-128 key"); +/// +/// let plaintext = [0x5Au8; 50]; +/// +/// // Data can be handed to the encryptor as it arrives, in pieces of any length, but a block is +/// // only written out once it is complete. Here, 7-byte pieces. +/// let (mut encryptor, iv) = key.create_encryptor(&mut rng).expect("IV draw"); +/// +/// let mut ciphertext = Vec::new(); +/// for piece in plaintext.chunks(7) { +/// let mut out = [0u8; AES_BLOCK_LEN]; +/// let bytes_written = encryptor.do_encrypt_out(piece, &mut out).expect("encryption"); +/// // If that doesn't complete a block, then nothing is written. +/// ciphertext.extend_from_slice(&out[..bytes_written]); +/// } +/// let (last_block, last_len) = encryptor.do_encrypt_final().expect("padding the final block"); +/// ciphertext.extend_from_slice(&last_block[..last_len]); +/// assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); +/// +/// // Decrypt the ciphertext in 19-byte pieces; the decryptor is infallible to set up. +/// let mut decryptor = key.create_decryptor(&iv); +/// let mut recovered = Vec::new(); +/// for piece in ciphertext.chunks(19) { +/// let mut out = [0u8; AES_BLOCK_LEN]; +/// let bytes_written = decryptor.do_decrypt_out(piece, &mut out).expect("decryption"); +/// recovered.extend_from_slice(&out[..bytes_written]); +/// } +/// let (last_block, last_len) = decryptor.do_decrypt_final().expect("a valid final block"); +/// recovered.extend_from_slice(&last_block[..last_len]); +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// ## With no padding scheme +/// +/// With [`NoPadding`] nothing is added, and a message that is not a whole number of blocks is an +/// error at `do_encrypt_final` rather than something silently padded: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CBC; +/// use bouncycastle_cipher::padding::NoPadding; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::>::from_key_material(&material) +/// .expect("a valid AES-128 key"); +/// +/// // A whole block is fine, and comes out the same length. +/// let mut out = [0u8; 16]; +/// let (written, _iv) = key.encrypt_out(&mut rng, &[0u8; 16], &mut out).expect("aligned"); +/// assert_eq!(written, 16); +/// +/// // Five bytes is not, and is refused rather than padded. +/// assert!(key.encrypt_out(&mut rng, b"hello", &mut out).is_err()); +/// ``` +/// +/// The padding scheme is part of the key type, so the two schemes' workers are different types +/// and cannot be interchanged. A value built with one will not satisfy a binding annotated with +/// the other. +/// +/// ```compile_fail +/// use bouncycastle_aes::{AES_CBC_128, AES128Key}; +/// use bouncycastle_cipher::Encrypting; +/// use bouncycastle_cipher::modes::CBC; +/// use bouncycastle_cipher::padding::{NoPadding, PKCS7}; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::>::from_key_material(&material) +/// .expect("a valid AES-128 key"); +/// +/// // Built from a NoPadding key, annotated as PKCS7: mismatched types. +/// let (enc, _iv) = key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).expect("an IV"); +/// let _mismatched: AES_CBC_128 = enc; +/// ``` +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::cbc`] apply. #[allow(non_camel_case_types)] -pub type AES_CBC_128 = ::Select< +pub type AES_CBC_128<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc<'k, AES128Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 16, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc<'k, AES128Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 16, AES_BLOCK_LEN, AES_BLOCK_LEN, >, @@ -180,18 +176,16 @@ pub type AES_CBC_128 = ::Select< /// AES-192 in CBC mode with a padding scheme. #[allow(non_camel_case_types)] -pub type AES_CBC_192 = ::Select< +pub type AES_CBC_192<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc<'k, AES192Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 24, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc<'k, AES192Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 24, AES_BLOCK_LEN, AES_BLOCK_LEN, >, @@ -199,18 +193,16 @@ pub type AES_CBC_192 = ::Select< /// AES-256 in CBC mode with a padding scheme. See [`AES_CBC_128`]. #[allow(non_camel_case_types)] -pub type AES_CBC_256 = ::Select< +pub type AES_CBC_256<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Cbc, + Cbc<'k, AES256Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 32, AES_BLOCK_LEN, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Cbc, + Cbc<'k, AES256Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 32, AES_BLOCK_LEN, AES_BLOCK_LEN, >, diff --git a/crypto/aes/src/ccm.rs b/crypto/aes/src/ccm.rs index 6c3b038bd..36b9e1111 100644 --- a/crypto/aes/src/ccm.rs +++ b/crypto/aes/src/ccm.rs @@ -1,196 +1,10 @@ -//! Type aliases for AES in CCM mode (NIST SP 800-38C). -//! -//! See [`bouncycastle_cipher::modes::ccm`] for details on the Counter with CBC-MAC construction. -//! -//! The aliases here are authenticated ciphers: encryption produces a tag as well as a ciphertext, -//! and decryption either returns the plaintext or fails the tag check. `Dir` is [`Encrypting`] or -//! [`Decrypting`]; the wrong direction is a compile error, not a runtime check. -//! -//! CCM must know the payload length before it starts (Sec 3), and the generic traits' streaming -//! constructor is handed no lengths, so [`AES_CCM_128`] and friends fix the payload length in the -//! type as the const parameter `DATA_LEN` -- the fixed frame of a packet protocol -- hold back -//! nothing but up to `AAD_LEN` bytes of AAD, and refuse any other amount of payload. Their -//! one-shots, with the whole message in hand, take any payload and AAD length. The nonce is -//! generated unless the caller supplies one through the trait's `_nonce` forms, which CCM -//! permits because it requires the nonce to be unique but not random (Sec 5.3), so a caller with -//! a counter can do better than a draw from a DRBG. -//! -//! A mode-bound key, [`AES128Key`]`>` and friends, is the other way onto the mode: -//! its constructors take the AAD and payload lengths, so its workers hold nothing back, and its -//! in-place one-shots take any length with no second buffer. -//! -//! # The nonce and tag length are parametrizable -//! -//! Unlike the other aliases in this crate, these do not pin everything: `NONCE_LEN` and `TAG_LEN` -//! are exposed as parameters. -//! -//! * **`NONCE_LEN` (the spec's `n`) fixes the maximum payload.** NIST SP 800-38C A.1 requires -//! `n + q = 15`, and `q` bounds the payload at `2^8q - 1` bytes. So a 13-byte nonce caps a message -//! at 64 KiB - 1, and a 7-byte nonce lifts the cap entirely at the cost of nonce space. -//! * **`TAG_LEN` (the spec's `t`) is the forgery bound.** Sec B.2: "a value of Tlen that is less -//! than 64 shall not be used without a careful analysis of the risks of accepting inauthentic -//! data as authentic". -//! -//! Both are still checked at compile time against A.1's permitted sets, so a wrong value is a -//! compile error rather than a runtime `Err`. A generated nonce needs `NONCE_LEN` of at least 12; -//! a supplied one any A.1 length. -//! -//! [`CCM_NONCE_LEN`] and [`CCM_TAG_LEN`] are the default pair -- a 12-byte nonce and a -//! 16-byte tag, which is what the NIST ACVP vectors and most protocols use -- for callers who have -//! no reason to choose otherwise: -//! -//! ```text -//! // 12-byte nonce, 16-byte tag, up to 64 bytes of AAD, 2 KiB frames -//! AES_CCM_128 -//! ``` -//! -//! Though some alternative choices do exist, for example: -//! ```text -//! // IEEE 802.11 CCMP's 13 byte nonce and 8 byte tag -//! AES_CCM_128 -//! ``` -//! -//! # Usage Examples -//! -//! ## Generic AEAD API -//! -//! For code written against [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], [`AES_CCM_128`] -//! generates the nonce and returns it. The streaming methods take exactly `DATA_LEN` bytes of -//! 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_cipher::{Decrypting, Encrypting}; -//! -//! // Up to 64 bytes of AAD, and frames of exactly 2 KiB -- comfortably above an 802.11 frame, -//! // the packet size CCM was designed for. -//! 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 frame = [0x5Au8; 2048]; -//! -//! // The one-shots: a frame, or any other length, since they have the whole message in hand. -//! let (nonce, ciphertext, tag) = AESEnc::encrypt_detached(&key, b"header", &frame).expect("encryption"); -//! let plaintext = AESDec::decrypt_detached(&key, &nonce, b"header", &ciphertext, &tag).expect("decryption"); -//! assert_eq!(&plaintext[..], &frame[..]); -//! let (nonce, ciphertext, tag) = AESEnc::encrypt_detached(&key, b"header", b"message").expect("encryption"); -//! let plaintext = AESDec::decrypt_detached(&key, &nonce, b"header", &ciphertext, &tag).expect("decryption"); -//! assert_eq!(&plaintext[..], b"message"); -//! -//! // The streaming methods: exactly one frame, released as it is processed. -//! let (mut enc, nonce) = AESEnc::do_encrypt_init(&key).expect("init"); -//! enc.do_update_aad(b"header").expect("aad"); -//! let mut sealed = vec![0u8; 2048]; -//! let n = enc.do_encrypt_out(&frame, &mut sealed).expect("the whole frame comes out"); -//! assert_eq!(n, 2048); -//! let (_, _, tag) = enc.do_encrypt_final_detached().expect("the tag"); -//! -//! let mut dec = AESDec::do_decrypt_init(&key, &nonce).expect("init"); -//! dec.do_update_aad(b"header").expect("aad"); -//! let mut opened = vec![0u8; 2048]; -//! dec.do_decrypt_out(&sealed, &mut opened).expect("released, but not yet authenticated"); -//! dec.do_decrypt_final_detached(&tag).expect("...until the tag verifies"); -//! assert_eq!(opened, frame); -//! ``` -//! -//! ## A supplied nonce, in the spec's own layout -//! -//! The `_nonce` one-shots take the caller's nonce, and the inline forms produce and consume the -//! spec's own ciphertext layout, `ciphertext || tag` (Sec 6.1 step 8): -//! -//! ``` -//! use bouncycastle_aes::{AES_CCM_256, CCM_NONCE_LEN, CCM_TAG_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // The frame parameters are unused by the one-shots. -//! type AESEnc = AES_CCM_256; -//! type AESDec = AES_CCM_256; -//! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); -//! -//! // Supplied, not generated. It is the caller's responsibility that it never repeat under this key. -//! let nonce = [0x01u8; CCM_NONCE_LEN]; -//! -//! // The associated data is authenticated but not encrypted; the message is both. -//! let aad = b"header, sent in the clear"; -//! let message = b"a message of no particular length"; -//! -//! let sealed = AESEnc::encrypt_with_aad_nonce(&key, &nonce, aad, message).expect("encryption"); -//! assert_eq!(sealed.len(), message.len() + CCM_TAG_LEN, "the ciphertext plus the tag"); -//! -//! let opened = AESDec::decrypt_with_aad(&key, &nonce, aad, &sealed).expect("decryption"); -//! assert_eq!(&opened[..], message); -//! -//! // Tampering with either the ciphertext or the associated data fails the tag check. -//! let mut tampered = sealed.clone(); -//! tampered[0] ^= 1; -//! assert!(AESDec::decrypt_with_aad(&key, &nonce, aad, &tampered).is_err()); -//! assert!(AESDec::decrypt_with_aad(&key, &nonce, b"other header", &sealed).is_err()); -//! ``` -//! -//! ## In place, through a key -//! -//! A message already in a buffer of its own is sealed and opened there, with no second buffer: -//! -//! ``` -//! use bouncycastle_aes::AES128Key; -//! use bouncycastle_cipher::modes::CCM; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::symmetric_key::{AeadPacketDecryptionKey, AeadPacketEncryptionKey, FromKeyMaterial}; -//! -//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); -//! // Validated and expanded once, for any number of messages. -//! let key = AES128Key::>::from_key_material(&material).expect("a usable key"); -//! let nonce = [0x02u8; 12]; -//! -//! let mut buf = *b"a short packet"; -//! let tag = key.encrypt_detached_in_place_nonce(&nonce, &[], &mut buf).expect("encryption"); -//! key.decrypt_detached_in_place(&nonce, &[], &mut buf, &tag).expect("decryption"); -//! assert_eq!(&buf, b"a short packet"); -//! ``` -//! -//! # Memory Usage -//! -//! The value held between calls: -//! -//! | Type | AES-128 | AES-192 | AES-256 | -//! |---|---|---|---| -//! | [`AES128Key`]`>` and friends: the key schedule | 176 B | 208 B | 240 B | -//! | its workers, encrypting / decrypting | 264 B / 288 B | 296 B / 320 B | 328 B / 352 B | -//! | [`AES_CCM_128`] and friends, encrypting, `AAD_LEN = 64` | 344 B | 376 B | 408 B | -//! | [`AES_CCM_128`] and friends, decrypting, `AAD_LEN = 64` | 368 B | 400 B | 432 B | -//! -//! The difference between the key sizes is the key schedule; the pair adds the mode's state, the -//! `AAD_LEN`-byte AAD buffer and, on the decrypting side, the held-back tag. Peak stack over a -//! 16 KiB frame with AES-128, measured with massif on x86-64 in release mode by -//! `mem_usage_benches/src/bench_ccm_mem_usage.rs`, including the caller's own 16 KiB arrays: -//! -//! | Path | Peak stack | -//! |---|---| -//! | process start-up alone | 7 680 B | -//! | `AES128Key>` in place, one array | 19 328 B | -//! | `AES_CCM_128` streaming encrypt, two arrays | 37 400 B | -//! | `AES_CCM_128` streaming decrypt, two arrays | 36 368 B | -//! | `AES_CCM_128` one-shot, two arrays | 36 904 B | -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::ccm`] apply. Above all, a nonce -//! the caller supplies must never repeat under one key. +//! Type aliases for AES in CCM mode (NIST SP 800-38C). The docs are on [`AES_CCM_128`], which the +//! crate root re-exports. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::Direction; -use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor}; +use bouncycastle_cipher::modes::{CcmPacketDecryptor, CcmPacketEncryptor}; // Imports needed for docs #[allow(unused_imports)] @@ -198,6 +12,10 @@ use crate::AES128Key; #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; #[allow(unused_imports)] +use bouncycastle_core::symmetric_key::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, +}; +#[allow(unused_imports)] use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; // end of imports needed for docs @@ -214,47 +32,238 @@ pub const CCM_TAG_LEN: usize = 16; /// frames of exactly `DATA_LEN` payload bytes. /// /// This is the fixed-frame pair, for code written against the generic AEAD traits: the streaming -/// methods accept exactly `DATA_LEN` bytes of payload and up to `AAD_LEN` of AAD, the one-shots -/// any amount of either. The nonce is generated unless the caller supplies one through -/// [`AEADCipherEncryptor::do_encrypt_init_nonce`] or the `_nonce` one-shots; a generated nonce -/// needs `NONCE_LEN` of at least 12, a supplied one any A.1 length. `NONCE_LEN` must be 7..=13 +/// methods accept exactly `DATA_LEN` bytes of payload and up to `AAD_LEN` of AAD. It comes from +/// an [`AES128Key`]`>`, whose `create_encryptor` +/// draws the nonce and whose `create_encryptor_nonce` and `_nonce` one-shots take the caller's; +/// a drawn nonce needs `NONCE_LEN` of at least 12, a supplied one any A.1 length. `NONCE_LEN` must be 7..=13 /// and `TAG_LEN` one of 4, 6, 8, 10, 12, 14, 16 (A.1); anything else is a compile error. Use -/// [`CCM_NONCE_LEN`] and [`CCM_TAG_LEN`] if you have no reason to choose. See [`CcmEncryptor`] +/// [`CCM_NONCE_LEN`] and [`CCM_TAG_LEN`] if you have no reason to choose. See [`CcmPacketEncryptor`] /// for the rules. +/// +/// See [`bouncycastle_cipher::modes::ccm`] for details on the Counter with CBC-MAC construction. +/// +/// The aliases here are authenticated ciphers: encryption produces a tag as well as a ciphertext, +/// and decryption either returns the plaintext or fails the tag check. `Dir` is [`Encrypting`] or +/// [`Decrypting`]; the wrong direction is a compile error, not a runtime check. +/// +/// CCM must know the payload length before it starts (Sec 3), and the generic AEAD pair's +/// `create_encryptor` is handed no lengths, so [`AES_CCM_128`] and friends fix the payload length +/// in the type as the const parameter `DATA_LEN` -- the fixed frame of a packet protocol -- hold +/// back nothing but up to `AAD_LEN` bytes of AAD, and refuse any other amount of payload. They +/// come from a key, [`AES128Key`]`>` and friends, +/// through the [`AeadEncryptionKey`] / [`AeadDecryptionKey`] pair, whose one-shots take the +/// frame length too. The nonce is drawn from the caller's RNG unless supplied through the +/// `_nonce` forms, which CCM permits because it requires the nonce to be unique but not random +/// (Sec 5.3), so a caller with a counter can do better than a draw from a DRBG. +/// +/// A message of any length goes through the other key, [`AES128Key`]`>` +/// and friends: its [`AeadDeclaredEncryptionKey`] / [`AeadDeclaredDecryptionKey`] constructors take +/// the AAD and payload lengths, so its workers hold nothing back, and its in-place one-shots +/// take any length with no second buffer. +/// +/// # The nonce and tag length are parametrizable +/// +/// Unlike the other aliases in this crate, these do not pin everything: `NONCE_LEN` and `TAG_LEN` +/// are exposed as parameters. +/// +/// * **`NONCE_LEN` (the spec's `n`) fixes the maximum payload.** NIST SP 800-38C A.1 requires +/// `n + q = 15`, and `q` bounds the payload at `2^8q - 1` bytes. So a 13-byte nonce caps a message +/// at 64 KiB - 1, and a 7-byte nonce lifts the cap entirely at the cost of nonce space. +/// * **`TAG_LEN` (the spec's `t`) is the forgery bound.** Sec B.2: "a value of Tlen that is less +/// than 64 shall not be used without a careful analysis of the risks of accepting inauthentic +/// data as authentic". +/// +/// Both are still checked at compile time against A.1's permitted sets, so a wrong value is a +/// compile error rather than a runtime `Err`. A drawn nonce needs `NONCE_LEN` of at least 12; +/// a supplied one any A.1 length. +/// +/// [`CCM_NONCE_LEN`] and [`CCM_TAG_LEN`] are the default pair -- a 12-byte nonce and a +/// 16-byte tag, which is what the NIST ACVP vectors and most protocols use -- for callers who have +/// no reason to choose otherwise: +/// +/// ```text +/// // 12-byte nonce, 16-byte tag, up to 64 bytes of AAD, 2 KiB frames +/// AES128Key> +/// ``` +/// +/// Though some alternative choices do exist, for example: +/// ```text +/// // IEEE 802.11 CCMP's 13 byte nonce and 8 byte tag +/// AES128Key> +/// ``` +/// +/// # Usage Examples +/// +/// ## Fixed frames, through the generic AEAD pair +/// +/// For code written against [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], the `CCM_Packet` +/// key draws the nonce and returns it. Its streaming methods and one-shots take exactly +/// `DATA_LEN` bytes of payload: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CCM_Packet; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// // Up to 64 bytes of AAD, and frames of exactly 2 KiB -- comfortably above an 802.11 frame, +/// // the packet size CCM was designed for. +/// let key = AES128Key::>::from_key_material(&material) +/// .expect("a valid AES-128 key"); +/// +/// let frame = [0x5Au8; 2048]; +/// +/// // The one-shots: a frame. +/// let (nonce, ciphertext, tag) = key.encrypt_detached(&mut rng, b"header", &frame).expect("encryption"); +/// let plaintext = key.decrypt_detached(&nonce, b"header", &ciphertext, &tag).expect("decryption"); +/// assert_eq!(&plaintext[..], &frame[..]); +/// +/// // The streaming methods: exactly one frame, released as it is processed. +/// let (mut enc, nonce) = key.create_encryptor(&mut rng).expect("nonce draw"); +/// enc.do_update_aad(b"header").expect("aad"); +/// let mut sealed = vec![0u8; 2048]; +/// let n = enc.do_encrypt_out(&frame, &mut sealed).expect("the whole frame comes out"); +/// assert_eq!(n, 2048); +/// let (_, _, tag) = enc.do_encrypt_final_detached().expect("the tag"); +/// +/// let mut dec = key.create_decryptor(&nonce); +/// dec.do_update_aad(b"header").expect("aad"); +/// let mut opened = vec![0u8; 2048]; +/// dec.do_decrypt_out(&sealed, &mut opened).expect("released, but not yet authenticated"); +/// dec.do_decrypt_final_detached(&tag).expect("...until the tag verifies"); +/// assert_eq!(opened, frame); +/// ``` +/// +/// ## Any length, a supplied nonce, in the spec's own layout +/// +/// The `CCM` key's `_nonce` one-shots take the caller's nonce and any length, and the inline +/// forms produce and consume the spec's own ciphertext layout, `ciphertext || tag` (Sec 6.1 +/// step 8): +/// +/// ``` +/// use bouncycastle_aes::{AES256Key, CCM_NONCE_LEN, CCM_TAG_LEN}; +/// use bouncycastle_cipher::modes::CCM; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial}; +/// +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::>::from_key_material(&material) +/// .expect("a valid AES-256 key"); +/// +/// // Supplied, not drawn. It is the caller's responsibility that it never repeat under this key. +/// let nonce = [0x01u8; CCM_NONCE_LEN]; +/// +/// // The associated data is authenticated but not encrypted; the message is both. +/// let aad = b"header, sent in the clear"; +/// let message = b"a message of no particular length"; +/// +/// let sealed = key.encrypt_with_aad_nonce(&nonce, aad, message).expect("encryption"); +/// assert_eq!(sealed.len(), message.len() + CCM_TAG_LEN, "the ciphertext plus the tag"); +/// +/// let opened = key.decrypt_with_aad(&nonce, aad, &sealed).expect("decryption"); +/// assert_eq!(&opened[..], message); +/// +/// // Tampering with either the ciphertext or the associated data fails the tag check. +/// let mut tampered = sealed.clone(); +/// tampered[0] ^= 1; +/// assert!(key.decrypt_with_aad(&nonce, aad, &tampered).is_err()); +/// assert!(key.decrypt_with_aad(&nonce, b"other header", &sealed).is_err()); +/// ``` +/// +/// ## In place +/// +/// A message already in a buffer of its own is sealed and opened there, with no second buffer: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CCM; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial}; +/// +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// // Validated and expanded once, for any number of messages. +/// let key = AES128Key::>::from_key_material(&material).expect("a usable key"); +/// let nonce = [0x02u8; 12]; +/// +/// let mut buf = *b"a short packet"; +/// let tag = key.encrypt_detached_in_place_nonce(&nonce, &[], &mut buf).expect("encryption"); +/// key.decrypt_detached_in_place(&nonce, &[], &mut buf, &tag).expect("decryption"); +/// assert_eq!(&buf, b"a short packet"); +/// ``` +/// +/// # Memory Usage +/// +/// The value held between calls: +/// +/// | Type | AES-128 | AES-192 | AES-256 | +/// |---|---|---|---| +/// | [`AES128Key`]`>` and friends: the key schedule | 176 B | 208 B | 240 B | +/// | its workers, encrypting / decrypting | 96 B / 120 B | 96 B / 120 B | 96 B / 120 B | +/// | [`AES_CCM_128`] and friends, encrypting, `AAD_LEN = 64` | 176 B | 176 B | 176 B | +/// | [`AES_CCM_128`] and friends, decrypting, `AAD_LEN = 64` | 200 B | 200 B | 200 B | +/// +/// The difference between the key sizes is the key schedule, which the workers borrow rather +/// than copy; the pair adds the mode's state, the `AAD_LEN`-byte AAD buffer and, on the +/// decrypting side, the held-back tag. Peak stack over a +/// 16 KiB frame with AES-128, measured with massif on x86-64 in release mode by +/// `mem_usage_benches/src/bench_ccm_mem_usage.rs`, including the caller's own 16 KiB arrays: +/// +/// | Path | Peak stack | +/// |---|---| +/// | process start-up alone | 7 680 B | +/// | `AES128Key>` in place, one array | 18 608 B | +/// | `AES_CCM_128` streaming encrypt, two arrays | 36 216 B | +/// | `AES_CCM_128` streaming decrypt, two arrays | 35 424 B | +/// | `AES_CCM_128` one-shot, two arrays | 35 880 B | +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::ccm`] apply. Above all, a nonce +/// the caller supplies must never repeat under one key. #[allow(non_camel_case_types)] pub type AES_CCM_128< + 'k, Dir, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmPacketEncryptor<'k, AES128Internal, AES_BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>, + CcmPacketDecryptor<'k, AES128Internal, 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`]. #[allow(non_camel_case_types)] pub type AES_CCM_192< + 'k, Dir, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmPacketEncryptor<'k, AES192Internal, AES_BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>, + CcmPacketDecryptor<'k, AES192Internal, 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`]. #[allow(non_camel_case_types)] pub type AES_CCM_256< + 'k, Dir, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, > = ::Select< - CcmEncryptor, - CcmDecryptor, + CcmPacketEncryptor<'k, AES256Internal, AES_BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>, + CcmPacketDecryptor<'k, AES256Internal, 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..22166059d 100644 --- a/crypto/aes/src/cfb.rs +++ b/crypto/aes/src/cfb.rs @@ -1,92 +1,5 @@ -//! Type aliases for AES in CFB mode (NIST SP 800-38A Sec 6.3). -//! -//! See [`bouncycastle_cipher::modes::cfb`] for details on the CipherFeedback construction. -//! -//! The aliases here are stream ciphers: the data is a `&mut [u8]` of any length, encrypted or -//! decrypted in place, and the ciphertext is exactly as long as the plaintext. The IV is generated -//! by encryption and returned; there is no API for supplying one. `Dir` is [`Encrypting`] or -//! [`Decrypting`]; the wrong direction is a compile error, not a runtime check. -//! -//! The segment size is the full block, so the constructions used here are equivalent to **CFB128**. -//! SP 800-38A's `s = 8` variant is a different, non-interoperable mode with its own aliases, -//! [`AES_CFB8_128`](crate::AES_CFB8_128) and friends, and `s = 1` is not implemented. -//! -//! # Usage Examples -//! -//! ## One-shot API -//! -//! 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_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"); -//! -//! // An arbitrary plaintext to encrypt. -//! // Any length: a stream cipher does not need a whole number of blocks. -//! let plaintext = [0x5Au8; 47]; -//! -//! // Encryption works in place. The IV is generated for you and returned; there is no API for -//! // supplying one. -//! let mut data = plaintext; -//! let (_, iv) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); -//! -//! AESDec::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); -//! assert_eq!(data, plaintext); -//! ``` -//! -//! ## Streaming API -//! -//! For data that arrives in pieces, the following APIs can be used. A stream cipher processes -//! every byte it is given, so nothing is held back between calls and the pieces can be of any -//! length: -//! -//! ``` -//! use bouncycastle_aes::AES_CFB_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{ -//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, -//! SymmetricCipherEncryptor, -//! }; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! 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"); -//! -//! // An arbitrary plaintext to encrypt -//! let plaintext = [0x5Au8; 50]; -//! -//! // Encrypt in 7-byte pieces, each in place. -//! let (mut encryptor, iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); -//! let mut ciphertext = plaintext; -//! for piece in ciphertext.chunks_mut(7) { -//! encryptor.do_encrypt_inplace(piece).expect("encryption"); -//! } -//! -//! // Decrypt in 19-byte pieces: the boundaries need not match the encryptor's. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &iv).expect("decrypt init"); -//! let mut recovered = ciphertext; -//! for piece in recovered.chunks_mut(19) { -//! decryptor.do_decrypt_inplace(piece).expect("decryption"); -//! } -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::cfb`] apply. +//! Type aliases for AES in CFB mode (NIST SP 800-38A Sec 6.3). The docs are on [`AES_CFB_128`], +//! which the crate root re-exports. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; @@ -96,17 +9,98 @@ use bouncycastle_cipher::modes::Cfb; #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; #[allow(unused_imports)] -use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; // end of imports needed for docs /// AES-128 in CFB128 mode. +/// +/// See [`bouncycastle_cipher::modes::cfb`] for details on the CipherFeedback construction. +/// +/// The aliases here are stream ciphers: the data is a `&mut [u8]` of any length, encrypted or +/// decrypted in place, and the ciphertext is exactly as long as the plaintext. They come from a +/// key, [`AES128Key`](crate::AES128Key) and friends, through the [`SymmetricEncryptionKey`] +/// / [`SymmetricDecryptionKey`] pair. The IV is drawn from the caller's RNG by encryption and +/// returned; there is no API for supplying one. `Dir` is [`Encrypting`] or [`Decrypting`]; the +/// wrong direction is a compile error, not a runtime check. +/// +/// The segment size is the full block, so the constructions used here are equivalent to **CFB128**. +/// SP 800-38A's `s = 8` variant is a different, non-interoperable mode with its own aliases, +/// [`AES_CFB8_128`](crate::AES_CFB8_128) and friends, and `s = 1` is not implemented. +/// +/// # Usage Examples +/// +/// ## One-shot API +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::CFB; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // Any length: a stream cipher does not need a whole number of blocks. +/// let plaintext = [0x5Au8; 47]; +/// +/// // Encryption works in place. The IV is drawn from `rng` and returned. +/// let mut data = plaintext; +/// let (_, iv) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); +/// +/// key.decrypt_inplace(&iv, &mut data).expect("decryption"); +/// assert_eq!(data, plaintext); +/// ``` +/// +/// ## Streaming API +/// +/// For data that arrives in pieces, the workers' streaming methods can be used. A stream cipher +/// processes every byte it is given, so nothing is held back between calls and the pieces can be +/// of any length: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CFB; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// +/// let plaintext = [0x5Au8; 50]; +/// +/// // Encrypt in 7-byte pieces, each in place. +/// let (mut encryptor, iv) = key.create_encryptor(&mut rng).expect("IV draw"); +/// let mut ciphertext = plaintext; +/// for piece in ciphertext.chunks_mut(7) { +/// encryptor.do_encrypt_inplace(piece).expect("encryption"); +/// } +/// +/// // Decrypt in 19-byte pieces: the boundaries need not match the encryptor's. +/// let mut decryptor = key.create_decryptor(&iv); +/// let mut recovered = ciphertext; +/// for piece in recovered.chunks_mut(19) { +/// decryptor.do_decrypt_inplace(piece).expect("decryption"); +/// } +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::cfb`] apply. #[allow(non_camel_case_types)] -pub type AES_CFB_128 = Cfb; +pub type AES_CFB_128<'k, Dir> = Cfb<'k, AES128Internal, Dir, AES_BLOCK_LEN>; /// AES-192 in CFB128 mode. #[allow(non_camel_case_types)] -pub type AES_CFB_192 = Cfb; +pub type AES_CFB_192<'k, Dir> = Cfb<'k, AES192Internal, Dir, AES_BLOCK_LEN>; /// 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<'k, Dir> = Cfb<'k, AES256Internal, Dir, AES_BLOCK_LEN>; diff --git a/crypto/aes/src/cfb8.rs b/crypto/aes/src/cfb8.rs index c4ec07579..2d98504ac 100644 --- a/crypto/aes/src/cfb8.rs +++ b/crypto/aes/src/cfb8.rs @@ -1,115 +1,5 @@ -//! Type aliases for AES in CFB8 mode (NIST SP 800-38A Sec 6.3, `s = 8`). -//! -//! See [`bouncycastle_cipher::modes::cfb8`] for details on the CipherFeedback construction with an 8-bit segment. -//! -//! The aliases here are stream ciphers: the data is a `&mut [u8]` of any -//! length, encrypted or decrypted in-place since the ciphertext is exactly as long as the plaintext. -//! The IV is generated by encryption and returned; there is no API for supplying one. `Dir` is -//! [`Encrypting`] or [`Decrypting`]; the wrong direction is a compile error, not a runtime check. -//! -//! CFB8 is a **different, non-interoperable mode** from CFB, not a variant of it: their -//! ciphertexts differ from the second byte, and it costs a full AES call per byte, sixteen times -//! the work of [`AES_CFB_128`](crate::AES_CFB_128). See [`bouncycastle_cipher::modes::cfb8`] for when -//! that is the right trade. -//! -//! # Usage Examples -//! -//! ## One-shot API -//! -//! 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_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"); -//! -//! // An arbitrary plaintext to encrypt. -//! // 5 bytes: CFB8's segment is one byte, so any length at all is fine. -//! let plaintext = *b"hello"; -//! -//! // Encryption works in place. The IV is generated for you and returned; there is no API for -//! // supplying one. -//! let mut data = plaintext; -//! let (_, iv) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); -//! -//! AESDec::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); -//! assert_eq!(data, plaintext); -//! ``` -//! -//! ## Streaming API -//! -//! For data that arrives in pieces, the following APIs can be used. A stream cipher processes -//! every byte it is given, so nothing is held back between calls and the pieces can be of any -//! length: -//! -//! ``` -//! use bouncycastle_aes::AES_CFB8_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{ -//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, -//! SymmetricCipherEncryptor, -//! }; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! 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"); -//! -//! // An arbitrary plaintext to encrypt -//! let plaintext = [0x5Au8; 50]; -//! -//! // Encrypt in 3-byte pieces, each in place. -//! let (mut encryptor, iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); -//! let mut ciphertext = plaintext; -//! for piece in ciphertext.chunks_mut(3) { -//! encryptor.do_encrypt_inplace(piece).expect("encryption"); -//! } -//! -//! // Decrypt in 20-byte pieces: the boundaries need not match the encryptor's. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &iv).expect("decrypt init"); -//! let mut recovered = ciphertext; -//! for piece in recovered.chunks_mut(20) { -//! decryptor.do_decrypt_inplace(piece).expect("decryption"); -//! } -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! ## 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. -//! -//! ``` -//! use bouncycastle_aes::{AES_CFB8_128, AES_CFB_128}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! 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. -//! AES_CFB_128::::decrypt_inplace(&key, &iv, &mut data).unwrap(); -//! assert_ne!(data, plaintext); -//! ``` -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::cfb8`] apply. +//! Type aliases for AES in CFB8 mode (NIST SP 800-38A Sec 6.3, `s = 8`). The docs are on +//! [`AES_CFB8_128`], which the crate root re-exports. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; @@ -119,17 +9,126 @@ use bouncycastle_cipher::modes::Cfb8; #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; #[allow(unused_imports)] -use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; // end of imports needed for docs /// AES-128 in CFB8 mode. +/// +/// See [`bouncycastle_cipher::modes::cfb8`] for details on the CipherFeedback construction with an 8-bit segment. +/// +/// The aliases here are stream ciphers: the data is a `&mut [u8]` of any length, encrypted or +/// decrypted in-place since the ciphertext is exactly as long as the plaintext. They come from a +/// key, [`AES128Key`](crate::AES128Key) and friends, through the [`SymmetricEncryptionKey`] +/// / [`SymmetricDecryptionKey`] pair. The IV is drawn from the caller's RNG by encryption and +/// returned; there is no API for supplying one. `Dir` is [`Encrypting`] or [`Decrypting`]; the +/// wrong direction is a compile error, not a runtime check. +/// +/// CFB8 is a **different, non-interoperable mode** from CFB, not a variant of it: their +/// ciphertexts differ from the second byte, and it costs a full AES call per byte, sixteen times +/// the work of [`AES_CFB_128`](crate::AES_CFB_128). See [`bouncycastle_cipher::modes::cfb8`] for when +/// that is the right trade. +/// +/// # Usage Examples +/// +/// ## One-shot API +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::CFB8; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // 5 bytes: CFB8's segment is one byte, so any length at all is fine. +/// let plaintext = *b"hello"; +/// +/// // Encryption works in place. The IV is drawn from `rng` and returned. +/// let mut data = plaintext; +/// let (_, iv) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); +/// +/// key.decrypt_inplace(&iv, &mut data).expect("decryption"); +/// assert_eq!(data, plaintext); +/// ``` +/// +/// ## Streaming API +/// +/// For data that arrives in pieces, the workers' streaming methods can be used. A stream cipher +/// processes every byte it is given, so nothing is held back between calls and the pieces can be +/// of any length: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CFB8; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// +/// let plaintext = [0x5Au8; 50]; +/// +/// // Encrypt in 3-byte pieces, each in place. +/// let (mut encryptor, iv) = key.create_encryptor(&mut rng).expect("IV draw"); +/// let mut ciphertext = plaintext; +/// for piece in ciphertext.chunks_mut(3) { +/// encryptor.do_encrypt_inplace(piece).expect("encryption"); +/// } +/// +/// // Decrypt in 20-byte pieces: the boundaries need not match the encryptor's. +/// let mut decryptor = key.create_decryptor(&iv); +/// let mut recovered = ciphertext; +/// for piece in recovered.chunks_mut(20) { +/// decryptor.do_decrypt_inplace(piece).expect("decryption"); +/// } +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// ## 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. +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::{CFB, CFB8}; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let cfb8_key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// let cfb_key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// let plaintext = *b"hello"; +/// +/// let mut data = plaintext; +/// let (_, iv) = cfb8_key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); +/// +/// // Decrypting CFB8 output as CFB128 does not recover the plaintext. +/// cfb_key.decrypt_inplace(&iv, &mut data).expect("decryption"); +/// assert_ne!(data, plaintext); +/// ``` +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::cfb8`] apply. #[allow(non_camel_case_types)] -pub type AES_CFB8_128 = Cfb8; +pub type AES_CFB8_128<'k, Dir> = Cfb8<'k, AES128Internal, Dir, AES_BLOCK_LEN>; /// AES-192 in CFB8 mode. #[allow(non_camel_case_types)] -pub type AES_CFB8_192 = Cfb8; +pub type AES_CFB8_192<'k, Dir> = Cfb8<'k, AES192Internal, Dir, AES_BLOCK_LEN>; /// 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<'k, Dir> = Cfb8<'k, AES256Internal, Dir, AES_BLOCK_LEN>; diff --git a/crypto/aes/src/cmac.rs b/crypto/aes/src/cmac.rs index 62f4493cf..70450bcf4 100644 --- a/crypto/aes/src/cmac.rs +++ b/crypto/aes/src/cmac.rs @@ -1,129 +1,60 @@ //! Type aliases for AES in CMAC (NIST SP 800-38B), and the `CMAC` mode impls that make -//! [`AES128Key`] and friends MAC keys. -//! -//! See [`bouncycastle_cipher::modes::cmac`] for the construction, the key requirements, -//! truncation and security considerations. [`AES_CMAC_128`] and friends pin the permutation and -//! its key and block lengths; the [`MAC`] trait on them derives the subkeys per message from the -//! key material. A mode-bound key, [`AES128Key`], derives them once and hands out the same -//! worker through [`MacGenerationKey`] and [`MacVerificationKey`]. -//! -//! # Usage Examples -//! -//! One message from the material: -//! -//! ``` -//! use bouncycastle_aes::AES_CMAC_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); -//! let tag = AES_CMAC_128::new(&key).unwrap().mac(b"a message"); -//! assert!(AES_CMAC_128::new(&key).unwrap().verify(b"a message", &tag)); -//! ``` -//! -//! Many messages from a key object, which takes the same [`KeyType::MACKey`] material: -//! -//! ``` -//! use bouncycastle_aes::AES256Key; -//! use bouncycastle_cipher::modes::CMAC; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; -//! use bouncycastle_core::traits::MAC; -//! -//! let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::MACKey).unwrap(); -//! let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 MAC key"); -//! -//! let mut generator = key.create_generator(); -//! generator.do_update(b"a message that arrives "); -//! generator.do_update(b"in pieces"); -//! let mut tag = [0u8; 16]; -//! assert_eq!(generator.do_final_out(&mut tag).unwrap(), 16); -//! -//! assert!(key.verify(b"a message that arrives in pieces", &tag)); -//! assert_eq!(key.mac(b"a message that arrives in pieces"), tag); -//! ``` -//! -//! # Memory Usage -//! -//! A key is the expanded schedule plus the two 16-byte subkeys: 208, 240 and 272 bytes for -//! AES-128, -192 and -256. A worker is a clone of that plus the running `C_i`, the pending block -//! and its length: 248, 280 and 312 bytes. The `MAC` route builds the same worker per message, -//! deriving the subkeys each time. -//! -//! [`AES128Key`]: crate::AES128Key -//! [`MAC`]: bouncycastle_core::traits::MAC -//! [`MacGenerationKey`]: bouncycastle_core::symmetric_key::MacGenerationKey -//! [`MacVerificationKey`]: bouncycastle_core::symmetric_key::MacVerificationKey -//! [`KeyType::MACKey`]: bouncycastle_core::key_material::KeyType::MACKey +//! [`AES128Key`] and friends MAC keys. The docs are on [`AES_CMAC_128`], which the crate root +//! re-exports. use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::{CMAC, Cmac, CmacKeyState}; -use bouncycastle_core::hazmat::{MacMode, Mode}; -use bouncycastle_core::key_material::KeyType; +use bouncycastle_cipher::modes::Cmac; /// AES-128 in CMAC. +/// +/// See [`bouncycastle_cipher::modes::cmac`] for the construction, the key requirements, +/// truncation and security considerations. [`AES_CMAC_128`] and friends name the worker, which +/// implements [`MAC`]; it comes from a key, [`AES128Key`], which takes +/// [`KeyType::MACKey`] material, derives the subkeys once and hands out the worker through +/// [`MacGenerationKey`] and [`MacVerificationKey`]. +/// +/// # Usage Examples +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::CMAC; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; +/// use bouncycastle_core::traits::MAC; +/// +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::MACKey).unwrap(); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 MAC key"); +/// +/// // A message that arrives in pieces, through the worker. +/// let mut generator = key.create_generator(); +/// generator.do_update(b"a message that arrives "); +/// generator.do_update(b"in pieces"); +/// let mut tag = [0u8; 16]; +/// assert_eq!(generator.do_final_out(&mut tag).unwrap(), 16); +/// +/// // One-shots on the key. +/// assert!(key.verify(b"a message that arrives in pieces", &tag)); +/// assert_eq!(key.mac(b"a message that arrives in pieces"), tag); +/// ``` +/// +/// # Memory Usage +/// +/// A key is the expanded schedule plus the two 16-byte subkeys: 208, 240 and 272 bytes for +/// AES-128, -192 and -256. A worker borrows that and adds the running `C_i`, the pending block +/// and its length: 48 bytes at every key length. +/// +/// [`AES128Key`]: crate::AES128Key +/// [`MAC`]: bouncycastle_core::traits::MAC +/// [`MacGenerationKey`]: bouncycastle_core::symmetric_key::MacGenerationKey +/// [`MacVerificationKey`]: bouncycastle_core::symmetric_key::MacVerificationKey +/// [`KeyType::MACKey`]: bouncycastle_core::key_material::KeyType::MACKey #[allow(non_camel_case_types)] -pub type AES_CMAC_128 = Cmac; +pub type AES_CMAC_128<'k> = Cmac<'k, AES128Internal, 16>; /// AES-192 in CMAC. See [`AES_CMAC_128`]. #[allow(non_camel_case_types)] -pub type AES_CMAC_192 = Cmac; +pub type AES_CMAC_192<'k> = Cmac<'k, AES192Internal, 16>; /// AES-256 in CMAC. See [`AES_CMAC_128`]. #[allow(non_camel_case_types)] -pub type AES_CMAC_256 = Cmac; - -// Per key length for the reason the GCM impls are (see `gcm.rs`): the subkey derivation needs the -// permutation's `ElectronicCodeBook` bound, and `KEY_LEN` cannot be tied to -// `Mode

` from a generic impl. - -impl Mode for CMAC { - type KeyState = CmacKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES128Internal) -> Self::KeyState { - CmacKeyState::new::<16>(perm) - } -} - -impl MacMode for CMAC { - type Worker = AES_CMAC_128; - - fn mac_init(state: &Self::KeyState) -> Self::Worker { - Cmac::from_key_state(state) - } -} - -impl Mode for CMAC { - type KeyState = CmacKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES192Internal) -> Self::KeyState { - CmacKeyState::new::<24>(perm) - } -} - -impl MacMode for CMAC { - type Worker = AES_CMAC_192; - - fn mac_init(state: &Self::KeyState) -> Self::Worker { - Cmac::from_key_state(state) - } -} - -impl Mode for CMAC { - type KeyState = CmacKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES256Internal) -> Self::KeyState { - CmacKeyState::new::<32>(perm) - } -} - -impl MacMode for CMAC { - type Worker = AES_CMAC_256; - - fn mac_init(state: &Self::KeyState) -> Self::Worker { - Cmac::from_key_state(state) - } -} +pub type AES_CMAC_256<'k> = Cmac<'k, AES256Internal, 16>; diff --git a/crypto/aes/src/ctr.rs b/crypto/aes/src/ctr.rs index c2456c47e..6786e973e 100644 --- a/crypto/aes/src/ctr.rs +++ b/crypto/aes/src/ctr.rs @@ -1,97 +1,5 @@ -//! Type aliases for AES in CTR mode (NIST SP 800-38A Sec 6.5). -//! -//! See [`bouncycastle_cipher::modes::ctr`] for details on the Counter construction. -//! -//! The aliases here are stream ciphers: the data is a `&mut [u8]` of any length, encrypted or -//! decrypted in place since ciphertext is exactly as long as the plaintext. The nonce is generated -//! by encryption and returned; there is no API for supplying one. `Dir` is [`Encrypting`] or [`Decrypting`]; -//! the wrong direction is a compile error, not a runtime check. -//! -//! # Nonce and counter length -//! -//! **The nonce length is 12 bytes, the counter is 4 bytes.** -//! -//! These aliases fix a **12-byte nonce** ([`CTR_NONCE_LEN`]), leaving the remainder of each block to -//! be a 4-byte counter. That allows 2^32 blocks -- 64 GiB -- in one message, and past it the -//! mode errors rather than repeating the keystream. A shorter message limit in exchange for more nonce -//! bits is available by using `Ctr` directly with a 13, 14 or 15-byte nonce. -//! -//! # Usage Examples -//! -//! ## One-shot API -//! -//! 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_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"); -//! -//! // An arbitrary plaintext to encrypt. -//! // Any length: a stream cipher does not need a whole number of blocks. -//! let plaintext = [0x5Au8; 47]; -//! -//! // Encryption works in place. The nonce is generated for you and returned; there is no API for -//! // supplying one. -//! let mut data = plaintext; -//! let (_, nonce) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); -//! -//! AESDec::decrypt_inplace(&key, &nonce, &mut data).expect("decryption"); -//! assert_eq!(data, plaintext); -//! ``` -//! -//! ## Streaming API -//! -//! For data that arrives in pieces, the following APIs can be used. A stream cipher processes -//! every byte it is given, so nothing is held back between calls and the pieces can be of any -//! length: -//! -//! ``` -//! use bouncycastle_aes::AES_CTR_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{ -//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, -//! SymmetricCipherEncryptor, -//! }; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! 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"); -//! -//! // An arbitrary plaintext to encrypt -//! let plaintext = [0x5Au8; 50]; -//! -//! // Encrypt in 7-byte pieces, each in place. -//! let (mut encryptor, nonce) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); -//! let mut ciphertext = plaintext; -//! for piece in ciphertext.chunks_mut(7) { -//! encryptor.do_encrypt_inplace(piece).expect("encryption"); -//! } -//! -//! // Decrypt in 19-byte pieces: the boundaries need not match the encryptor's. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &nonce).expect("decrypt init"); -//! let mut recovered = ciphertext; -//! for piece in recovered.chunks_mut(19) { -//! decryptor.do_decrypt_inplace(piece).expect("decryption"); -//! } -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::ctr`] apply. +//! Type aliases for AES in CTR mode (NIST SP 800-38A Sec 6.5). The docs are on [`AES_CTR_128`], +//! which the crate root re-exports. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; @@ -101,20 +9,107 @@ use bouncycastle_cipher::modes::Ctr; #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; #[allow(unused_imports)] -use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; // end of imports needed for docs /// The nonce length these aliases use, leaving a 4-byte counter. pub const CTR_NONCE_LEN: usize = 12; /// AES-128 in CTR mode with a 12-byte nonce. +/// +/// See [`bouncycastle_cipher::modes::ctr`] for details on the Counter construction. +/// +/// The aliases here are stream ciphers: the data is a `&mut [u8]` of any length, encrypted or +/// decrypted in place since ciphertext is exactly as long as the plaintext. They come from a key, +/// [`AES128Key`](crate::AES128Key) and friends, through the [`SymmetricEncryptionKey`] / +/// [`SymmetricDecryptionKey`] pair. The nonce is drawn from the caller's RNG by encryption and +/// returned; there is no API for supplying one. `Dir` is [`Encrypting`] or [`Decrypting`]; the +/// wrong direction is a compile error, not a runtime check. +/// +/// # Nonce and counter length +/// +/// **The nonce length is 12 bytes, the counter is 4 bytes.** +/// +/// These aliases, and the key's mode `CTR` with its defaults, fix a **12-byte nonce** +/// ([`CTR_NONCE_LEN`]), leaving the remainder of each block to be a 4-byte counter. That allows +/// 2^32 blocks -- 64 GiB -- in one message, and past it the mode errors rather than repeating +/// the keystream. A shorter message limit in exchange for more nonce bits is available by naming +/// `CTR<13>`, `CTR<14>` or `CTR<15>` as the key's mode. +/// +/// # Usage Examples +/// +/// ## One-shot API +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::CTR; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // Any length: a stream cipher does not need a whole number of blocks. +/// let plaintext = [0x5Au8; 47]; +/// +/// // Encryption works in place. The nonce is drawn from `rng` and returned. +/// let mut data = plaintext; +/// let (_, nonce) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); +/// +/// key.decrypt_inplace(&nonce, &mut data).expect("decryption"); +/// assert_eq!(data, plaintext); +/// ``` +/// +/// ## Streaming API +/// +/// For data that arrives in pieces, the workers' streaming methods can be used. A stream cipher +/// processes every byte it is given, so nothing is held back between calls and the pieces can be +/// of any length: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::CTR; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +/// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// +/// let plaintext = [0x5Au8; 50]; +/// +/// // Encrypt in 7-byte pieces, each in place. +/// let (mut encryptor, nonce) = key.create_encryptor(&mut rng).expect("nonce draw"); +/// let mut ciphertext = plaintext; +/// for piece in ciphertext.chunks_mut(7) { +/// encryptor.do_encrypt_inplace(piece).expect("encryption"); +/// } +/// +/// // Decrypt in 19-byte pieces: the boundaries need not match the encryptor's. +/// let mut decryptor = key.create_decryptor(&nonce); +/// let mut recovered = ciphertext; +/// for piece in recovered.chunks_mut(19) { +/// decryptor.do_decrypt_inplace(piece).expect("decryption"); +/// } +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::ctr`] apply. #[allow(non_camel_case_types)] -pub type AES_CTR_128

= Ctr; +pub type AES_CTR_128<'k, Dir> = Ctr<'k, AES128Internal, Dir, AES_BLOCK_LEN, CTR_NONCE_LEN>; /// 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<'k, Dir> = Ctr<'k, AES192Internal, Dir, AES_BLOCK_LEN, CTR_NONCE_LEN>; /// 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<'k, Dir> = Ctr<'k, AES256Internal, Dir, AES_BLOCK_LEN, CTR_NONCE_LEN>; diff --git a/crypto/aes/src/gcm.rs b/crypto/aes/src/gcm.rs index c81378318..2df3dfc34 100644 --- a/crypto/aes/src/gcm.rs +++ b/crypto/aes/src/gcm.rs @@ -1,282 +1,168 @@ -//! Type aliases for AES in GCM (NIST SP 800-38D). -//! -//! See [`bouncycastle_cipher::modes::gcm`] for details on the abstract Galois/Counter Mode construction. -//! -//! The aliases here are authenticated ciphers: encryption produces a tag as well as a ciphertext, -//! and decryption either returns the plaintext or fails the tag check. Both directions implement -//! [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], which keep the tag detached, and -//! [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`], which carry it inline as -//! `ciphertext || tag`. The nonce is generated by encryption and returned; there is no API for -//! supplying one. `Dir` is [`Encrypting`] or [`Decrypting`]; the wrong direction is a compile -//! error, not a runtime check. -//! -//! The tag is fixed at 128 bits, the maximum SP 800-38D Sec 5.2.1.2 allows. For a shorter tag -//! (96, 104, 112 or 120 bits), name [`Gcm`] directly with the desired `TAG_LEN`. The nonce is -//! always [`GCM_NONCE_LEN`] (12 bytes / 96 bits): `Gcm` has no nonce-length parameter at all, -//! unlike `Ctr`'s aliases, because SP 800-38D's `len(IV) != 96` branch (deriving `J0` from a GHASH -//! of the IV) is not implemented. -//! -//! # Usage Examples -//! -//! ## One-shot API -//! -//! Basic usage can be obtained via the [`AEADCipherEncryptor`] and [`AEADCipherDecryptor`] API, -//! 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_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"); -//! -//! // The associated data is authenticated but not encrypted; the message is both. -//! let aad = b"header, sent in the clear"; -//! let message = b"a message of no particular length"; -//! -//! // The nonce is generated for you and returned; there is no API for supplying one. -//! let (nonce, ciphertext, tag) = AESEnc::encrypt_detached(&key, aad, message).expect("encryption"); -//! assert_eq!(ciphertext.len(), message.len(), "GCM never expands the payload"); -//! -//! let recovered = AESDec::decrypt_detached(&key, &nonce, aad, &ciphertext, &tag).expect("decryption"); -//! assert_eq!(recovered, message); -//! -//! // Tampering with the ciphertext, the tag or the associated data fails the tag check. -//! let mut tampered = ciphertext.clone(); -//! tampered[0] ^= 1; -//! assert!(AESDec::decrypt_detached(&key, &nonce, aad, &tampered, &tag).is_err()); -//! assert!(AESDec::decrypt_detached(&key, &nonce, b"other header", &ciphertext, &tag).is_err()); -//! ``` -//! -//! ## Inline `ciphertext || tag` -//! -//! Through the [`SymmetricCipherEncryptor`] and [`SymmetricCipherDecryptor`] API the tag is -//! 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_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 message = b"a message of no particular length at all"; -//! -//! let mut ciphertext = vec![0u8; AESEnc::encrypt_out_len(message.len())]; -//! let (nonce, written) = AESEnc::encrypt_out(&key, message, &mut ciphertext).expect("encryption"); -//! assert_eq!(written, message.len() + 16, "the ciphertext plus the tag"); -//! -//! let mut plaintext = vec![0u8; AESDec::decrypt_out_len(ciphertext.len())]; -//! let n = AESDec::decrypt_out(&key, &nonce, &ciphertext, &mut plaintext).expect("decryption"); -//! assert_eq!(&plaintext[..n], message); -//! ``` -//! -//! ## Streaming API -//! -//! For data that arrives in pieces, the following APIs can be used. All associated data must be -//! given via `do_update_aad` before the first piece of data, and the tag comes from -//! `do_encrypt_final`: -//! -//! ``` -//! use bouncycastle_aes::AES_GCM_128; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{ -//! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, -//! }; -//! 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 aad = b"header"; -//! let plaintext = [0x5Au8; 50]; -//! -//! // Encrypt in 7-byte pieces. A piece is encrypted as soon as it is given, so each call writes -//! // exactly as many bytes as it was handed. -//! let (mut encryptor, nonce) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); -//! encryptor.do_update_aad(aad).expect("aad"); -//! let mut ciphertext = Vec::new(); -//! for piece in plaintext.chunks(7) { -//! let mut out = [0u8; 7]; -//! let bytes_written = encryptor.do_encrypt_out(piece, &mut out).expect("encryption"); -//! ciphertext.extend_from_slice(&out[..bytes_written]); -//! } -//! // The tag is computed over everything, so it is the last thing out. -//! let (tag, tag_len) = encryptor.do_encrypt_final().expect("the tag"); -//! ciphertext.extend_from_slice(&tag[..tag_len]); -//! assert_eq!(ciphertext.len(), plaintext.len() + 16); -//! -//! // Decrypt in 19-byte pieces. The decryptor holds back the last 16 bytes it has seen, since -//! // those may be the tag, so a call can write fewer bytes than it was handed; `do_decrypt_final` -//! // checks the tag and releases whatever is still held back. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &nonce).expect("decrypt init"); -//! decryptor.do_update_aad(aad).expect("aad"); -//! let mut recovered = Vec::new(); -//! for piece in ciphertext.chunks(19) { -//! let mut out = [0u8; 19]; -//! let bytes_written = decryptor.do_decrypt_out(piece, &mut out).expect("decryption"); -//! recovered.extend_from_slice(&out[..bytes_written]); -//! } -//! let (last, last_len) = decryptor.do_decrypt_final().expect("a valid tag"); -//! recovered.extend_from_slice(&last[..last_len]); -//! assert_eq!(recovered, plaintext); -//! ``` -//! -//! # 🚨 Security Considerations 🚨 -//! -//! All security considerations from [`bouncycastle_cipher::modes::gcm`] apply. +//! Type aliases for AES in GCM (NIST SP 800-38D). The docs are on [`AES_GCM_128`], which the crate +//! root re-exports. use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::{GCM, GCM_NONCE_LEN, Gcm, GcmKeyState}; +use bouncycastle_cipher::modes::Gcm; + +// Imports needed for docs +#[allow(unused_imports)] +use bouncycastle_cipher::modes::GCM_NONCE_LEN; +#[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::{AeadDecryptMode, AeadEncryptMode, Mode}; -use bouncycastle_core::key_material::KeyType; -use bouncycastle_core::traits::RNG; // Imports needed for docs #[allow(unused_imports)] +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey}; +#[allow(unused_imports)] use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; // end of imports needed for docs /// AES-128 in GCM with a 128-bit tag. +/// +/// See [`bouncycastle_cipher::modes::gcm`] for details on the abstract Galois/Counter Mode construction. +/// +/// The aliases here are authenticated ciphers: encryption produces a tag as well as a ciphertext, +/// and decryption either returns the plaintext or fails the tag check. Both directions implement +/// [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], which keep the tag detached, and +/// [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`], which carry it inline as +/// `ciphertext || tag`. They come from a key, [`AES128Key`](crate::AES128Key) and friends, +/// through the [`AeadEncryptionKey`] / [`AeadDecryptionKey`] pair, which draws the nonce from +/// the caller's RNG and returns it, or takes the caller's through its `_nonce` forms. `Dir` is +/// [`Encrypting`] or [`Decrypting`]; the wrong direction is a compile error, not a runtime check. +/// +/// The tag is fixed at 128 bits, the maximum SP 800-38D Sec 5.2.1.2 allows. For a shorter tag +/// (96, 104, 112 or 120 bits), name the key's mode as `GCM<12>` and so on, or [`Gcm`] directly +/// with the desired `TAG_LEN`. The nonce is always [`GCM_NONCE_LEN`] (12 bytes / 96 bits): `Gcm` +/// has no nonce-length parameter at all, unlike `Ctr`'s aliases, because SP 800-38D's +/// `len(IV) != 96` branch (deriving `J0` from a GHASH of the IV) is not implemented. +/// +/// # Usage Examples +/// +/// ## One-shot API +/// +/// The detached one-shots return the tag separately from the ciphertext: +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::GCM; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // The associated data is authenticated but not encrypted; the message is both. +/// let aad = b"header, sent in the clear"; +/// let message = b"a message of no particular length"; +/// +/// // The nonce is drawn from `rng` and returned. +/// let (nonce, ciphertext, tag) = key.encrypt_detached(&mut rng, aad, message).expect("encryption"); +/// assert_eq!(ciphertext.len(), message.len(), "GCM never expands the payload"); +/// +/// let recovered = key.decrypt_detached(&nonce, aad, &ciphertext, &tag).expect("decryption"); +/// assert_eq!(recovered, message); +/// +/// // Tampering with the ciphertext, the tag or the associated data fails the tag check. +/// let mut tampered = ciphertext.clone(); +/// tampered[0] ^= 1; +/// assert!(key.decrypt_detached(&nonce, aad, &tampered, &tag).is_err()); +/// assert!(key.decrypt_detached(&nonce, b"other header", &ciphertext, &tag).is_err()); +/// ``` +/// +/// ## Inline `ciphertext || tag` +/// +/// Through the `_with_aad` one-shots the tag is appended to the ciphertext, so the output is 16 +/// bytes longer than the input; an empty AAD is the no-AAD case: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::GCM; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// let message = b"a message of no particular length at all"; +/// +/// let (nonce, ciphertext) = key.encrypt_with_aad(&mut rng, &[], message).expect("encryption"); +/// assert_eq!(ciphertext.len(), message.len() + 16, "the ciphertext plus the tag"); +/// +/// let plaintext = key.decrypt_with_aad(&nonce, &[], &ciphertext).expect("decryption"); +/// assert_eq!(plaintext, message); +/// ``` +/// +/// ## Streaming API +/// +/// For data that arrives in pieces, the workers' streaming methods can be used. All associated +/// data must be given via `do_update_aad` before the first piece of data, and the tag comes from +/// `do_encrypt_final`: +/// +/// ``` +/// use bouncycastle_aes::AES128Key; +/// use bouncycastle_cipher::modes::GCM; +/// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_core::traits::{ +/// AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +/// }; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AES128Key::::from_key_material(&material).expect("a valid AES-128 key"); +/// let aad = b"header"; +/// let plaintext = [0x5Au8; 50]; +/// +/// // Encrypt in 7-byte pieces. A piece is encrypted as soon as it is given, so each call writes +/// // exactly as many bytes as it was handed. +/// let (mut encryptor, nonce) = key.create_encryptor(&mut rng).expect("nonce draw"); +/// encryptor.do_update_aad(aad).expect("aad"); +/// let mut ciphertext = Vec::new(); +/// for piece in plaintext.chunks(7) { +/// let mut out = [0u8; 7]; +/// let bytes_written = encryptor.do_encrypt_out(piece, &mut out).expect("encryption"); +/// ciphertext.extend_from_slice(&out[..bytes_written]); +/// } +/// // The tag is computed over everything, so it is the last thing out. +/// let (tag, tag_len) = encryptor.do_encrypt_final().expect("the tag"); +/// ciphertext.extend_from_slice(&tag[..tag_len]); +/// assert_eq!(ciphertext.len(), plaintext.len() + 16); +/// +/// // Decrypt in 19-byte pieces. The decryptor holds back the last 16 bytes it has seen, since +/// // those may be the tag, so a call can write fewer bytes than it was handed; `do_decrypt_final` +/// // checks the tag and releases whatever is still held back. +/// let mut decryptor = key.create_decryptor(&nonce); +/// decryptor.do_update_aad(aad).expect("aad"); +/// let mut recovered = Vec::new(); +/// for piece in ciphertext.chunks(19) { +/// let mut out = [0u8; 19]; +/// let bytes_written = decryptor.do_decrypt_out(piece, &mut out).expect("decryption"); +/// recovered.extend_from_slice(&out[..bytes_written]); +/// } +/// let (last, last_len) = decryptor.do_decrypt_final().expect("a valid tag"); +/// recovered.extend_from_slice(&last[..last_len]); +/// assert_eq!(recovered, plaintext); +/// ``` +/// +/// # 🚨 Security Considerations 🚨 +/// +/// All security considerations from [`bouncycastle_cipher::modes::gcm`] apply. #[allow(non_camel_case_types)] -pub type AES_GCM_128 = Gcm; +pub type AES_GCM_128<'k, Dir> = Gcm<'k, AES128Internal, Dir, 16>; /// 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<'k, Dir> = Gcm<'k, AES192Internal, Dir, 16>; /// AES-256 in GCM with a 128-bit tag. See [`AES_GCM_128`]. #[allow(non_camel_case_types)] -pub type AES_GCM_256 = Gcm; - -// The `GCM` mode impls for the three AES permutations, which is what makes `AES128Key` and -// friends AEAD keys. Written per key length rather than once over `AESInternal

`: the generic -// form needs `Gcm, Dir, { P::KEY_LEN }, TAG_LEN>`, a const expression stable Rust -// rejects, and a generic impl in `cipher` over `P: ElectronicCodeBook` leaves -// `KEY_LEN` unconstrained on `Mode

`. Both go once `KEY_LEN` leaves the cipher traits. - -impl Mode for GCM { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; - - fn key_state(perm: AES128Internal) -> Self::KeyState { - GcmKeyState::new::<16>(perm) - } -} - -impl AeadEncryptMode - for GCM -{ - type Encryptor = Gcm; - - fn encrypt_init( - state: &Self::KeyState, - rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { - Self::Encryptor::from_key_state(state, rng) - } - - fn encrypt_init_nonce(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Encryptor { - Self::Encryptor::from_key_state_nonce(state, nonce) - } -} - -impl AeadDecryptMode - for GCM -{ - type Decryptor = Gcm; - - fn decrypt_init(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Decryptor { - Self::Decryptor::from_key_state(state, nonce) - } -} - -impl Mode for GCM { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; - - fn key_state(perm: AES192Internal) -> Self::KeyState { - GcmKeyState::new::<24>(perm) - } -} - -impl AeadEncryptMode - for GCM -{ - type Encryptor = Gcm; - - fn encrypt_init( - state: &Self::KeyState, - rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { - Self::Encryptor::from_key_state(state, rng) - } - - fn encrypt_init_nonce(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Encryptor { - Self::Encryptor::from_key_state_nonce(state, nonce) - } -} - -impl AeadDecryptMode - for GCM -{ - type Decryptor = Gcm; - - fn decrypt_init(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Decryptor { - Self::Decryptor::from_key_state(state, nonce) - } -} - -impl Mode for GCM { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; - - fn key_state(perm: AES256Internal) -> Self::KeyState { - GcmKeyState::new::<32>(perm) - } -} - -impl AeadEncryptMode - for GCM -{ - type Encryptor = Gcm; - - fn encrypt_init( - state: &Self::KeyState, - rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { - Self::Encryptor::from_key_state(state, rng) - } - - fn encrypt_init_nonce(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Encryptor { - Self::Encryptor::from_key_state_nonce(state, nonce) - } -} - -impl AeadDecryptMode - for GCM -{ - type Decryptor = Gcm; - - fn decrypt_init(state: &Self::KeyState, nonce: &[u8; GCM_NONCE_LEN]) -> Self::Decryptor { - Self::Decryptor::from_key_state(state, nonce) - } -} +pub type AES_GCM_256<'k, Dir> = Gcm<'k, AES256Internal, Dir, 16>; diff --git a/crypto/aes/src/gmac.rs b/crypto/aes/src/gmac.rs index 8b3c692b1..f68687167 100644 --- a/crypto/aes/src/gmac.rs +++ b/crypto/aes/src/gmac.rs @@ -1,192 +1,109 @@ -//! Type aliases for AES in GMAC (NIST SP 800-38D Sec 5.2), and the `GMAC` mode impls and the -//! generator/verifier pair that make [`AES128Key`] and friends GMAC keys. -//! -//! See [`bouncycastle_cipher::modes::gmac`] for the construction, the nonce and the security -//! considerations. [`AES_GMAC_128`] and friends pin the permutation and its key length and fix -//! the tag at 128 bits, as the `AES_GCM_*` aliases do; for a shorter tag name -//! [`Gmac`] directly, or the key's mode as `GMAC<12>`. -//! -//! A GMAC tag is bound to a nonce, so the pair is the MAC role pair's names with the nonce added, -//! which is the AEAD role pair's shape with no ciphertext. It is inherent on the key rather than a -//! role trait in `core` because GMAC is the only nonce-based MAC in the tree. -//! -//! # Usage Examples -//! -//! ``` -//! use bouncycastle_aes::AES256Key; -//! use bouncycastle_cipher::modes::GMAC; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::symmetric_key::FromKeyMaterial; -//! use bouncycastle_rng::HashDRBG_SHA512; -//! -//! let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages -//! let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::MACKey).unwrap(); -//! let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 MAC key"); -//! -//! // The nonce is drawn from `rng` and returned; the Result is for that draw only. -//! let (mut generator, nonce) = key.create_generator(&mut rng).expect("nonce draw"); -//! generator.do_update(b"a message that arrives "); -//! generator.do_update(b"in pieces"); -//! let tag = generator.do_final(); -//! -//! // Verification needs the nonce the tag was made under, and is infallible to set up. -//! assert!(key.create_verifier(&nonce).verify(b"a message that arrives in pieces", &tag)); -//! assert!(!key.create_verifier(&nonce).verify(b"another message", &tag)); -//! ``` -//! -//! # Memory Usage -//! -//! A key is the expanded schedule plus the 16-byte hash subkey, the same as a GCM key: 192, 224 -//! and 256 bytes for AES-128, -192 and -256. A worker is a `Gcm` of the same shape. -//! -//! [`AES128Key`]: crate::AES128Key +//! Type aliases for AES in GMAC (NIST SP 800-38D Sec 5.2), and the generator/verifier pair that +//! makes [`AES128Key`] and friends GMAC keys. The docs are on [`AES_GMAC_128`], which the +//! crate root re-exports. -use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use crate::{AES128Key, AES192Key, AES256Key}; -use bouncycastle_cipher::modes::{GCM_NONCE_LEN, GMAC, GcmKeyState, Gmac}; +use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal, AESInternal}; +use crate::{AESKey, AESParams}; +use bouncycastle_cipher::modes::{GCM_NONCE_LEN, GMAC, Gmac, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::Mode; -use bouncycastle_core::key_material::KeyType; +use bouncycastle_core::hazmat::{BlockPermutation, NonceMacMode}; use bouncycastle_core::traits::RNG; +// Imports needed for docs +#[allow(unused_imports)] +use crate::AES128Key; +// end of imports needed for docs + /// AES-128 in GMAC with a 128-bit tag. `Dir` is [`Encrypting`] to generate a tag or /// [`Decrypting`] to verify one. +/// +/// See [`bouncycastle_cipher::modes::gmac`] for the construction, the nonce and the security +/// considerations. [`AES_GMAC_128`] and friends pin the permutation and its key length and fix +/// the tag at 128 bits, as the `AES_GCM_*` aliases do; for a shorter tag name +/// [`Gmac`] directly, or the key's mode as `GMAC<12>`. +/// +/// A GMAC tag is bound to a nonce, so the pair is the MAC role pair's names with the nonce added, +/// which is the AEAD role pair's shape with no ciphertext. It is inherent on the key rather than a +/// role trait in `core` because GMAC is the only nonce-based MAC in the tree. +/// +/// # Usage Examples +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::GMAC; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::FromKeyMaterial; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::MACKey).unwrap(); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 MAC key"); +/// +/// // The nonce is drawn from `rng` and returned; the Result is for that draw only. +/// let (mut generator, nonce) = key.create_generator(&mut rng).expect("nonce draw"); +/// generator.do_update(b"a message that arrives "); +/// generator.do_update(b"in pieces"); +/// let tag = generator.do_final(); +/// +/// // Verification needs the nonce the tag was made under, and is infallible to set up. +/// assert!(key.create_verifier(&nonce).verify(b"a message that arrives in pieces", &tag)); +/// assert!(!key.create_verifier(&nonce).verify(b"another message", &tag)); +/// ``` +/// +/// # Memory Usage +/// +/// A key is the expanded schedule plus the 16-byte hash subkey, the same as a GCM key: 192, 224 +/// and 256 bytes for AES-128, -192 and -256. A worker is a `Gcm` of the same shape. +/// +/// [`AES128Key`]: crate::AES128Key #[allow(non_camel_case_types)] -pub type AES_GMAC_128

= Gmac; +pub type AES_GMAC_128<'k, Dir> = Gmac<'k, AES128Internal, Dir, 16>; /// AES-192 in GMAC with a 128-bit tag. See [`AES_GMAC_128`]. #[allow(non_camel_case_types)] -pub type AES_GMAC_192 = Gmac; +pub type AES_GMAC_192<'k, Dir> = Gmac<'k, AES192Internal, Dir, 16>; /// AES-256 in GMAC with a 128-bit tag. See [`AES_GMAC_128`]. #[allow(non_camel_case_types)] -pub type AES_GMAC_256 = Gmac; - -// Per key length for the reason the GCM impls are (see `gcm.rs`): the worker carries `KEY_LEN`, -// which cannot be tied to `Mode

` from a generic impl. The pair is per key length for the same -// reason, since its return types name the worker. - -impl Mode for GMAC { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES128Internal) -> Self::KeyState { - GcmKeyState::new::<16>(perm) - } -} - -impl AES128Key> { +pub type AES_GMAC_256<'k, Dir> = Gmac<'k, AES256Internal, Dir, 16>; + +// The pair is inherent on the key rather than a role trait, since a GMAC tag is bound to a nonce; +// one impl over every AES key length, forwarding to the mode's `NonceMacMode` impl as the role +// impls forward to theirs. +impl AESKey> +where + P: AESParams, + AESInternal

: BlockPermutation<16> + ModeNames + Clone, +{ /// Begins a tag under a nonce drawn from `rng`, which is returned with the generator. /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - pub fn create_generator( - &self, + pub fn create_generator<'k>( + &'k self, rng: &mut dyn RNG, ) -> Result< - (Gmac, [u8; GCM_NONCE_LEN]), + (Gmac<'k, AESInternal

, Encrypting, TAG_LEN>, [u8; GCM_NONCE_LEN]), SymmetricCipherError, > { - Gmac::::from_key_state(&self.state, rng) + GMAC::::generate_init(&self.state, rng) } /// Begins a tag under a nonce the caller supplies, who takes on the obligation that it never /// repeat under this key. - pub fn create_generator_nonce( - &self, + pub fn create_generator_nonce<'k>( + &'k self, nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state_nonce(&self.state, nonce) + ) -> Gmac<'k, AESInternal

, Encrypting, TAG_LEN> { + GMAC::::generate_init_nonce(&self.state, nonce) } /// Begins the verification of a tag made under `nonce`. Infallible. - pub fn create_verifier( - &self, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state(&self.state, nonce) - } -} - -impl Mode for GMAC { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES192Internal) -> Self::KeyState { - GcmKeyState::new::<24>(perm) - } -} - -impl AES192Key> { - /// As [`AES128Key::create_generator`]. - /// - /// # Errors - /// As [`AES128Key::create_generator`]. - pub fn create_generator( - &self, - rng: &mut dyn RNG, - ) -> Result< - (Gmac, [u8; GCM_NONCE_LEN]), - SymmetricCipherError, - > { - Gmac::::from_key_state(&self.state, rng) - } - - /// As [`AES128Key::create_generator_nonce`]. - pub fn create_generator_nonce( - &self, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state_nonce(&self.state, nonce) - } - - /// As [`AES128Key::create_verifier`]. - pub fn create_verifier( - &self, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state(&self.state, nonce) - } -} - -impl Mode for GMAC { - type KeyState = GcmKeyState; - const KEY_TYPE: KeyType = KeyType::MACKey; - - fn key_state(perm: AES256Internal) -> Self::KeyState { - GcmKeyState::new::<32>(perm) - } -} - -impl AES256Key> { - /// As [`AES128Key::create_generator`]. - /// - /// # Errors - /// As [`AES128Key::create_generator`]. - pub fn create_generator( - &self, - rng: &mut dyn RNG, - ) -> Result< - (Gmac, [u8; GCM_NONCE_LEN]), - SymmetricCipherError, - > { - Gmac::::from_key_state(&self.state, rng) - } - - /// As [`AES128Key::create_generator_nonce`]. - pub fn create_generator_nonce( - &self, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state_nonce(&self.state, nonce) - } - - /// As [`AES128Key::create_verifier`]. - pub fn create_verifier( - &self, + pub fn create_verifier<'k>( + &'k self, nonce: &[u8; GCM_NONCE_LEN], - ) -> Gmac { - Gmac::::from_key_state(&self.state, nonce) + ) -> Gmac<'k, AESInternal

, Decrypting, TAG_LEN> { + GMAC::::verify_init(&self.state, nonce) } } diff --git a/crypto/aes/src/hazmat/aes_internal.rs b/crypto/aes/src/hazmat/aes_internal.rs index 8133c4a5a..4437b4f10 100644 --- a/crypto/aes/src/hazmat/aes_internal.rs +++ b/crypto/aes/src/hazmat/aes_internal.rs @@ -10,7 +10,7 @@ //! ``` //! use bouncycastle_aes::hazmat::AES128Internal; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::hazmat::ElectronicCodeBook; +//! use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; //! //! let key = KeyMaterial::<16>::from_bytes_as_type( //! &[0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, @@ -44,15 +44,15 @@ //! block: `u16` planes hold one block, `u32` planes two and `u64` planes four (see the //! `bitslice` module in the source). The round functions cost about the same whatever the //! width, so on a 64-bit machine four independent blocks cost little more than one. Where a -//! caller has them, [`ElectronicCodeBook::encrypt_4blocks`] is 3.0x the throughput of four -//! [`ElectronicCodeBook::encrypt_block`] calls on x86-64 (3.7x for decryption), and -//! [`ElectronicCodeBook::encrypt_2blocks`] 1.75x that of two (1.95x for decryption); the crate +//! caller has them, [`BlockPermutation::encrypt_4blocks`] is 3.0x the throughput of four +//! [`BlockPermutation::encrypt_block`] calls on x86-64 (3.7x for decryption), and +//! [`BlockPermutation::encrypt_2blocks`] 1.75x that of two (1.95x for decryption); the crate //! docs have the table and the benches record the numbers: //! //! ``` //! use bouncycastle_aes::hazmat::AES256Internal; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::hazmat::ElectronicCodeBook; +//! use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); @@ -101,6 +101,17 @@ pub struct AESInternal { schedule: Secret, } +// Written out rather than derived so that `P`, a marker type, need not be `PartialEq`. Round key +// 0 is the key itself (FIPS 197 Sec 5.2), so two schedules are equal exactly when the keys are; +// `Secret`'s equality inspects every byte, so the time taken does not say where they differ. +impl PartialEq for AESInternal

{ + fn eq(&self, other: &Self) -> bool { + self.schedule == other.schedule + } +} + +impl Eq for AESInternal

{} + /// AES-128: 16-byte key, 10 rounds (FIPS 197 Table 3). #[allow(non_camel_case_types)] pub type AES128Internal = AESInternal; diff --git a/crypto/aes/src/hazmat/ecb.rs b/crypto/aes/src/hazmat/ecb.rs index c2b44d912..6d910ed04 100644 --- a/crypto/aes/src/hazmat/ecb.rs +++ b/crypto/aes/src/hazmat/ecb.rs @@ -5,100 +5,90 @@ //! //! See [`bouncycastle_cipher::modes::hazmat::ecb::Ecb`] for details on the ElectronicCodebook construction. //! -//! The aliases here are padded block ciphers that accept input of any size; `NoPadding` accepts +//! The aliases here name the padded workers, which accept input of any size; `NoPadding` accepts //! only whole blocks but goes through the same adapter. The unpadded mode underneath them, which //! implements the block-cipher traits directly, is [`Ecb`] and is not re-exported from this crate. +//! The workers come from a key: [`AES128Key>`](crate::AES128Key) (or `ECB`) +//! hands them out through the [`SymmetricEncryptionKey`] / [`SymmetricDecryptionKey`] pair, and +//! `AES128Key>` hands out [`Ecb`] itself through the [`EcbEncryptionKey`] / +//! [`EcbDecryptionKey`] pair. //! -//! ECB has no IV, so its `INIT_DATA_LEN` is 0: encryption returns an empty array, decryption takes -//! one, and the ciphertext is exactly the padded plaintext with nothing prepended. The RNG-taking -//! constructors, `do_encrypt_init_rng` and `encrypt_rng_out`, panic, as -//! [`SymmetricCipherEncryptor::do_encrypt_init_rng`] requires of a cipher with no init data to -//! generate; use the plain `do_encrypt_init` / `encrypt_out`. +//! ECB has no IV, so its `INIT_DATA_LEN` is 0: the padded pair's encryption returns an empty +//! array, its decryption takes one, and the ciphertext is exactly the padded plaintext with +//! nothing prepended. The RNG that pair's `create_encryptor` and one-shots take is never drawn +//! from, and the `ECB` pair takes none at all. //! //! # Usage Examples //! //! ## One-shot API //! -//! 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_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_aes::AES256Key; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; //! use bouncycastle_cipher::padding::PKCS7; +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! // Define ourselves convenience types. -//! type AESEnc = AES_ECB_256; -//! type AESDec = AES_ECB_256; -//! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); +//! let key = AES256Key::>::from_key_material(&material).expect("a valid AES-256 key"); //! -//! // An arbitrary plaintext to encrypt //! // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. //! let plaintext = [0x5Au8; 50]; //! //! // ECB has no IV, so the init data that comes back is empty. -//! let (no_iv, ciphertext) = AESEnc::encrypt(&key, &plaintext).expect("encryption"); +//! let (no_iv, ciphertext) = key.encrypt(&mut rng, &plaintext).expect("encryption"); //! assert_eq!(no_iv, [0u8; 0]); //! assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); //! -//! let recovered = AESDec::decrypt(&key, &no_iv, &ciphertext).expect("decryption"); +//! let recovered = key.decrypt(&no_iv, &ciphertext).expect("decryption"); //! assert_eq!(recovered, plaintext); //! ``` //! //! ## Streaming API //! -//! For data that arrives in pieces, the following APIs can be used: +//! For data that arrives in pieces, the workers' streaming methods can be used: //! //! ``` -//! use bouncycastle_aes::AES_BLOCK_LEN; -//! use bouncycastle_aes::hazmat::AES_ECB_128; +//! use bouncycastle_aes::{AES128Key, AES_BLOCK_LEN}; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; +//! use bouncycastle_cipher::padding::PKCS7; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; //! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_cipher::padding::PKCS7; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! // Define ourselves convenience types. -//! type AESEnc = AES_ECB_128; -//! type AESDec = AES_ECB_128; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let key = AES128Key::>::from_key_material(&material).expect("a valid AES-128 key"); //! -//! // An arbitrary plaintext to encrypt //! let plaintext = [0x5Au8; 50]; //! -//! // The streaming (chunked) API allows for data to be handed to the cipher as it arrives, in chunks -//! // of any length, but it will only be processed once a full block has been received. -//! // Here, we will use 7-byte chunks -//! let (mut encryptor, no_iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); +//! // Data can be handed to the encryptor as it arrives, in pieces of any length, but a block is +//! // only written out once it is complete. Here, 7-byte pieces. +//! let (mut encryptor, no_iv) = key.create_encryptor(&mut rng).expect("nothing is drawn"); //! //! let mut ciphertext = Vec::new(); -//! //! for piece in plaintext.chunks(7) { //! let mut out = [0u8; AES_BLOCK_LEN]; //! let bytes_written = encryptor.do_encrypt_out(piece, &mut out).expect("encryption"); -//! //! // If that doesn't complete a block, then nothing is written. -//! if bytes_written != 0 { -//! ciphertext.extend_from_slice(&out[..bytes_written]); -//! } +//! ciphertext.extend_from_slice(&out[..bytes_written]); //! } //! let (last_block, last_len) = encryptor.do_encrypt_final().expect("padding the final block"); //! ciphertext.extend_from_slice(&last_block[..last_len]); //! assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); //! -//! // Decrypt the ciphertext in 19-byte chunks. -//! let mut decryptor = AESDec::do_decrypt_init(&key, &no_iv).expect("decrypt init"); +//! // Decrypt the ciphertext in 19-byte pieces; the decryptor is infallible to set up. +//! let mut decryptor = key.create_decryptor(&no_iv); //! let mut recovered = Vec::new(); //! for piece in ciphertext.chunks(19) { //! let mut out = [0u8; AES_BLOCK_LEN]; //! let bytes_written = decryptor.do_decrypt_out(piece, &mut out).expect("decryption"); -//! if bytes_written != 0 { -//! recovered.extend_from_slice(&out[..bytes_written]); -//! } +//! recovered.extend_from_slice(&out[..bytes_written]); //! } //! let (last_block, last_len) = decryptor.do_decrypt_final().expect("a valid final block"); //! recovered.extend_from_slice(&last_block[..last_len]); @@ -111,41 +101,50 @@ //! 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_cipher::Encrypting; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; //! use bouncycastle_cipher::padding::NoPadding; +//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! // 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 mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES128Key::>::from_key_material(&material) +//! .expect("a valid AES-128 key"); //! //! // A whole block is fine, and comes out the same length. //! let mut out = [0u8; 16]; -//! let (_no_iv, written) = Enc::encrypt_out(&key, &[0u8; 16], &mut out).expect("aligned"); +//! let (written, _no_iv) = key.encrypt_out(&mut rng, &[0u8; 16], &mut out).expect("aligned"); //! assert_eq!(written, 16); //! //! // Five bytes is not, and is refused rather than padded. -//! let mut out = [0u8; 16]; -//! assert!(Enc::encrypt_out(&key, b"hello", &mut out).is_err()); +//! assert!(key.encrypt_out(&mut rng, b"hello", &mut out).is_err()); //! ``` //! -//! The padding scheme is part of the type, so the two schemes are different types and cannot be -//! interchanged. A value built with one will not satisfy a binding annotated with the other. +//! The padding scheme is part of the key type, so the two schemes' workers are different types +//! and cannot be interchanged. A value built with one will not satisfy a binding annotated with +//! the other. //! //! ```compile_fail +//! use bouncycastle_aes::AES128Key; //! use bouncycastle_aes::hazmat::AES_ECB_128; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; //! use bouncycastle_cipher::Encrypting; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; //! use bouncycastle_cipher::padding::{NoPadding, PKCS7}; +//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES128Key::>::from_key_material(&material) +//! .expect("a valid AES-128 key"); //! -//! // Built as NoPadding, annotated as PKCS7: mismatched types. -//! let (enc, _no_iv) = AES_ECB_128::::do_encrypt_init(&key).unwrap(); +//! // Built from a NoPadding key, annotated as PKCS7: mismatched types. +//! let (enc, _no_iv) = +//! key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).expect("no IV to draw"); //! let _mismatched: AES_ECB_128 = enc; //! ``` //! @@ -157,19 +156,22 @@ //! 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_aes::AES128Key; +//! use bouncycastle_cipher::modes::Blocks; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; +//! use bouncycastle_core::hazmat::EcbEncryptionKey; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; -//! use bouncycastle_cipher::Encrypting; -//! use bouncycastle_cipher::padding::NoPadding; +//! use bouncycastle_core::symmetric_key::FromKeyMaterial; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES128Key::>::from_key_material(&material).expect("a valid AES-128 key"); //! //! // Two identical blocks in... -//! let (_, ciphertext) = -//! AES_ECB_128::::encrypt(&key, &[0x5Au8; 32]).expect("encryption"); +//! let mut blocks = [0x5Au8; 32]; +//! key.encrypt_inplace(&mut blocks).expect("encryption"); //! // ...two identical blocks out. Nothing here chains, so nothing hides the repetition. -//! assert_eq!(ciphertext[..16], ciphertext[16..]); +//! assert_eq!(blocks[..16], blocks[16..]); //! ``` use crate::AES_BLOCK_LEN; @@ -181,30 +183,30 @@ 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::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::KeyMaterial; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_cipher::padding::{NoPadding, PKCS7}; #[allow(unused_imports)] -use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::hazmat::{EcbDecryptionKey, EcbEncryptionKey}; +#[allow(unused_imports)] +use bouncycastle_core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; // end of imports needed for docs /// AES-128 in ECB mode with a padding scheme. #[allow(non_camel_case_types)] -pub type AES_ECB_128 =

::Select< +pub type AES_ECB_128<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb<'k, AES128Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 16, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb<'k, AES128Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 16, 0, AES_BLOCK_LEN, >, @@ -212,18 +214,16 @@ pub type AES_ECB_128 = ::Select< /// AES-192 in ECB mode with a padding scheme. #[allow(non_camel_case_types)] -pub type AES_ECB_192 = ::Select< +pub type AES_ECB_192<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb<'k, AES192Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 24, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb<'k, AES192Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 24, 0, AES_BLOCK_LEN, >, @@ -231,18 +231,16 @@ pub type AES_ECB_192 = ::Select< /// AES-256 in ECB mode with a padding scheme. See [`AES_ECB_128`]. #[allow(non_camel_case_types)] -pub type AES_ECB_256 = ::Select< +pub type AES_ECB_256<'k, Dir, Pad> = ::Select< PaddedBlockCipherEncryptor< - Ecb, + Ecb<'k, AES256Internal, Encrypting, AES_BLOCK_LEN>, Pad, - 32, 0, AES_BLOCK_LEN, >, PaddedBlockCipherDecryptor< - Ecb, + Ecb<'k, AES256Internal, Decrypting, AES_BLOCK_LEN>, Pad, - 32, 0, AES_BLOCK_LEN, >, @@ -252,6 +250,9 @@ impl ElectronicCodeBook<16, AES_BLOCK_LEN> for AES128Internal { fn new(key: &KeyMaterial<16>) -> Result { AES128Internal::new(key) } +} + +impl BlockPermutation for AES128Internal { fn encrypt_block(&self, block: &mut Block) { Self::encrypt_block(self, block) } @@ -276,6 +277,9 @@ impl ElectronicCodeBook<24, AES_BLOCK_LEN> for AES192Internal { fn new(key: &KeyMaterial<24>) -> Result { AES192Internal::new(key) } +} + +impl BlockPermutation for AES192Internal { fn encrypt_block(&self, block: &mut Block) { Self::encrypt_block(self, block) } @@ -300,6 +304,9 @@ impl ElectronicCodeBook<32, AES_BLOCK_LEN> for AES256Internal { fn new(key: &KeyMaterial<32>) -> Result { AES256Internal::new(key) } +} + +impl BlockPermutation for AES256Internal { fn encrypt_block(&self, block: &mut Block) { Self::encrypt_block(self, block) } diff --git a/crypto/aes/src/key.rs b/crypto/aes/src/key.rs index 81a0fe1ba..82d1bc994 100644 --- a/crypto/aes/src/key.rs +++ b/crypto/aes/src/key.rs @@ -1,83 +1,21 @@ -//! AES key objects: a key bound to one AES variant and one mode, validated and expanded once, -//! from which the mode's encryptors and decryptors are made. -//! -//! [`AES128Key`], [`AES192Key`] and [`AES256Key`] are one type each per mode `M`, so a key -//! is bound to its AES variant and its mode by the Rust type rather than by the length of a byte -//! buffer and the call site. Validation and key expansion happen once, in the constructor, and -//! every message under the key starts from the stored schedule (and, for GCM, the stored hash -//! subkey, or for CMAC the stored subkeys). Using one key under two modes is two -//! `from_key_material` calls on two key types; the accidental version does not compile. A MAC -//! mode's key takes `KeyType::MACKey` material, a cipher mode's `KeyType::SymmetricCipherKey`. -//! Which workers a key hands out is decided by its mode: the AEAD and MAC role traits for the -//! modes that fit them, and for `GMAC`, whose tag is nonce-bound, an inherent pair (see -//! [`crate::gmac`]). -//! -//! # Usage Examples -//! -//! ``` -//! use bouncycastle_aes::AES256Key; -//! use bouncycastle_cipher::modes::GCM; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherEncryptor}; -//! use bouncycastle_rng::HashDRBG_SHA512; -//! -//! let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages -//! let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) -//! .expect("a 32-byte symmetric cipher key"); -//! let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); -//! -//! // Streaming: the nonce is drawn from `rng` and returned; the Result is for that draw only. -//! let (mut enc, nonce) = key.create_encryptor(&mut rng).expect("nonce draw"); -//! enc.do_update_aad(b"header").expect("aad"); -//! let mut ciphertext = [0u8; 14]; -//! enc.do_encrypt_out(b"attack at dawn", &mut ciphertext).expect("encryption"); -//! let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; 16]).expect("the tag"); -//! -//! // Decryption from the same key is infallible to set up; the nonce type follows from the key. -//! let mut dec = key.create_decryptor(&nonce); -//! dec.do_update_aad(b"header").expect("aad"); -//! // ... feed `ciphertext`, then check `tag` with `do_decrypt_final_detached_out`. -//! -//! // One-shots take the same path. -//! let (n, nonce) = key -//! .encrypt_with_aad_out(&mut rng, b"header", b"attack at dawn", &mut [0u8; 30]) -//! .expect("one-shot"); -//! assert_eq!(n, 14 + 16); -//! ``` -//! -//! The nonce a decryptor takes is the array the key's mode fixes, so a nonce of another length is -//! a compile error rather than a runtime one: -//! -//! ```compile_fail -//! # use bouncycastle_aes::AES128Key; -//! # use bouncycastle_cipher::modes::GCM; -//! # use bouncycastle_core::symmetric_key::{AeadDecryptionKey, FromKeyMaterial}; -//! # use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! # let km = KeyMaterial128::from_bytes_as_type(&[1; 16], KeyType::SymmetricCipherKey).unwrap(); -//! let key = AES128Key::::from_key_material(&km).unwrap(); -//! let dec = key.create_decryptor(&[0u8; 16]); // GCM's nonce is 12 bytes -//! ``` -//! -//! # Memory Usage -//! -//! A key is its mode's per-key state: the expanded schedule, 176, 208 or 240 bytes for AES-128, -//! -192 and -256, plus 16 bytes of hash subkey under `GCM` or `GMAC`, or 32 bytes of subkeys -//! under `CMAC`. -//! The cost is paid per key, not per message, and replaces the per-message validation and -//! expansion of the `KeyMaterial` path. +//! AES key objects: a key bound to one AES variant and one mode, validated and expanded once, from +//! which the mode's encryptors and decryptors are made. The docs are on [`AESKey`], which the crate +//! root re-exports. use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal, AESInternal}; use crate::{AES128Params, AES192Params, AES256Params, AESParams}; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; use bouncycastle_core::hazmat::{ - AeadDecryptMode, AeadEncryptMode, AeadPacketDecryptMode, AeadPacketEncryptMode, MacMode, Mode, + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, AeadDecryptMode, AeadEncryptMode, + BlockDecryptMode, BlockEncryptMode, EcbDecryptMode, EcbDecryptionKey, EcbEncryptMode, + EcbEncryptionKey, MacMode, Mode, ModeKey, SymmetricDecryptMode, SymmetricEncryptMode, do_hazardous_operations, }; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::symmetric_key::{ - AeadDecryptionKey, AeadEncryptionKey, AeadPacketDecryptionKey, AeadPacketEncryptionKey, - FromKeyMaterial, MacGenerationKey, MacVerificationKey, + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, + BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, MacGenerationKey, MacVerificationKey, + SymmetricDecryptionKey, SymmetricEncryptionKey, }; use bouncycastle_core::traits::RNG; @@ -87,6 +25,74 @@ use bouncycastle_core::traits::RNG; /// The only state is the mode's per-key state, which holds the expanded schedule in a `Secret` /// and so is zeroized on drop; the key has no `Debug` and no way to read the material back. A /// caller who needs the bytes again keeps the `KeyMaterial`. +/// +/// [`AES128Key`], [`AES192Key`] and [`AES256Key`] are one type each per mode `M`, so a key +/// is bound to its AES variant and its mode by the Rust type rather than by the length of a byte +/// buffer and the call site. Validation and key expansion happen once, in the constructor, and +/// every message under the key starts from the stored schedule (and, for GCM, the stored hash +/// subkey, or for CMAC the stored subkeys), which the workers borrow rather than copy. Using one +/// key under two modes is two `from_key_material` calls on two key types; the accidental version +/// does not compile. A MAC mode's key takes `KeyType::MACKey` material, a cipher mode's +/// `KeyType::SymmetricCipherKey`. Which workers a key hands out is decided by its mode: the +/// symmetric pair for the stream modes and the padded block modes (`CFB`, `CFB8`, `CTR`, +/// `CBC`, `ECB`), the block pair for `CBC`, the `hazmat` ECB pair for +/// `ECB`, the AEAD pairs for `GCM`, `CCM` and `CCM_Packet`, the MAC pair for `CMAC`, and +/// for `GMAC`, whose tag is nonce-bound, an inherent pair (see [`AES_GMAC_128`](crate::AES_GMAC_128)). +/// +/// # Usage Examples +/// +/// ``` +/// use bouncycastle_aes::AES256Key; +/// use bouncycastle_cipher::modes::GCM; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); // one RNG, held across messages +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +/// .expect("a 32-byte symmetric cipher key"); +/// let key = AES256Key::::from_key_material(&material).expect("a valid AES-256 key"); +/// +/// // Streaming: the nonce is drawn from `rng` and returned; the Result is for that draw only. +/// let (mut enc, nonce) = key.create_encryptor(&mut rng).expect("nonce draw"); +/// enc.do_update_aad(b"header").expect("aad"); +/// let mut ciphertext = [0u8; 14]; +/// enc.do_encrypt_out(b"attack at dawn", &mut ciphertext).expect("encryption"); +/// let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; 16]).expect("the tag"); +/// +/// // Decryption from the same key is infallible to set up; the nonce type follows from the key. +/// let mut dec = key.create_decryptor(&nonce); +/// dec.do_update_aad(b"header").expect("aad"); +/// // ... feed `ciphertext`, then check `tag` with `do_decrypt_final_detached_out`. +/// +/// // One-shots take the same path. +/// let (n, nonce) = key +/// .encrypt_with_aad_out(&mut rng, b"header", b"attack at dawn", &mut [0u8; 30]) +/// .expect("one-shot"); +/// assert_eq!(n, 14 + 16); +/// ``` +/// +/// The nonce a decryptor takes is the array the key's mode fixes, so a nonce of another length is +/// a compile error rather than a runtime one: +/// +/// ```compile_fail +/// # use bouncycastle_aes::AES128Key; +/// # use bouncycastle_cipher::modes::GCM; +/// # use bouncycastle_core::symmetric_key::{AeadDecryptionKey, FromKeyMaterial}; +/// # use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +/// # let km = KeyMaterial128::from_bytes_as_type(&[1; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let key = AES128Key::::from_key_material(&km).unwrap(); +/// let dec = key.create_decryptor(&[0u8; 16]); // GCM's nonce is 12 bytes +/// ``` +/// +/// # Memory Usage +/// +/// A key is its mode's per-key state: the expanded schedule, 176, 208 or 240 bytes for AES-128, +/// -192 and -256, plus 16 bytes of hash subkey under `GCM` or `GMAC`, or 32 bytes of subkeys +/// under `CMAC`. +/// The cost is paid per key, not per message, and replaces the per-message validation and +/// expansion of the `KeyMaterial` path. pub struct AESKey>> { pub(crate) state: M::KeyState, } @@ -108,6 +114,20 @@ impl>> Clone for AESKey { } } +// Likewise for equality: two keys are equal when their mode states are, which every state over +// `AESInternal` decides in constant time, so the comparison follows the material without +// exposing it. +impl>> PartialEq for AESKey +where + M::KeyState: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.state == other.state + } +} + +impl>> Eq for AESKey where M::KeyState: Eq {} + /// The key-type check the mode asks for (`Mode::KEY_TYPE`), ahead of the permutation's own /// validation. fn check_key_type( @@ -192,6 +212,12 @@ impl> FromKeyMaterial<32> for AES256Key { } } +impl>> ModeKey, M> for AESKey { + fn state(&self) -> &M::KeyState { + &self.state + } +} + // The role impls forward to whichever mode traits `M` implements over this permutation, so which // roles a key type carries is decided by its mode. They are per crate rather than a blanket in // `core` because the key state leaves the key only through here. @@ -208,16 +234,19 @@ where P: AESParams, M: AeadEncryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, { - type Encryptor = M::Encryptor; + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; - fn create_encryptor( - &self, + fn create_encryptor<'k>( + &'k self, rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError> { + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { M::encrypt_init(&self.state, rng) } - fn create_encryptor_nonce(&self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor { + fn create_encryptor_nonce<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor<'k> { M::encrypt_init_nonce(&self.state, nonce) } @@ -254,9 +283,12 @@ where P: AESParams, M: AeadDecryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, { - type Decryptor = M::Decryptor; + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; - fn create_decryptor(&self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor { + fn create_decryptor<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k> { M::decrypt_init(&self.state, nonce) } @@ -278,28 +310,31 @@ impl< const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, -> AeadPacketEncryptionKey for AESKey +> AeadDeclaredEncryptionKey for AESKey where P: AESParams, - M: AeadPacketEncryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, + M: AeadDeclaredEncryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, { - type Encryptor = M::Encryptor; + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; - fn create_encryptor( - &self, + fn create_encryptor<'k>( + &'k self, rng: &mut dyn RNG, aad_len: usize, payload_len: usize, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError> { + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { M::encrypt_init(&self.state, rng, aad_len, payload_len) } - fn create_encryptor_nonce( - &self, + fn create_encryptor_nonce<'k>( + &'k self, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result { + ) -> Result, SymmetricCipherError> { M::encrypt_init_nonce(&self.state, nonce, aad_len, payload_len) } @@ -330,19 +365,22 @@ impl< const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, -> AeadPacketDecryptionKey for AESKey +> AeadDeclaredDecryptionKey for AESKey where P: AESParams, - M: AeadPacketDecryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, + M: AeadDeclaredDecryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, { - type Decryptor = M::Decryptor; + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; - fn create_decryptor( - &self, + fn create_decryptor<'k>( + &'k self, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result { + ) -> Result, SymmetricCipherError> { M::decrypt_init(&self.state, nonce, aad_len, payload_len) } @@ -357,18 +395,126 @@ where } } +impl + SymmetricEncryptionKey for AESKey +where + P: AESParams, + M: SymmetricEncryptMode, KEY_LEN, INIT_DATA_LEN, FINAL_LEN>, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng) + } +} + +impl + SymmetricDecryptionKey for AESKey +where + P: AESParams, + M: SymmetricDecryptMode, KEY_LEN, INIT_DATA_LEN, FINAL_LEN>, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state, init_data) + } +} + +impl + BlockEncryptionKey for AESKey +where + P: AESParams, + M: BlockEncryptMode, KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng) + } +} + +impl + BlockDecryptionKey for AESKey +where + P: AESParams, + M: BlockDecryptMode, KEY_LEN, INIT_DATA_LEN, BLOCK_LEN>, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state, init_data) + } +} + +impl EcbEncryptionKey + for AESKey +where + P: AESParams, + M: EcbEncryptMode, KEY_LEN, BLOCK_LEN>, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>(&'k self) -> Self::Encryptor<'k> { + M::encrypt_init(&self.state) + } +} + +impl EcbDecryptionKey + for AESKey +where + P: AESParams, + M: EcbDecryptMode, KEY_LEN, BLOCK_LEN>, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state) + } +} + impl>> MacGenerationKey for AESKey { - type Generator = M::Worker; + type Generator<'k> + = M::Worker<'k> + where + Self: 'k; - fn create_generator(&self) -> Self::Generator { + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { M::mac_init(&self.state) } } impl>> MacVerificationKey for AESKey { - type Verifier = M::Worker; + type Verifier<'k> + = M::Worker<'k> + where + Self: 'k; - fn create_verifier(&self) -> Self::Verifier { + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { M::mac_init(&self.state) } } diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index 5f240af11..f203062ee 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -12,20 +12,21 @@ //! is not secure to use by itself; see //! [A block permutation is not a cipher](#a-block-permutation-is-not-a-cipher) below. //! -//! For ready-to-use primitives, see the documentation for one of the provided modes of operation: -//! -//! * [AES_CBC](crate::cbc) -//! * [AES_CCM](crate::ccm) -//! * [AES_CFB](crate::cfb) -//! * [AES_CFB8](crate::cfb8) -//! * [AES_CMAC](crate::cmac), a MAC rather than a cipher -//! * [AES_CTR](crate::ctr) -//! * [AES_GCM](crate::gcm) -//! * [AES_GMAC](crate::gmac), a nonce-based MAC rather than a cipher -//! -//! A key that will encrypt many messages is better held as a mode-bound key object such as -//! [`AES256Key`](AES256Key), which validates and expands once and hands out the mode's -//! encryptors and decryptors; see [`key`]. +//! For ready-to-use primitives, see the documentation of one of the provided modes of +//! operation, on the 128-bit alias of each: +//! +//! * [`AES_CBC_128`] +//! * [`AES_CCM_128`] +//! * [`AES_CFB_128`] +//! * [`AES_CFB8_128`] +//! * [`AES_CMAC_128`], a MAC rather than a cipher +//! * [`AES_CTR_128`] +//! * [`AES_GCM_128`] +//! * [`AES_GMAC_128`], a nonce-based MAC rather than a cipher +//! +//! Each mode is used through a mode-bound key object such as [`AES256Key`](AES256Key), +//! which validates and expands the material once and hands out the mode's encryptors and +//! decryptors; see [`AESKey`]. //! //! AES in ECB mode, [`AES_ECB_128`](hazmat::AES_ECB_128) and friends, is under [`hazmat`] because //! it is a building block for other modes, not itself a confidentiality mode for data. @@ -174,16 +175,16 @@ #![allow(private_bounds)] mod bitslice; -pub mod cbc; -pub mod ccm; -pub mod cfb; -pub mod cfb8; -pub mod cmac; -pub mod ctr; -pub mod gcm; -pub mod gmac; +mod cbc; +mod ccm; +mod cfb; +mod cfb8; +mod cmac; +mod ctr; +mod gcm; +mod gmac; pub mod hazmat; -pub mod key; +mod key; mod round; mod sbox; mod schedule; diff --git a/crypto/aes/tests/alg_name_tests.rs b/crypto/aes/tests/alg_name_tests.rs index 67378cd59..481a4bd3a 100644 --- a/crypto/aes/tests/alg_name_tests.rs +++ b/crypto/aes/tests/alg_name_tests.rs @@ -1,6 +1,4 @@ -//! Each public AES alias reports its own algorithm through `Algorithm`, not the AES permutation it -//! is built on: `AES_GCM_128` is `"AES_GCM_128"`, while `AES128Internal` stays `"AES-128"`. The -//! strength is the key's, so it is the same for every mode at a key size. +//! What each public AES alias is: the algorithm it reports and the type it resolves to. //! //! The direction and the padding are type parameters of one algorithm, not different algorithms, //! so every combination of them must report the same name, and so must every CCM frame. @@ -10,16 +8,22 @@ use bouncycastle_aes::{ AES_CBC_128, AES_CBC_192, AES_CBC_256, AES_CCM_128, AES_CCM_192, AES_CCM_256, AES_CFB_128, AES_CFB_192, AES_CFB_256, AES_CFB8_128, AES_CFB8_192, AES_CFB8_256, AES_CMAC_128, AES_CMAC_192, AES_CMAC_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, AES_GCM_128, AES_GCM_192, AES_GCM_256, - AES_GMAC_128, AES_GMAC_192, AES_GMAC_256, CCM_NONCE_LEN, CCM_TAG_LEN, + AES_GMAC_128, AES_GMAC_192, AES_GMAC_256, AES128Key, CCM_NONCE_LEN, CCM_TAG_LEN, }; -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; -use bouncycastle_cipher::modes::{Cbc, Gcm}; +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; +use bouncycastle_cipher::modes::{CBC, Cbc, Gcm}; use bouncycastle_cipher::padding::{ NoPadding, PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor, }; use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_core::hazmat::Mode; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::Algorithm; +use bouncycastle_rng::HashDRBG_SHA512; use core::mem::size_of; /// Both directions of one algorithm report `name` and `strength`. @@ -32,68 +36,80 @@ fn check(name: &str, strength: SecurityStrength) #[test] fn cbc() { - type Enc = AES_CBC_128; + type Enc<'k> = AES_CBC_128<'k, Encrypting, NoPadding>; assert_eq!(Enc::ALG_NAME, "AES_CBC_128"); assert_eq!(Enc::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); use SecurityStrength::*; - check::, AES_CBC_128>( + check::, AES_CBC_128<'_, Decrypting, NoPadding>>( + "AES_CBC_128", _128bit, + ); + check::, AES_CBC_128<'_, Decrypting, PKCS7>>( "AES_CBC_128", _128bit, ); - check::, AES_CBC_128>("AES_CBC_128", _128bit); - check::, AES_CBC_192>( + check::, AES_CBC_192<'_, Decrypting, NoPadding>>( + "AES_CBC_192", _192bit, + ); + check::, AES_CBC_192<'_, Decrypting, PKCS7>>( "AES_CBC_192", _192bit, ); - check::, AES_CBC_192>("AES_CBC_192", _192bit); - check::, AES_CBC_256>( + check::, AES_CBC_256<'_, Decrypting, NoPadding>>( + "AES_CBC_256", _256bit, + ); + check::, AES_CBC_256<'_, Decrypting, PKCS7>>( "AES_CBC_256", _256bit, ); - check::, AES_CBC_256>("AES_CBC_256", _256bit); } #[test] fn ecb() { use SecurityStrength::*; - check::, AES_ECB_128>( + check::, AES_ECB_128<'_, Decrypting, NoPadding>>( + "AES_ECB_128", _128bit, + ); + check::, AES_ECB_128<'_, Decrypting, PKCS7>>( "AES_ECB_128", _128bit, ); - check::, AES_ECB_128>("AES_ECB_128", _128bit); - check::, AES_ECB_192>( + check::, AES_ECB_192<'_, Decrypting, NoPadding>>( + "AES_ECB_192", _192bit, + ); + check::, AES_ECB_192<'_, Decrypting, PKCS7>>( "AES_ECB_192", _192bit, ); - check::, AES_ECB_192>("AES_ECB_192", _192bit); - check::, AES_ECB_256>( + check::, AES_ECB_256<'_, Decrypting, NoPadding>>( + "AES_ECB_256", _256bit, + ); + check::, AES_ECB_256<'_, Decrypting, PKCS7>>( "AES_ECB_256", _256bit, ); - check::, AES_ECB_256>("AES_ECB_256", _256bit); } #[test] fn cfb_cfb8_ctr() { use SecurityStrength::*; - check::, AES_CFB_128>("AES_CFB_128", _128bit); - check::, AES_CFB_192>("AES_CFB_192", _192bit); - check::, AES_CFB_256>("AES_CFB_256", _256bit); + check::, AES_CFB_128<'_, Decrypting>>("AES_CFB_128", _128bit); + check::, AES_CFB_192<'_, Decrypting>>("AES_CFB_192", _192bit); + check::, AES_CFB_256<'_, Decrypting>>("AES_CFB_256", _256bit); - check::, AES_CFB8_128>("AES_CFB8_128", _128bit); - check::, AES_CFB8_192>("AES_CFB8_192", _192bit); - check::, AES_CFB8_256>("AES_CFB8_256", _256bit); + check::, AES_CFB8_128<'_, Decrypting>>("AES_CFB8_128", _128bit); + check::, AES_CFB8_192<'_, Decrypting>>("AES_CFB8_192", _192bit); + check::, AES_CFB8_256<'_, Decrypting>>("AES_CFB8_256", _256bit); - check::, AES_CTR_128>("AES_CTR_128", _128bit); - check::, AES_CTR_192>("AES_CTR_192", _192bit); - check::, AES_CTR_256>("AES_CTR_256", _256bit); + check::, AES_CTR_128<'_, Decrypting>>("AES_CTR_128", _128bit); + check::, AES_CTR_192<'_, Decrypting>>("AES_CTR_192", _192bit); + check::, AES_CTR_256<'_, Decrypting>>("AES_CTR_256", _256bit); } #[test] fn gcm() { - type Enc = AES_GCM_128; + type Enc<'k> = AES_GCM_128<'k, Encrypting>; assert_eq!(Enc::ALG_NAME, "AES_GCM_128"); assert_eq!(Enc::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); use SecurityStrength::*; - check::, AES_GCM_128>("AES_GCM_128", _128bit); - check::, AES_GCM_192>("AES_GCM_192", _192bit); - check::, AES_GCM_256>("AES_GCM_256", _256bit); + check::, AES_GCM_128<'_, Decrypting>>("AES_GCM_128", _128bit); + check::, AES_GCM_192<'_, Decrypting>>("AES_GCM_192", _192bit); + check::, AES_GCM_256<'_, Decrypting>>("AES_GCM_256", _256bit); } #[test] @@ -102,16 +118,16 @@ fn ccm() { const N: usize = CCM_NONCE_LEN; const T: usize = CCM_TAG_LEN; // The nonce and tag lengths are parameters too, not part of the name. - check::, AES_CCM_128>( + check::, AES_CCM_128<'_, Decrypting, 7, 4, 16, 32>>( "AES_CCM_128", _128bit, ); - check::, AES_CCM_128>( + check::, AES_CCM_128<'_, Decrypting, N, T, 16, 32>>( "AES_CCM_128", _128bit, ); - check::, AES_CCM_192>( + check::, AES_CCM_192<'_, Decrypting, N, T, 16, 32>>( "AES_CCM_192", _192bit, ); - check::, AES_CCM_256>( + check::, AES_CCM_256<'_, Decrypting, N, T, 16, 32>>( "AES_CCM_256", _256bit, ); } @@ -119,9 +135,9 @@ fn ccm() { #[test] fn gmac() { use SecurityStrength::*; - check::, AES_GMAC_128>("AES_GMAC_128", _128bit); - check::, AES_GMAC_192>("AES_GMAC_192", _192bit); - check::, AES_GMAC_256>("AES_GMAC_256", _256bit); + check::, AES_GMAC_128<'_, Decrypting>>("AES_GMAC_128", _128bit); + check::, AES_GMAC_192<'_, Decrypting>>("AES_GMAC_192", _192bit); + check::, AES_GMAC_256<'_, Decrypting>>("AES_GMAC_256", _256bit); } #[test] @@ -141,7 +157,14 @@ fn cmac() { ); } -/// The aliases must resolve to exactly the types they claim to, at both directions. +fn key_for>() -> AES128Key { + let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) + .expect("a 16-byte symmetric cipher key"); + AES128Key::from_key_material(&key).expect("a valid key") +} + +/// The aliases must resolve to exactly the types they claim to, at both directions, and be what +/// the keys hand out. /// /// A type alias that quietly resolved to something else -- the wrong padding, the wrong direction, /// the wrong key length -- would still compile everywhere it is used, so this pins the projection @@ -150,44 +173,51 @@ fn cmac() { #[test] fn the_aliases_name_the_expected_types() { assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>, - >() + size_of::>(), + size_of::, PKCS7, 16, 16>>( + ) ); assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 16, 16>, - >() + size_of::>(), + size_of::, PKCS7, 16, 16>>( + ) ); // The two directions are genuinely different types, so the encryptor and the decryptor do not // have to agree in size -- and here they do not, which is itself evidence the projection // selected two different adapters rather than one. assert_ne!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 0, 16>, - >() + size_of::>(), + size_of::, PKCS7, 0, 16>>( + ) ); assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 0, 16>, - >() + size_of::>(), + size_of::, PKCS7, 0, 16>>( + ) ); assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); assert_eq!( - size_of::>(), - size_of::>() - ); + size_of::>(), + size_of::>() + ); + + // ...and the keys hand out exactly those types. + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = key_for::>(); + let (enc, iv) = key.create_encryptor(&mut rng).unwrap(); + let _: AES_CBC_128<'_, Encrypting, PKCS7> = enc; + let _: AES_CBC_128<'_, Decrypting, PKCS7> = key.create_decryptor(&iv); + let key = key_for::>(); + let (enc, no_iv) = key.create_encryptor(&mut rng).unwrap(); + let _: AES_ECB_128<'_, Encrypting, PKCS7> = enc; + let _: AES_ECB_128<'_, Decrypting, PKCS7> = key.create_decryptor(&no_iv); } diff --git a/crypto/aes/tests/cbc_bc-test-data.rs b/crypto/aes/tests/cbc_bc-test-data.rs index 686067658..28d2554e5 100644 --- a/crypto/aes/tests/cbc_bc-test-data.rs +++ b/crypto/aes/tests/cbc_bc-test-data.rs @@ -21,7 +21,7 @@ //! 2150 AFT (Algorithm Functional Test) cases across all three key lengths and both directions, //! including 60 whose payload spans 2 to 10 blocks. Every case is run **twice**: once block by //! block, and once in pairs with a one-block remainder for odd lengths. The second pass is what -//! puts the multi-block cases through `ElectronicCodeBook::decrypt_2blocks`, so the pair path is +//! puts the multi-block cases through `BlockPermutation::decrypt_2blocks`, so the pair path is //! exercised against real vectors and not only against the toy in //! `crypto/cipher/tests/modes/cbc_tests.rs`. //! @@ -30,11 +30,9 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cbc; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{Blocks, CBC}; +use bouncycastle_core::symmetric_key::{BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use bouncycastle_rng::FixedSeedRNG; @@ -59,12 +57,12 @@ enum Grouping { Pairs, } -/// Runs one CBC case in one direction, for a given permutation, under the given grouping. +/// Runs one CBC case in one direction, for a given key type, under the given 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( +/// Encryption is driven through the key's `create_encryptor` 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( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[[u8; BLOCK_LEN]], @@ -72,17 +70,16 @@ fn run_case( grouping: Grouping, ) -> Vec<[u8; BLOCK_LEN]> where - P: ElectronicCodeBook + ModeNames, + K: FromKeyMaterial + + BlockEncryptionKey + + BlockDecryptionKey, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)).expect("a valid key"); let mut out: Vec<[u8; BLOCK_LEN]> = Vec::with_capacity(input.len()); if encrypt { - let (mut enc, got_iv) = Cbc::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::new(iv), - ) - .expect("encrypt init"); + let (mut enc, got_iv) = + key.create_encryptor(&mut FixedSeedRNG::new(iv)).expect("encrypt init"); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the vector's IV"); match grouping { @@ -108,8 +105,7 @@ where } } } else { - let mut dec = - Cbc::::do_decrypt_init(&key, &iv).expect("dec init"); + let mut dec = key.create_decryptor(&iv); match grouping { Grouping::Single => { @@ -147,9 +143,9 @@ 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::>, 16>(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::>, 24>(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::>, 32>(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 51b6e0eb3..8f8fe0451 100644 --- a/crypto/aes/tests/cbc_wycheproof.rs +++ b/crypto/aes/tests/cbc_wycheproof.rs @@ -9,10 +9,11 @@ //! //! The file's "PKCS #5" is the padding of RFC 5652 Sec 6.3 (`k - (lth mod k)` octets of value //! `k - (lth mod k)`), which the cipher crate provides as [`PKCS7`]; the two names differ only in -//! that PKCS #5 was written for 8-byte blocks. So these vectors drive the padded aliases -//! `AES_CBC_128<_, PKCS7>` and friends, i.e. CBC through the `PaddedBlockCipherEncryptor` / -//! `PaddedBlockCipherDecryptor` adapters, where `cbc_bc-test-data.rs` and `sp800_38a_cbc_tests.rs` -//! drive the unpadded [`Cbc`](bouncycastle_cipher::modes::Cbc) underneath them. +//! that PKCS #5 was written for 8-byte blocks. So these vectors drive `AES128Key>` and +//! friends, whose workers are the padded aliases `AES_CBC_128<'_, _, PKCS7>`, i.e. CBC through the +//! `PaddedBlockCipherEncryptor` / `PaddedBlockCipherDecryptor` adapters, where +//! `cbc_bc-test-data.rs` and `sp800_38a_cbc_tests.rs` drive the unpadded +//! [`Cbc`](bouncycastle_cipher::modes::Cbc) underneath them through `CBC`. //! //! # Why this set is worth having alongside the ACVP one //! @@ -26,13 +27,16 @@ //! The IV is supplied through a `FixedSeedRNG`, as in `cbc_bc-test-data.rs`, and the returned IV is //! asserted to be the vector's. -use bouncycastle_aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CBC; 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::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; use bouncycastle_rng::FixedSeedRNG; @@ -69,15 +73,15 @@ enum Expected { NoFinalBlock, } -/// Runs one case through the padded `AES_CBC_*<_, PKCS7>` pair at one key length. +/// Runs one case through the `AES*Key>` key at one key length. /// /// For a valid case, `msg` must encrypt to exactly `expected_ct` under the vector's IV, and /// `expected_ct` must decrypt back to `msg`. For an invalid case only the decrypt direction is /// checked -- re-encrypting `msg` with correct padding has no reason to reproduce a deliberately /// 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( +/// output buffer zeroized: CBC writes plaintext before it reaches the padding, and the key's +/// `decrypt_out` must wipe it when the final block does not unpad. +fn run_case( tc_id: u64, key_bytes: &[u8], iv: [u8; BLOCK_LEN], @@ -85,14 +89,21 @@ fn run_case( expected_ct: &[u8], expected: Expected, ) where - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)) + .unwrap_or_else(|e| panic!("tcId {tc_id}: the key should be accepted: {e:?}")); if expected == Expected::Valid { - let mut ct = vec![0u8; E::encrypt_out_len(msg.len())]; - let (got_iv, written) = E::encrypt_rng_out(&key, &mut FixedSeedRNG::new(iv), msg, &mut ct) + let ct_len = + as SymmetricCipherEncryptor>::encrypt_out_len( + msg.len(), + ); + let mut ct = vec![0u8; ct_len]; + let (written, got_iv) = key + .encrypt_out(&mut FixedSeedRNG::new(iv), msg, &mut ct) .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); assert_eq!(got_iv, iv, "tcId {tc_id}: the seeded RNG must reproduce the vector's IV"); ct.truncate(written); @@ -101,8 +112,12 @@ 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; D::decrypt_out_len(expected_ct.len())]; - let outcome = D::decrypt_out(&key, &iv, expected_ct, &mut plaintext); + let pt_len = + as SymmetricCipherDecryptor>::decrypt_out_len( + expected_ct.len(), + ); + let mut plaintext = vec![0xEEu8; pt_len]; + let outcome = key.decrypt_out(&iv, expected_ct, &mut plaintext); if outcome.is_err() { assert!( plaintext.iter().all(|&b| b == 0), @@ -128,7 +143,7 @@ fn run_case( } } -/// Dispatches on the key length to the matching `AES_CBC_*` alias pair. +/// Dispatches on the key length to the matching `AES*Key>`. fn dispatch( tc_id: u64, key_bytes: &[u8], @@ -138,15 +153,15 @@ 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::>, 16>(tc_id, key_bytes, iv, msg, expected_ct, expected) + } + 24 => { + run_case::>, 24>(tc_id, key_bytes, iv, msg, expected_ct, expected) + } + 32 => { + run_case::>, 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 f7a98dfb7..bd5e705c6 100644 --- a/crypto/aes/tests/ccm_bc-test-data.rs +++ b/crypto/aes/tests/ccm_bc-test-data.rs @@ -48,7 +48,9 @@ use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::{CCM, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::{AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook}; +use bouncycastle_core::hazmat::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, +}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use std::collections::BTreeMap; diff --git a/crypto/aes/tests/ccm_key_tests.rs b/crypto/aes/tests/ccm_key_tests.rs index 0781844c0..8776eb42c 100644 --- a/crypto/aes/tests/ccm_key_tests.rs +++ b/crypto/aes/tests/ccm_key_tests.rs @@ -1,17 +1,20 @@ -//! The mode-bound CCM key: the packet role traits, whose constructors take the AAD and payload +//! The mode-bound CCM key: the declared-length role traits, whose constructors take the AAD and payload //! lengths, their one-shots at any length, and agreement with the generic-trait pair. //! //! Only the worker traits are in scope here, since the pair's cipher-trait methods share their //! names. -use bouncycastle_aes::{AES_CCM_128, AES128Key, AES192Key}; -use bouncycastle_cipher::Encrypting; +use bouncycastle_aes::hazmat::AES128Internal; +use bouncycastle_aes::{AES128Key, AES192Key}; use bouncycastle_cipher::modes::CCM; use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::hazmat::{AeadDeclaredEncryptMode, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::symmetric_key::{ - AeadDecryptWorker, AeadEncryptWorker, AeadPacketDecryptionKey, AeadPacketEncryptionKey, - FromKeyMaterial, + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial, +}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_rng::FixedSeedRNG; @@ -31,15 +34,12 @@ fn message(len: usize) -> Vec { (0..len).map(|i| (i * 31 % 251) as u8).collect() } -/// The pair's any-length `_nonce` one-shot, the reference for every form below. +/// The mode's in-place `_nonce` one-shot over the bare permutation (the hazmat mode-trait +/// route), the reference for every form below. fn reference(km: &KeyMaterial<16>, nonce: &[u8; 12], msg: &[u8]) -> (Vec, [u8; 16]) { - use bouncycastle_core::traits::AEADCipherEncryptor; - let mut ct = vec![0u8; msg.len()]; - let (n, tag) = AES_CCM_128::::encrypt_detached_nonce_out( - km, nonce, AAD, msg, &mut ct, - ) - .unwrap(); - assert_eq!(n, msg.len()); + let perm = AES128Internal::new(km).unwrap(); + let mut ct = msg.to_vec(); + let tag = CCM::<12, 16>::encrypt_detached_in_place_nonce(&perm, nonce, AAD, &mut ct).unwrap(); (ct, tag) } @@ -180,12 +180,9 @@ fn a_short_nonce_is_reachable_through_the_supplied_nonce_forms() { let mut buf = msg.clone(); let tag = key.encrypt_detached_in_place_nonce(&nonce, AAD, &mut buf).unwrap(); let (ct, tag_p) = { - use bouncycastle_core::traits::AEADCipherEncryptor; - let mut ct = vec![0u8; msg.len()]; - let (_, t) = AES_CCM_128::::encrypt_detached_nonce_out( - &km, &nonce, AAD, &msg, &mut ct, - ) - .unwrap(); + let perm = AES128Internal::new(&km).unwrap(); + let mut ct = msg.clone(); + let t = CCM::<8, 8>::encrypt_detached_in_place_nonce(&perm, &nonce, AAD, &mut ct).unwrap(); (ct, t) }; assert_eq!((ct, tag_p), (buf.clone(), tag)); @@ -193,17 +190,58 @@ fn a_short_nonce_is_reachable_through_the_supplied_nonce_forms() { assert_eq!(buf, msg); } -/// The workers are the mode's packet state, 264/296/328 B for AES-128/192/256, with the -/// decryptor adding the tag it holds back and that tag's length; the aes docs quote these. +/// The workers are the mode's `Ccm` state over a borrowed schedule, 96 B at every key length, +/// with the decryptor adding the tag it holds back and that tag's length; the aes docs quote +/// these. #[test] fn worker_sizes_match_the_documented_memory_table() { use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; - use bouncycastle_cipher::modes::{CcmPacketDecryptor, CcmPacketEncryptor}; + use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor}; use core::mem::size_of; - assert_eq!(size_of::>(), 264); - assert_eq!(size_of::>(), 296); - assert_eq!(size_of::>(), 328); - assert_eq!(size_of::>(), 288); - assert_eq!(size_of::>(), 320); - assert_eq!(size_of::>(), 352); + assert_eq!(size_of::>(), 96); + assert_eq!(size_of::>(), 96); + assert_eq!(size_of::>(), 96); + assert_eq!(size_of::>(), 120); + assert_eq!(size_of::>(), 120); + assert_eq!(size_of::>(), 120); +} + +/// A refused `do_encrypt_out` / `do_decrypt_out` leaves nothing of the input in the caller's +/// buffer, and zeroes the buffer first as every `_out` method must: payload offered while declared +/// AAD is still owed, and payload past the declared length, on both sides. +#[test] +fn a_refused_update_leaves_no_payload_in_the_output_buffer() { + use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; + + let key = AES128Key::>::from_key_material(&material::<16>()).unwrap(); + let nonce = [0x11u8; 12]; + let payload = [0x5Au8; 16]; + + // AAD still owed: the four bytes declared have not been supplied. + let mut enc = key.create_encryptor_nonce(&nonce, 4, 16).unwrap(); + let mut out = [0xEEu8; 32]; + assert!(matches!( + enc.do_encrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "a refused encrypt must leave only zeros behind"); + + // Past the declared payload length. + let mut enc = key.create_encryptor_nonce(&nonce, 0, 8).unwrap(); + let mut out = [0xEEu8; 32]; + assert!(matches!( + enc.do_encrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "an over-length encrypt must leave only zeros behind"); + + // The decryptor, with AAD still owed: the sixteen bytes are within the declared payload, so + // they are not held back as a tag, and must not be copied either. + let mut dec = key.create_decryptor(&nonce, 4, 16).unwrap(); + let mut out = [0xEEu8; 32]; + assert!(matches!( + dec.do_decrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "a refused decrypt must leave only zeros behind"); } diff --git a/crypto/aes/tests/ccm_wycheproof.rs b/crypto/aes/tests/ccm_wycheproof.rs index f44694edf..1c12afd6a 100644 --- a/crypto/aes/tests/ccm_wycheproof.rs +++ b/crypto/aes/tests/ccm_wycheproof.rs @@ -37,7 +37,7 @@ use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::modes::{CCM, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::{ - AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook, do_hazardous_operations, + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, do_hazardous_operations, }; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; diff --git a/crypto/aes/tests/cfb8_bc-test-data.rs b/crypto/aes/tests/cfb8_bc-test-data.rs index 0c10072ab..60ba0c850 100644 --- a/crypto/aes/tests/cfb8_bc-test-data.rs +++ b/crypto/aes/tests/cfb8_bc-test-data.rs @@ -23,7 +23,7 @@ //! that reach the batch paths. Every case is run **four times**: as one call over the whole //! payload, byte by byte, in 8-byte calls, and in 3-byte calls that never line up with the //! 8-byte batch. Between them those put the multi-byte cases through -//! [`ElectronicCodeBook::encrypt_4blocks`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the +//! [`BlockPermutation::encrypt_4blocks`] and [`BlockPermutation::encrypt_2blocks`] -- the //! *forward* function, even on the decrypt side -- and through the single-byte path, with the //! shift register carried across calls at every alignment. So all of that is exercised against real //! vectors and not only against the toys in `crypto/cipher/tests/modes/cfb8_tests.rs`. @@ -33,15 +33,12 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cfb8; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CFB8; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use bouncycastle_rng::FixedSeedRNG; use std::collections::BTreeMap; @@ -81,12 +78,12 @@ impl Grouping { } } -/// Runs one CFB8 case in one direction, for a given permutation, under the given grouping. +/// Runs one CFB8 case in one direction, for a given key type, under the given 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( +/// Encryption is driven through the key's `create_encryptor` 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( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[u8], @@ -94,25 +91,26 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: 'static, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)).expect("a valid key"); 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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .expect("encrypt init"); + let (mut enc, got_iv) = + key.create_encryptor(&mut FixedSeedRNG::new(iv)).expect("encrypt init"); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the vector's IV"); for piece in data.chunks_mut(chunk) { enc.do_encrypt_inplace(piece).unwrap(); } } else { - let mut dec = Cfb8::::do_decrypt_init(&key, &iv) - .expect("dec init"); + let mut dec = key.create_decryptor(&iv); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); } @@ -130,9 +128,9 @@ 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::, 16>(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::, 24>(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::, 32>(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 ffaf49fd9..ee5b934af 100644 --- a/crypto/aes/tests/cfb_bc-test-data.rs +++ b/crypto/aes/tests/cfb_bc-test-data.rs @@ -25,7 +25,7 @@ //! block, in pairs with a one-block remainder for odd lengths, as one call over the whole payload, //! and in 5-byte calls that never line up with a block. The second and third passes are what put //! the multi-block cases through the pair and four-block paths -- which for CFB are -//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_4blocks`], the +//! [`BlockPermutation::encrypt_2blocks`] and [`BlockPermutation::encrypt_4blocks`], the //! *forward* function, even on the decrypt side -- and the fourth is what puts them through the //! byte path with segments left open between calls. So all of that is exercised against real //! vectors and not only against the toys in `crypto/cipher/tests/modes/cfb_tests.rs`. Every ACVP @@ -37,15 +37,12 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cfb; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CFB; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use bouncycastle_rng::FixedSeedRNG; use std::collections::BTreeMap; @@ -86,12 +83,12 @@ impl Grouping { } } -/// Runs one CFB128 case in one direction, for a given permutation, under the given grouping. +/// Runs one CFB128 case in one direction, for a given key type, under the given 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( +/// Encryption is driven through the key's `create_encryptor` 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( key_bytes: &[u8], iv: [u8; BLOCK_LEN], input: &[u8], @@ -99,25 +96,26 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: 'static, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)).expect("a valid key"); 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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .expect("encrypt init"); + let (mut enc, got_iv) = + key.create_encryptor(&mut FixedSeedRNG::new(iv)).expect("encrypt init"); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the vector's IV"); for piece in data.chunks_mut(chunk) { enc.do_encrypt_inplace(piece).unwrap(); } } else { - let mut dec = - Cfb::::do_decrypt_init(&key, &iv).expect("dec init"); + let mut dec = key.create_decryptor(&iv); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); } @@ -135,9 +133,9 @@ 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::, 16>(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::, 24>(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::, 32>(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/cmac_bc-test-data.rs b/crypto/aes/tests/cmac_bc-test-data.rs index d716e2074..df2a90df9 100644 --- a/crypto/aes/tests/cmac_bc-test-data.rs +++ b/crypto/aes/tests/cmac_bc-test-data.rs @@ -7,8 +7,9 @@ //! The set includes 8- and 56-bit tags, below the `CMAC_MIN_TAG_LEN` that `mac_out` will truncate //! to, so every case is computed as a full tag and compared on its first `macLen` bits. -use bouncycastle_aes::{AES_CMAC_128, AES_CMAC_192, AES_CMAC_256}; -use bouncycastle_core::traits::MAC; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CMAC; +use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use std::collections::BTreeMap; @@ -23,19 +24,19 @@ const RESPONSE_FILE: &str = "CMAC-AES.4014544.rsp.json"; /// The full 16-byte AES-CMAC tag of `msg` under `key`, dispatched on the key length. fn aes_cmac(key: &[u8], msg: &[u8]) -> Vec { match key.len() { - 16 => AES_CMAC_128::new(&mac_key::<16>(key)).unwrap().mac(msg), - 24 => AES_CMAC_192::new(&mac_key::<24>(key)).unwrap().mac(msg), - 32 => AES_CMAC_256::new(&mac_key::<32>(key)).unwrap().mac(msg), + 16 => AES128Key::::from_key_material(&mac_key::<16>(key)).unwrap().mac(msg), + 24 => AES192Key::::from_key_material(&mac_key::<24>(key)).unwrap().mac(msg), + 32 => AES256Key::::from_key_material(&mac_key::<32>(key)).unwrap().mac(msg), other => panic!("unexpected AES key length {other}"), } } -/// `MAC::verify` of a full-length tag, dispatched on the key length. +/// The key's `verify` of a full-length tag, dispatched on the key length. fn aes_cmac_verify(key: &[u8], msg: &[u8], tag: &[u8]) -> bool { match key.len() { - 16 => AES_CMAC_128::new(&mac_key::<16>(key)).unwrap().verify(msg, tag), - 24 => AES_CMAC_192::new(&mac_key::<24>(key)).unwrap().verify(msg, tag), - 32 => AES_CMAC_256::new(&mac_key::<32>(key)).unwrap().verify(msg, tag), + 16 => AES128Key::::from_key_material(&mac_key::<16>(key)).unwrap().verify(msg, tag), + 24 => AES192Key::::from_key_material(&mac_key::<24>(key)).unwrap().verify(msg, tag), + 32 => AES256Key::::from_key_material(&mac_key::<32>(key)).unwrap().verify(msg, tag), other => panic!("unexpected AES key length {other}"), } } diff --git a/crypto/aes/tests/cmac_key_tests.rs b/crypto/aes/tests/cmac_key_tests.rs index 74e2ddc9c..6f14fe67f 100644 --- a/crypto/aes/tests/cmac_key_tests.rs +++ b/crypto/aes/tests/cmac_key_tests.rs @@ -1,9 +1,11 @@ //! `AES*Key`: the key object holding the subkeys, and the workers it hands out producing -//! exactly what `MAC::new` produces from the same material. +//! exactly what the mode's own route produces over a `CmacKeyState` built from the same bytes. +use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_aes::{AES_CMAC_128, AES_CMAC_192, AES_CMAC_256, AES128Key, AES192Key, AES256Key}; use bouncycastle_cipher::modes::{CMAC, CmacKeyState}; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; +use bouncycastle_core::hazmat::{ElectronicCodeBook, MacMode, Mode}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; @@ -15,6 +17,13 @@ fn mac_key(fill: u8) -> KeyMaterial { KeyMaterial::::from_bytes_as_type(&[fill; N], KeyType::MACKey).expect("a MAC key") } +/// The same bytes as [`mac_key`] tagged as the permutation itself takes them, for the bare key +/// state the key object is compared against. +fn cipher_key(fill: u8) -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&[fill; N], KeyType::SymmetricCipherKey) + .expect("a cipher key") +} + const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; /// NIST CSRC AES_CMAC.pdf Example #3 (Mlen = 20), AES-128: the key object reproduces a published @@ -32,23 +41,29 @@ fn the_key_object_gives_the_csrc_answer() { } #[test] -fn the_key_and_the_worker_constructor_agree_at_every_key_length() { +fn the_key_and_the_bare_key_state_agree_at_every_key_length() { + let state_16 = >::key_state( + AES128Internal::new(&cipher_key::<16>(0x0b)).unwrap(), + ); + let state_24 = >::key_state( + AES192Internal::new(&cipher_key::<24>(0x0c)).unwrap(), + ); + let state_32 = >::key_state( + AES256Internal::new(&cipher_key::<32>(0x0d)).unwrap(), + ); for len in [0usize, 1, 15, 16, 17, 43] { let msg = &MSG[..len]; - let k16 = mac_key::<16>(0x0b); assert_eq!( - AES128Key::::from_key_material(&k16).unwrap().mac(msg), - AES_CMAC_128::new(&k16).unwrap().mac(msg) + AES128Key::::from_key_material(&mac_key::<16>(0x0b)).unwrap().mac(msg), + >::mac_init(&state_16).mac(msg) ); - let k24 = mac_key::<24>(0x0c); assert_eq!( - AES192Key::::from_key_material(&k24).unwrap().mac(msg), - AES_CMAC_192::new(&k24).unwrap().mac(msg) + AES192Key::::from_key_material(&mac_key::<24>(0x0c)).unwrap().mac(msg), + >::mac_init(&state_24).mac(msg) ); - let k32 = mac_key::<32>(0x0d); assert_eq!( - AES256Key::::from_key_material(&k32).unwrap().mac(msg), - AES_CMAC_256::new(&k32).unwrap().mac(msg) + AES256Key::::from_key_material(&mac_key::<32>(0x0d)).unwrap().mac(msg), + >::mac_init(&state_32).mac(msg) ); } } @@ -127,6 +142,16 @@ fn sizes() { ); assert_eq!( [size_of::(), size_of::(), size_of::()], - [248, 280, 312] + [48, 48, 48] ); } + +/// Equality follows the material through the CMAC state, subkeys included: the same bytes +/// compare equal, as does a clone, and different bytes do not. +#[test] +fn cmac_keys_compare_equal_exactly_when_their_material_does() { + let key = AES128Key::::from_key_material(&mac_key::<16>(0x2b)).unwrap(); + assert!(key == key.clone()); + assert!(key == AES128Key::::from_key_material(&mac_key::<16>(0x2b)).unwrap()); + assert!(key != AES128Key::::from_key_material(&mac_key::<16>(0x2c)).unwrap()); +} diff --git a/crypto/aes/tests/cmac_tests.rs b/crypto/aes/tests/cmac_tests.rs index fe6aca250..97cc2f9d6 100644 --- a/crypto/aes/tests/cmac_tests.rs +++ b/crypto/aes/tests/cmac_tests.rs @@ -1,15 +1,17 @@ -//! `AES_CMAC_*` against the NIST CSRC AES-CMAC examples through the MAC trait test framework, -//! plus the streaming split points, key validation and suspend/resume over the real cipher. The -//! structural tests are in `bouncycastle-cipher` over a toy; the large vector sets are in -//! `cmac_bc-test-data.rs` and `cmac_wycheproof.rs`. +//! `AES*Key` against the NIST CSRC AES-CMAC examples through the MAC trait test framework, +//! plus the streaming split points, the key's construction policy and suspend/resume over the +//! real cipher. The structural tests are in `bouncycastle-cipher` over a toy; the large vector +//! sets are in `cmac_bc-test-data.rs` and `cmac_wycheproof.rs`. -use bouncycastle_aes::{AES_CMAC_128, AES_CMAC_192, AES_CMAC_256}; -use bouncycastle_cipher::modes::CMAC_MIN_TAG_LEN; -use bouncycastle_core::errors::{KeyMaterialError, MACError}; -use bouncycastle_core::hazmat::do_hazardous_operations; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial128, KeyMaterialTrait, KeyType}; +use bouncycastle_aes::hazmat::{AES128Internal, AES256Internal}; +use bouncycastle_aes::{AES_CMAC_128, AES_CMAC_256, AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{CMAC, CMAC_MIN_TAG_LEN, ModeNames}; +use bouncycastle_core::hazmat::{ModeKey, do_hazardous_operations}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{Algorithm, AlgorithmInfo, MAC, SuspendableKeyed}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; @@ -62,26 +64,17 @@ const TAGS_256: [&str; 4] = [ fn csrc_examples() { let msg = hex::decode(MSG).unwrap(); let tf = TestFrameworkMAC::new(); + let key_128 = AES128Key::::from_key_material(&mac_key::<16>(KEY_128)).unwrap(); for (mlen, tag) in MLENS.iter().zip(TAGS_128) { - tf.test_mac::( - &mac_key::<16>(KEY_128), - &msg[..*mlen], - &hex::decode(tag).unwrap(), - ); + tf.test_mac_key(&key_128, &msg[..*mlen], &hex::decode(tag).unwrap()); } + let key_192 = AES192Key::::from_key_material(&mac_key::<24>(KEY_192)).unwrap(); for (mlen, tag) in MLENS.iter().zip(TAGS_192) { - tf.test_mac::( - &mac_key::<24>(KEY_192), - &msg[..*mlen], - &hex::decode(tag).unwrap(), - ); + tf.test_mac_key(&key_192, &msg[..*mlen], &hex::decode(tag).unwrap()); } + let key_256 = AES256Key::::from_key_material(&mac_key::<32>(KEY_256)).unwrap(); for (mlen, tag) in MLENS.iter().zip(TAGS_256) { - tf.test_mac::( - &mac_key::<32>(KEY_256), - &msg[..*mlen], - &hex::decode(tag).unwrap(), - ); + tf.test_mac_key(&key_256, &msg[..*mlen], &hex::decode(tag).unwrap()); } } @@ -89,90 +82,57 @@ fn csrc_examples() { /// held-back final block must come out the same wherever the call boundaries fall. #[test] fn streaming_split_points() { - let key = mac_key::<16>(KEY_128); + let key = AES128Key::::from_key_material(&mac_key::<16>(KEY_128)).unwrap(); let msg = hex::decode(MSG).unwrap(); for mlen in [20, 32, 33, 64] { - let expected = AES_CMAC_128::new(&key).unwrap().mac(&msg[..mlen]); + let expected = key.mac(&msg[..mlen]); for split in 0..=mlen { - let mut cmac = AES_CMAC_128::new(&key).unwrap(); + let mut cmac = key.create_generator(); cmac.do_update(&msg[..split]); cmac.do_update(&msg[split..mlen]); assert_eq!(cmac.do_final(), expected, "mlen {mlen}, split at {split}"); } - let mut cmac = AES_CMAC_128::new(&key).unwrap(); + let mut cmac = key.create_generator(); for b in &msg[..mlen] { cmac.do_update(core::slice::from_ref(b)); } assert_eq!(cmac.do_final(), expected, "mlen {mlen}, byte at a time"); } assert_eq!( - AES_CMAC_128::new(&key).unwrap().mac(&msg), + key.mac(&msg), hex::decode(TAGS_128[3]).unwrap(), "the 64-byte one-shot is the CSRC example" ); } +/// The key's construction suite at both ends of the key-length range: a `MACKey` of the right +/// length and strength, bound to the CMAC name or to nothing; a block cipher key is not a CMAC +/// key (SP 800-38B Sec 5.2, key separation). Wrong lengths are a compile-time matter on this path. #[test] -fn key_validation() { - // A block cipher key is not a CMAC key (SP 800-38B Sec 5.2, key separation). - let cipher_key = KeyMaterial128::from_bytes_as_type( - &hex::decode(KEY_128).unwrap(), - KeyType::SymmetricCipherKey, - ) - .unwrap(); - assert!(matches!( - AES_CMAC_128::new_allow_weak_key(&cipher_key), - Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) - )); - - // A full-strength MAC key of the wrong length. - let key_32 = mac_key::<32>(KEY_256); - for result in - [AES_CMAC_128::new(&key_32).err(), AES_CMAC_128::new_allow_weak_key(&key_32).err()] - { - assert_eq!(result, Some(MACError::KeyMaterialError(KeyMaterialError::InvalidLength))); - } - let key_16 = mac_key::<16>(KEY_128); - assert_eq!( - AES_CMAC_256::new_allow_weak_key(&key_16).err(), - Some(MACError::KeyMaterialError(KeyMaterialError::InvalidLength)) - ); - - // A 32-byte key tagged at 128 bits is too weak for AES-256, but accepted when asked. - let mut weak = mac_key::<32>(KEY_256); - weak.set_security_strength(SecurityStrength::_128bit).unwrap(); - assert!(matches!( - AES_CMAC_256::new(&weak), - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); - assert!(AES_CMAC_256::new_allow_weak_key(&weak).is_ok()); - - // The all-zero key is `KeyType::Zeroized`: weak, but a permitted type. - let zero = KeyMaterial128::from_bytes_as_type(&[0u8; 16], KeyType::MACKey).unwrap(); - assert_eq!(zero.key_type(), KeyType::Zeroized); - assert!(matches!( - AES_CMAC_128::new(&zero), - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); - assert!(AES_CMAC_128::new_allow_weak_key(&zero).is_ok()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::<16, AES128Key>(AES128Internal::CMAC_ALG_NAME, SecurityStrength::_128bit); + TestFrameworkFromKeyMaterial::new() + .test::<32, AES256Key>(AES256Internal::CMAC_ALG_NAME, SecurityStrength::_256bit); } #[test] fn truncation_to_the_minimum_tag() { - let key = mac_key::<16>(KEY_128); + let key = AES128Key::::from_key_material(&mac_key::<16>(KEY_128)).unwrap(); let msg = hex::decode(MSG).unwrap(); let full = hex::decode(TAGS_128[2]).unwrap(); let mut out = [0xAAu8; CMAC_MIN_TAG_LEN]; - assert_eq!(AES_CMAC_128::new(&key).unwrap().mac_out(&msg[..20], &mut out).unwrap(), 8); + assert_eq!(key.mac_out(&msg[..20], &mut out).unwrap(), 8); assert_eq!(out[..], full[..8]); - assert!(!AES_CMAC_128::new(&key).unwrap().verify(&msg[..20], &full[..8])); + assert!(!key.verify(&msg[..20], &full[..8])); } #[test] fn algorithm_params() { assert_eq!(AES_CMAC_128::ALG_NAME, "AES_CMAC_128"); assert_eq!(AES_CMAC_256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); - let cmac = AES_CMAC_192::new(&mac_key::<24>(KEY_192)).unwrap(); + let key = AES192Key::::from_key_material(&mac_key::<24>(KEY_192)).unwrap(); + let cmac = key.create_generator(); assert_eq!(cmac.output_len(), 16); assert_eq!(cmac.max_security_strength(), SecurityStrength::_192bit); assert_eq!(STATE_LEN, 36); @@ -182,15 +142,16 @@ fn algorithm_params() { /// one-shot tag. #[test] fn suspend_resume() { - let key = mac_key::<16>(KEY_128); + let material = mac_key::<16>(KEY_128); + let key = AES128Key::::from_key_material(&material).unwrap(); let msg = hex::decode(MSG).unwrap(); for (mlen, tag) in MLENS.iter().zip(TAGS_128) { let expected = hex::decode(tag).unwrap(); for split in 0..=*mlen { - let mut cmac = AES_CMAC_128::new(&key).unwrap(); + let mut cmac = key.create_generator(); cmac.do_update(&msg[..split]); let state: [u8; STATE_LEN] = cmac.suspend(); - let mut resumed = AES_CMAC_128::from_suspended(state, &key).unwrap(); + let mut resumed = AES_CMAC_128::from_suspended(state, key.state()).unwrap(); resumed.do_update(&msg[split..*mlen]); assert_eq!(resumed.do_final(), expected, "mlen {mlen}, suspended at {split}"); } diff --git a/crypto/aes/tests/cmac_wycheproof.rs b/crypto/aes/tests/cmac_wycheproof.rs index d3e438132..082623548 100644 --- a/crypto/aes/tests/cmac_wycheproof.rs +++ b/crypto/aes/tests/cmac_wycheproof.rs @@ -4,15 +4,11 @@ //! this repository, i.e. at `../wycheproof` relative to the root of this git project. If it is //! missing, the test prints a warning and passes without checking anything. //! -//! Every valid tag is also checked through the key object, `AES*Key`, so the two routes are -//! held to the same answers. +//! Every tag is checked through the worker an `AES*Key` hands out and through the key's own +//! one-shots, so the two routes are held to the same answers. -use bouncycastle_aes::{AES_CMAC_128, AES_CMAC_192, AES_CMAC_256, AES128Key, AES192Key, AES256Key}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; use bouncycastle_cipher::modes::{CMAC, CMAC_MIN_TAG_LEN}; -use bouncycastle_core::errors::{KeyMaterialError, MACError}; -use bouncycastle_core::hazmat::do_hazardous_operations; -use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::MAC; use bouncycastle_core_test_framework::test_data_loaders::{hex_field, wycheproof_json}; @@ -25,21 +21,17 @@ use cmac_helpers::mac_key; fn run_case(tc_id: u64, key: &[u8], msg: &[u8], tag: &[u8], valid: bool) where K: FromKeyMaterial + MacGenerationKey + MacVerificationKey, - ::Generator: MAC, + for<'k> ::Generator<'k>: MAC, { let material = mac_key::(key); - let full = ::Generator::new(&material).unwrap().mac(msg); + let key = K::from_key_material(&material).unwrap_or_else(|e| panic!("tcId {tc_id}: {e:?}")); + let full = key.create_generator().mac(msg); assert_eq!(full == tag, valid, "tcId {tc_id}"); - assert_eq!( - ::Generator::new(&material).unwrap().verify(msg, tag), - valid, - "tcId {tc_id}" - ); + assert_eq!(key.create_generator().verify(msg, tag), valid, "tcId {tc_id}"); - let key = K::from_key_material(&material).unwrap_or_else(|e| panic!("tcId {tc_id}: {e:?}")); - assert_eq!(key.verify(msg, tag), valid, "tcId {tc_id}: via the key object"); + assert_eq!(key.verify(msg, tag), valid, "tcId {tc_id}: via the key's one-shot"); if valid { - assert_eq!(key.mac(msg), tag, "tcId {tc_id}: via the key object"); + assert_eq!(key.mac(msg), tag, "tcId {tc_id}: via the key's one-shot"); let mut out = [0u8; CMAC_MIN_TAG_LEN]; key.mac_out(msg, &mut out).unwrap(); assert_eq!(out[..], tag[..CMAC_MIN_TAG_LEN], "tcId {tc_id}: truncated tag"); @@ -69,18 +61,9 @@ fn wycheproof_aes_cmac() { 24 => run_case::<24, AES192Key>(tc_id, &key, &msg, &tag, expect_valid), 32 => run_case::<32, AES256Key>(tc_id, &key, &msg, &tag, expect_valid), _ => { - // "InvalidKeySize": no AES-CMAC accepts it, even without the strength check. + // "InvalidKeySize": no `AES*Key` takes it, since the key length is in + // the `KeyMaterial<16 | 24 | 32>` type; nothing to run at runtime. assert!(!expect_valid, "tcId {tc_id}"); - let mut k = KeyMaterial512::new(); - do_hazardous_operations(&mut k, |k| { - k.set_bytes_as_type(&key, KeyType::MACKey)?; - k.set_security_strength(SecurityStrength::None) - }) - .unwrap(); - let bad_len = Some(MACError::KeyMaterialError(KeyMaterialError::InvalidLength)); - assert_eq!(AES_CMAC_128::new_allow_weak_key(&k).err(), bad_len); - assert_eq!(AES_CMAC_192::new_allow_weak_key(&k).err(), bad_len); - assert_eq!(AES_CMAC_256::new_allow_weak_key(&k).err(), bad_len); bad_key += 1; continue; } diff --git a/crypto/aes/tests/common/acvp_gcm_helpers.rs b/crypto/aes/tests/common/acvp_gcm_helpers.rs index 33bea858a..f92fb8f79 100644 --- a/crypto/aes/tests/common/acvp_gcm_helpers.rs +++ b/crypto/aes/tests/common/acvp_gcm_helpers.rs @@ -9,15 +9,12 @@ #![allow(dead_code)] -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Gcm; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::GCM; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{ - AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, -}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core::traits::{AEADCipherDecryptor, SymmetricCipherDecryptor}; use bouncycastle_rng::FixedSeedRNG; /// The nonce length these vectors use; every group in the ACVP AES-GCM/GMAC sets has `ivLen = 96`. @@ -41,46 +38,41 @@ pub fn run_encrypt_case( expected_tag: &[u8], ) { macro_rules! dispatch { - ($p:ty, $klen:literal) => {{ + ($k:ident, $klen:literal) => {{ let key = cipher_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::<$k>, $klen, 12>(&key, iv, aad, &mut data, expected_tag), + 13 => run_encrypt::<$k>, $klen, 13>(&key, iv, aad, &mut data, expected_tag), + 14 => run_encrypt::<$k>, $klen, 14>(&key, iv, aad, &mut data, expected_tag), + 15 => run_encrypt::<$k>, $klen, 15>(&key, iv, aad, &mut data, expected_tag), + 16 => run_encrypt::<$k>, $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!(AES128Key, 16), + 24 => dispatch!(AES192Key, 24), + 32 => dispatch!(AES256Key, 32), other => panic!("unexpected AES key length {other}"), } } -fn run_encrypt( +fn run_encrypt( key: &KeyMaterial, iv: [u8; GCM_NONCE_LEN], aad: &[u8], data: &mut [u8], expected_tag: &[u8], ) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: FromKeyMaterial + AeadEncryptionKey, { + let key = K::from_key_material(key).expect("a valid key"); 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 (written, got_iv, tag) = + key.encrypt_detached_out(&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,27 +91,27 @@ pub fn run_decrypt_case( expected_pt: Option<&[u8]>, ) { macro_rules! dispatch { - ($p:ty, $klen:literal) => {{ + ($k:ident, $klen:literal) => {{ let key = cipher_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::<$k>, $klen, 12>(&key, iv, aad, ct, tag, expected_pt), + 13 => run_decrypt::<$k>, $klen, 13>(&key, iv, aad, ct, tag, expected_pt), + 14 => run_decrypt::<$k>, $klen, 14>(&key, iv, aad, ct, tag, expected_pt), + 15 => run_decrypt::<$k>, $klen, 15>(&key, iv, aad, ct, tag, expected_pt), + 16 => run_decrypt::<$k>, $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!(AES128Key, 16), + 24 => dispatch!(AES192Key, 24), + 32 => dispatch!(AES256Key, 32), other => panic!("unexpected AES key length {other}"), } } -fn run_decrypt( +fn run_decrypt( key: &KeyMaterial, iv: [u8; GCM_NONCE_LEN], aad: &[u8], @@ -127,24 +119,20 @@ fn run_decrypt( tag: &[u8], expected_pt: Option<&[u8]>, ) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: FromKeyMaterial + AeadDecryptionKey, { + let key = K::from_key_material(key).expect("a valid key"); 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. + // The key's detached one-shot: AAD-capable, and its buffer is zeroized on a failed tag check. let mut data = vec![0xEEu8; ct.len()]; - let one_shot_result = Gcm::::decrypt_detached_out( - key, &iv, aad, ct, &tag_arr, &mut data, - ); + let one_shot_result = key.decrypt_detached_out(&iv, aad, ct, &tag_arr, &mut data); // The inline `SymmetricCipherDecryptor` streaming view, `ciphertext || tag` through - // `do_update_out`/`do_decrypt_final`, with AAD fed via `do_update_aad` first. Note this is - // *not* the AAD-less static `decrypt_out` one-shot (which has no AAD parameter at all, so it - // cannot run the cases that carry AAD, and most do): the streaming path is where the inline - // 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"); + // `do_update_out`/`do_decrypt_final`, with AAD fed via `do_update_aad` first; 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 = key.create_decryptor(&iv); 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/ctr_bc-test-data.rs b/crypto/aes/tests/ctr_bc-test-data.rs index 03d70761e..7ce86e415 100644 --- a/crypto/aes/tests/ctr_bc-test-data.rs +++ b/crypto/aes/tests/ctr_bc-test-data.rs @@ -37,15 +37,12 @@ //! `resultsArray` produced by a chained update rule defined in the ACVP AES specification rather //! than in SP 800-38A, and implementing it from anything else would be guesswork. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Ctr; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CTR; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; use bouncycastle_rng::FixedSeedRNG; use std::collections::BTreeMap; @@ -86,12 +83,12 @@ impl Grouping { } } -/// Runs one CTR case in one direction, for a given permutation, under the given grouping. +/// Runs one CTR case in one direction, for a given key type, under the given 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( +/// Encryption is driven through the key's `create_encryptor` with a `FixedSeedRNG` emitting the +/// vector's nonce, and the returned init data is checked against that nonce before any ciphertext +/// is compared -- so a change that ignored the RNG could not pass silently. +fn run_case( key_bytes: &[u8], nonce: [u8; NONCE_LEN], input: &[u8], @@ -99,27 +96,26 @@ fn run_case( grouping: Grouping, ) -> Vec where - P: ElectronicCodeBook + ModeNames, + K: 'static, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)).expect("a valid key"); 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( - &key, - &mut FixedSeedRNG::new(nonce), - ) - .expect("encrypt init"); + key.create_encryptor(&mut FixedSeedRNG::new(nonce)).expect("encrypt init"); assert_eq!(got_iv, nonce, "the pinned RNG should reproduce the vector's nonce"); for piece in data.chunks_mut(chunk) { enc.do_encrypt_inplace(piece).unwrap(); } } else { - let mut dec = - Ctr::::do_decrypt_init(&key, &nonce) - .expect("dec init"); + let mut dec = key.create_decryptor(&nonce); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); } @@ -137,9 +133,9 @@ 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::>, 16>(key_bytes, nonce, input, encrypt, grouping), + 24 => run_case::>, 24>(key_bytes, nonce, input, encrypt, grouping), + 32 => run_case::>, 32>(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 bef3a7abd..e71d6ca47 100644 --- a/crypto/aes/tests/ctr_bc_java_tests.rs +++ b/crypto/aes/tests/ctr_bc_java_tests.rs @@ -36,32 +36,32 @@ //! 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::hazmat::AES128Internal; -use bouncycastle_cipher::Encrypting; -use bouncycastle_cipher::modes::Ctr; +use bouncycastle_aes::AES128Key; +use bouncycastle_cipher::modes::CTR; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{StreamCipherEncryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricEncryptionKey}; +use bouncycastle_core::traits::StreamCipherEncryptor; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; /// 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> { +/// The key under CTR at the given nonce length. +fn key() -> AES128Key> { let raw = hex::decode(KEY).expect("valid hex"); - KeyMaterial::<16>::from_bytes_as_type(&raw, KeyType::SymmetricCipherKey).expect("a valid key") + let material = KeyMaterial::<16>::from_bytes_as_type(&raw, KeyType::SymmetricCipherKey) + .expect("a valid key"); + AES128Key::from_key_material(&material).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 key = key::(); + let (mut enc, got) = key.create_encryptor(&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 +151,8 @@ 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::new(nonce), - ) - .unwrap(); + let key = key::<15>(); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::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 c48129e97..29cade26f 100644 --- a/crypto/aes/tests/ctr_vector_tests.rs +++ b/crypto/aes/tests/ctr_vector_tests.rs @@ -26,16 +26,13 @@ //! the counter starting at zero, so the two line up exactly when the IV's low four bytes are zero, //! which is why the IV above ends in `00000000`. See the [`Ctr`] module docs. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Ctr; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CTR; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; @@ -100,11 +97,16 @@ fn key_material(hex_str: &str) -> KeyMaterial { /// 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: 'static, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); let nonce: [u8; NONCE_LEN] = unhex(NONCE).try_into().expect("a 12-byte nonce"); let plaintext = unhex(PLAINTEXT); let expected = unhex(expected_hex); @@ -114,11 +116,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( - &key, - &mut FixedSeedRNG::new(nonce), - ) - .expect("encrypt init"); + key.create_encryptor(&mut FixedSeedRNG::new(nonce)).expect("encrypt init"); assert_eq!(got, nonce, "{name}: the pinned RNG should reproduce the nonce"); let mut data = plaintext.clone(); @@ -130,9 +128,7 @@ where // Decryption, likewise. for chunk in [expected.len()].into_iter().chain(CHUNKINGS) { - let mut dec = - Ctr::::do_decrypt_init(&key, &nonce) - .expect("decrypt init"); + let mut dec = key.create_decryptor(&nonce); let mut data = expected.clone(); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).expect("decryption"); @@ -142,24 +138,23 @@ where // ...and the one-shot. let mut data = expected.clone(); - Ctr::::decrypt_inplace(&key, &nonce, &mut data) - .expect("one-shot decryption"); + key.decrypt_inplace(&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::>, 16>("AES-128", KEY_128, CT_128); } #[test] fn aes192_ctr_matches_openssl() { - check::("AES-192", KEY_192, CT_192); + check::>, 24>("AES-192", KEY_192, CT_192); } #[test] fn aes256_ctr_matches_openssl() { - check::("AES-256", KEY_256, CT_256); + check::>, 32>("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/dyn_worker_tests.rs b/crypto/aes/tests/dyn_worker_tests.rs index ca8c7ec46..78f3d6500 100644 --- a/crypto/aes/tests/dyn_worker_tests.rs +++ b/crypto/aes/tests/dyn_worker_tests.rs @@ -1,15 +1,17 @@ //! A worker lent out as a trait object for the streaming part of a job (issue #173): the owner //! keeps the concrete encryptor, hands `&mut dyn ...` to code that only needs to feed it, and -//! finishes it itself. No allocation, no `Box`, and the one-shot decrypts what came out. +//! finishes it itself. No allocation, no `Box`, and the key decrypts what came out. -use bouncycastle_aes::{AES_CBC_128, AES_GCM_128}; +use bouncycastle_aes::AES128Key; +use bouncycastle_cipher::modes::{CBC, GCM}; use bouncycastle_cipher::padding::PKCS7; -use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{ - AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +use bouncycastle_core::symmetric_key::{ + AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial, SymmetricDecryptionKey, + SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{AEADCipherEncryptor, SymmetricCipherEncryptor}; use bouncycastle_rng::FixedSeedRNG; const MSG: &[u8] = b"the quick brown fox jumps over the lazy dog, twice over, for two blocks"; @@ -20,7 +22,7 @@ fn key_material() -> KeyMaterial<16> { } /// Feeds `plaintext` in 7-byte pieces through the trait object and returns the ciphertext so far. -fn stream_through(enc: &mut dyn SymmetricCipherEncryptor<16, 16, 16>, plaintext: &[u8]) -> Vec { +fn stream_through(enc: &mut dyn SymmetricCipherEncryptor<16, 16>, plaintext: &[u8]) -> Vec { assert_eq!(enc.alg_name(), "AES_CBC_128"); assert_eq!(enc.max_security_strength(), SecurityStrength::_128bit); let mut out = Vec::new(); @@ -34,22 +36,21 @@ fn stream_through(enc: &mut dyn SymmetricCipherEncryptor<16, 16, 16>, plaintext: #[test] fn cbc_streams_through_a_trait_object() { - let key = key_material(); + let key = AES128Key::>::from_key_material(&key_material()).unwrap(); let mut rng = FixedSeedRNG::new([7u8; 16]); - let (mut enc, iv) = - AES_CBC_128::::do_encrypt_init_rng(&key, &mut rng).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut rng).unwrap(); let mut ct = stream_through(&mut enc, MSG); // The final is the concrete value's: `do_encrypt_final` takes `self`. let (last, n) = enc.do_encrypt_final().unwrap(); ct.extend_from_slice(&last[..n]); - assert_eq!(AES_CBC_128::::decrypt(&key, &iv, &ct).unwrap(), MSG); + assert_eq!(key.decrypt(&iv, &ct).unwrap(), MSG); } /// The AAD and the plaintext go through the trait object; the tag comes from the concrete final. fn aad_and_stream_through( - enc: &mut dyn AEADCipherEncryptor<16, 12, 16, 16>, + enc: &mut dyn AEADCipherEncryptor<12, 16, 16>, aad: &[u8], plaintext: &[u8], ) -> Vec { @@ -67,20 +68,14 @@ fn aad_and_stream_through( #[test] fn gcm_streams_through_a_trait_object() { - let key = key_material(); + let key = AES128Key::::from_key_material(&key_material()).unwrap(); let mut rng = FixedSeedRNG::new([9u8; 12]); - let (mut enc, nonce) = AES_GCM_128::::do_encrypt_init_rng(&key, &mut rng).unwrap(); + let (mut enc, nonce) = key.create_encryptor(&mut rng).unwrap(); let mut ct = aad_and_stream_through(&mut enc, b"header", MSG); let (last, n) = enc.do_encrypt_final().unwrap(); ct.extend_from_slice(&last[..n]); - assert_eq!( - AES_GCM_128::::decrypt_with_aad(&key, &nonce, b"header", &ct).unwrap(), - MSG - ); - assert!( - AES_GCM_128::::decrypt_with_aad(&key, &nonce, b"other", &ct).is_err(), - "the AAD was bound" - ); + assert_eq!(key.decrypt_with_aad(&nonce, b"header", &ct).unwrap(), MSG); + assert!(key.decrypt_with_aad(&nonce, b"other", &ct).is_err(), "the AAD was bound"); } diff --git a/crypto/aes/tests/ecb_bc-test-data.rs b/crypto/aes/tests/ecb_bc-test-data.rs index e1fae0de4..f46341bd3 100644 --- a/crypto/aes/tests/ecb_bc-test-data.rs +++ b/crypto/aes/tests/ecb_bc-test-data.rs @@ -48,8 +48,8 @@ use bouncycastle_aes::AES_BLOCK_LEN; use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json}; diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index df62bc68d..a37f0a23d 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -15,7 +15,7 @@ //! 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_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; diff --git a/crypto/aes/tests/gcm_bc_java_tests.rs b/crypto/aes/tests/gcm_bc_java_tests.rs index 1912e2327..1e43eb5e4 100644 --- a/crypto/aes/tests/gcm_bc_java_tests.rs +++ b/crypto/aes/tests/gcm_bc_java_tests.rs @@ -9,12 +9,10 @@ //! transcribed below, verified against the bc-java source read this session, with all-zero fields //! built programmatically rather than typed out (a zero key or plaintext cannot be mistyped). -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Gcm; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::GCM; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; @@ -180,9 +178,11 @@ fn cases() -> Vec { ] } -fn run(case: &Case) +fn run(case: &Case) where - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + K: FromKeyMaterial + + AeadEncryptionKey + + AeadDecryptionKey, { let key_bytes = hex::decode(&case.key).expect("valid hex key"); // `KeyMaterial` tags an all-zero buffer as `KeyType::Zeroized` regardless of the type @@ -208,15 +208,11 @@ where let expected_ct = hex::decode(&case.ct).expect("valid hex ct"); let expected_tag = hex::decode(case.tag).expect("valid hex tag"); + let key = K::from_key_material(&key).expect("a valid key"); let mut data = vec![0u8; pt.len()]; - let (got_iv, _, tag) = Gcm::::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new(iv), - &aad, - &pt, - &mut data, - ) - .expect("encrypt"); + let (_, got_iv, tag) = key + .encrypt_detached_out(&mut FixedSeedRNG::new(iv), &aad, &pt, &mut data) + .expect("encrypt"); assert_eq!(got_iv, iv, "{}: the pinned RNG should reproduce the vector's IV", case.name); assert_eq!(data, expected_ct, "{}: ciphertext mismatch", case.name); @@ -224,10 +220,8 @@ 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( - &key, &iv, &aad, &data, &tag_arr, &mut recovered, - ) - .unwrap_or_else(|e| panic!("{}: decrypt should have verified, got {e:?}", case.name)); + key.decrypt_detached_out(&iv, &aad, &data, &tag_arr, &mut recovered) + .unwrap_or_else(|e| panic!("{}: decrypt should have verified, got {e:?}", case.name)); assert_eq!(recovered, pt, "{}: decrypted plaintext mismatch", case.name); } @@ -237,9 +231,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::, 16>(&case), + 24 => run::, 24>(&case), + 32 => run::, 32>(&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..1fc0023b5 100644 --- a/crypto/aes/tests/gcm_tests.rs +++ b/crypto/aes/tests/gcm_tests.rs @@ -1,53 +1,41 @@ //! AES-GCM through the shared conformance suites. //! -//! What is checked here is that all three key lengths reach the shared `AEADCipherEncryptor` / -//! `AEADCipherDecryptor` conformance suite (`TestFrameworkAEADCipher`, which runs the -//! `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` suite first), that the inline +//! What is checked here is that all three key lengths reach the shared `AeadEncryptionKey` / +//! `AeadDecryptionKey` conformance suite (`TestFrameworkAEADCipher`), that the inline //! `ciphertext || tag` layout holds at every byte-boundary edge (`TestFrameworkAEADTaggedLayout`), //! and that a fresh nonce is generated per encryption. Algorithm correctness itself is pinned by //! 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_cipher::{Decrypting, Encrypting}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::GCM; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; use bouncycastle_core_test_framework::aead::TestFrameworkAEADTaggedLayout; +use bouncycastle_rng::HashDRBG_SHA512; -fn key() -> KeyMaterial { +fn material() -> KeyMaterial { 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") } -/// All three key lengths satisfy the shared AEAD conformance suite, which includes the -/// symmetric-cipher suite the padding adapters and the stream modes run. +fn key>() -> K { + K::from_key_material(&material::()).expect("a valid key") +} + +fn os_rng() -> HashDRBG_SHA512 { + HashDRBG_SHA512::new_from_os() +} + +/// All three key lengths satisfy the shared AEAD conformance suite, which reaches the no-AAD +/// symmetric contract as the AEAD one with an empty AAD. #[test] fn all_three_key_lengths_conform_to_the_aead_suite() { let framework = TestFrameworkAEADCipher::new(); - framework.test_encryptor_decryptor::< - 16, - 12, - 16, - 16, - AES_GCM_128, - AES_GCM_128, - >(); - framework.test_encryptor_decryptor::< - 24, - 12, - 16, - 16, - AES_GCM_192, - AES_GCM_192, - >(); - framework.test_encryptor_decryptor::< - 32, - 12, - 16, - 16, - AES_GCM_256, - AES_GCM_256, - >(); + let mut rng = os_rng(); + framework.test_key::<16, 12, 16, 16, _>(&key::<16, AES128Key>(), &mut rng); + framework.test_key::<24, 12, 16, 16, _>(&key::<24, AES192Key>(), &mut rng); + framework.test_key::<32, 12, 16, 16, _>(&key::<32, AES256Key>(), &mut rng); } /// The inline `ciphertext || tag` layout -- where the tag lands, what the decryptor holds back, @@ -57,8 +45,8 @@ 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_key::<16, 12, 16, 16, _>(&key::<16, AES128Key>()); + framework.test_key::<32, 12, 16, 16, _>(&key::<32, AES256Key>()); } /// The nonce is generated per encryption, so the same plaintext gives different ciphertext, and @@ -66,23 +54,15 @@ fn the_inline_tag_layout_conforms_at_every_edge() { #[test] fn each_encryption_gets_a_fresh_nonce() { let data = *b"the quick brown fox jumps over the lazy dog!!!"; + let key = key::<16, AES128Key>(); + let mut rng = os_rng(); 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) = key.encrypt_detached_out(&mut rng, 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>(), - &nonce, - b"aad", - &ct, - &tag, - &mut pt, - ) - .unwrap(); + key.decrypt_detached_out(&nonce, b"aad", &ct, &tag, &mut pt).unwrap(); assert_eq!(pt, data); } } diff --git a/crypto/aes/tests/gcm_wycheproof.rs b/crypto/aes/tests/gcm_wycheproof.rs index 5ae772c84..d1c54178f 100644 --- a/crypto/aes/tests/gcm_wycheproof.rs +++ b/crypto/aes/tests/gcm_wycheproof.rs @@ -19,10 +19,10 @@ //! # Ciphertext and tag are separate fields //! //! Wycheproof's AEAD schema (`aead_test_schema_v1`) carries `ct` and `tag` as distinct fields, so -//! these cases go through the detached pair, [`AEADCipherEncryptor::encrypt_detached_rng_out`] / -//! [`AEADCipherDecryptor::decrypt_detached_out`]. [`Gcm`] generates its own nonce, so the vector's -//! `iv` is supplied through a `FixedSeedRNG` and the returned nonce is asserted to be exactly that -//! IV, the same technique as `gcm_bc-test-data.rs`. +//! these cases go through the key's detached pair, `encrypt_detached_out` / +//! `decrypt_detached_out`. The key draws its own nonce, so the vector's `iv` is supplied through +//! a `FixedSeedRNG` and the returned nonce is asserted to be exactly that IV, the same technique +//! as `gcm_bc-test-data.rs`. //! //! # Only the 96-bit-IV groups can be dispatched to, by design //! @@ -36,16 +36,13 @@ //! 128-bit tag, which is within the `12..=16` bytes `Gcm` accepts, so the tag size never rules a //! case out. -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::modes::{GCM_NONCE_LEN, Gcm}; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{GCM, GCM_NONCE_LEN}; use bouncycastle_core::errors::SymmetricCipherError; -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::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; use bouncycastle_rng::FixedSeedRNG; @@ -74,7 +71,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,22 +81,18 @@ fn run_case( expected_tag: &[u8], valid: bool, ) where - P: ElectronicCodeBook + ModeNames, + K: FromKeyMaterial + + AeadEncryptionKey + + AeadDecryptionKey, { - let key = cipher_key::(key_bytes); + let key = K::from_key_material(&cipher_key::(key_bytes)).expect("a wycheproof key"); 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( - &key, - &mut FixedSeedRNG::new(iv), - aad, - msg, - &mut ct, - ) + let (written, got_iv, got_tag) = key + .encrypt_detached_out(&mut FixedSeedRNG::new(iv), aad, msg, &mut ct) .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); assert_eq!(got_iv, iv, "tcId {tc_id}: the seeded RNG must reproduce the vector's IV"); assert_eq!(written, msg.len(), "tcId {tc_id}: encrypt_detached writes exactly msg.len()"); @@ -110,9 +103,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( - &key, &iv, aad, expected_ct, &tag, &mut plaintext, - ) { + match key.decrypt_detached_out(&iv, aad, expected_ct, &tag, &mut plaintext) { Ok(n) => { assert!(valid, "tcId {tc_id}: an invalid vector decrypted and verified anyway"); plaintext.truncate(n); @@ -152,13 +143,13 @@ fn dispatch( return false; } match key_bytes.len() { - 16 => run_case::<16, 16, AES128Internal>( + 16 => run_case::<16, 16, AES128Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), - 24 => run_case::<24, 16, AES192Internal>( + 24 => run_case::<24, 16, AES192Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), - 32 => run_case::<32, 16, AES256Internal>( + 32 => run_case::<32, 16, AES256Key>( tc_id, key_bytes, iv, aad, msg, expected_ct, expected_tag, valid, ), _ => return false, diff --git a/crypto/aes/tests/gmac_key_tests.rs b/crypto/aes/tests/gmac_key_tests.rs index 4d06a63fe..78e3bcb66 100644 --- a/crypto/aes/tests/gmac_key_tests.rs +++ b/crypto/aes/tests/gmac_key_tests.rs @@ -1,17 +1,14 @@ //! `AES*Key`: the key object holding `H`, and the pair it hands out producing exactly what -//! `Gmac::new` and `AES_GCM_*` over AAD alone produce from the same bytes. +//! `AES_GCM_*` over AAD alone produces from the same bytes. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal}; -use bouncycastle_aes::{ - AES_GCM_128, AES_GMAC_128, AES_GMAC_192, AES_GMAC_256, AES128Key, AES192Key, AES256Key, -}; +use bouncycastle_aes::{AES_GCM_128, AES_GMAC_128, AES_GMAC_192, AES128Key, AES192Key, AES256Key}; use bouncycastle_cipher::modes::{GCM, GCM_NONCE_LEN, GMAC, GcmKeyState, Gmac}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::symmetric_key::FromKeyMaterial; -use bouncycastle_core::traits::AEADCipherEncryptor; +use bouncycastle_core::symmetric_key::{AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_rng::FixedSeedRNG; const NONCE: [u8; GCM_NONCE_LEN] = [0x5A; GCM_NONCE_LEN]; @@ -25,8 +22,8 @@ fn rng() -> FixedSeedRNG<[u8; GCM_NONCE_LEN]> { FixedSeedRNG::new(NONCE) } -/// The key object, the material constructor and GCM over AAD alone give one tag, at every key -/// length and through both generator constructors. +/// The key object and GCM over AAD alone give one tag, at every key length and through both +/// generator constructors. #[test] fn the_key_and_the_other_routes_agree_at_every_key_length() { macro_rules! agree { @@ -42,17 +39,8 @@ fn the_key_and_the_other_routes_agree_at_every_key_length() { "{} supplied nonce", $klen ); - assert_eq!( - $alias::::new_nonce(&mac_key, &NONCE).unwrap().mac(MSG), - tag, - "{} Gmac::new_nonce", - $klen - ); - let (gen_, nonce) = $alias::::new(&mac_key, &mut rng()).unwrap(); - assert_eq!((nonce, gen_.mac(MSG)), (NONCE, tag), "{} Gmac::new", $klen); assert!(key.create_verifier(&NONCE).verify(MSG, &tag)); assert!(!key.create_verifier(&NONCE).verify(b"another message", &tag)); - assert!($alias::::new(&mac_key, &NONCE).unwrap().verify(MSG, &tag)); tag }}; } @@ -61,14 +49,11 @@ fn the_key_and_the_other_routes_agree_at_every_key_length() { agree!(AES256Key, AES_GMAC_256, 32, 0x33); // And GCM over the same bytes as a cipher key, with the message as AAD and no payload. - let (nonce, _, from_gcm) = AES_GCM_128::::encrypt_detached_rng_out( - &material::<16>(0x11, KeyType::SymmetricCipherKey), - &mut rng(), - MSG, - &[], - &mut [], - ) - .unwrap(); + let (_, nonce, from_gcm) = + AES128Key::::from_key_material(&material::<16>(0x11, KeyType::SymmetricCipherKey)) + .unwrap() + .encrypt_detached_out(&mut rng(), MSG, &[], &mut []) + .unwrap(); assert_eq!((nonce, from_gcm), (NONCE, tag128)); } @@ -140,10 +125,6 @@ fn a_key_below_its_own_strength_is_refused_at_construction() { AES256Key::::from_key_material(&weak), Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) )); - assert!(matches!( - AES_GMAC_256::::new_nonce(&weak, &NONCE), - Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); } #[test] @@ -164,9 +145,9 @@ fn sizes() { [192, 224, 256] ); assert_eq!(size_of::>(), size_of::>()); - assert_eq!(size_of::>(), size_of::>()); + assert_eq!(size_of::>(), size_of::>()); assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); } diff --git a/crypto/aes/tests/key_tests.rs b/crypto/aes/tests/key_tests.rs index 8830b05a4..80fcb021a 100644 --- a/crypto/aes/tests/key_tests.rs +++ b/crypto/aes/tests/key_tests.rs @@ -1,11 +1,13 @@ //! The mode-bound AES key objects: validation at construction, and `key.create_encryptor(rng)` -//! producing exactly what the mode's own `do_encrypt_init_rng` produces from the same material -//! and RNG. +//! producing exactly what the mode's own route -- the hazmat mode traits the key forwards to -- +//! produces from the same material and RNG. -use bouncycastle_aes::{AES_GCM_128, AES_GCM_192, AES_GCM_256, AES128Key, AES192Key, AES256Key}; -use bouncycastle_cipher::modes::GCM; +use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_aes::{AES_GCM_128, AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{CTR, GCM, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; +use bouncycastle_core::hazmat::{AeadDecryptMode, AeadEncryptMode, ElectronicCodeBook, Mode}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; @@ -14,12 +16,6 @@ use bouncycastle_core::traits::{ }; use bouncycastle_rng::FixedSeedRNG; -/// The worker traits, kept out of scope: a GCM worker implements both them and the cipher -/// traits under the same method names, so the generic helpers name them by path. -mod w { - pub use bouncycastle_core::symmetric_key::{AeadDecryptWorker, AeadEncryptWorker}; -} - fn material() -> KeyMaterial { 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") @@ -38,11 +34,10 @@ where K: AeadEncryptionKey, { let (mut enc, nonce) = key.create_encryptor(&mut rng()).expect("nonce draw"); - w::AeadEncryptWorker::do_update_aad(&mut enc, AAD).unwrap(); + enc.do_update_aad(AAD).unwrap(); let mut ct = vec![0u8; MSG.len()]; - w::AeadEncryptWorker::do_encrypt_out(&mut enc, MSG, &mut ct).unwrap(); - let (_, tag) = - w::AeadEncryptWorker::do_encrypt_final_detached_out(enc, &mut [0u8; 16]).unwrap(); + enc.do_encrypt_out(MSG, &mut ct).unwrap(); + let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; 16]).unwrap(); (nonce, ct, tag) } @@ -59,11 +54,11 @@ where { // Decryption setup is infallible: no Result to unwrap. let mut dec = key.create_decryptor(nonce); - w::AeadDecryptWorker::do_update_aad(&mut dec, AAD)?; + dec.do_update_aad(AAD)?; let mut pt = vec![0u8; ct.len() + 16]; - let n = w::AeadDecryptWorker::do_decrypt_out(&mut dec, ct, &mut pt)?; + let n = dec.do_decrypt_out(ct, &mut pt)?; let mut last = [0u8; 16]; - let m = w::AeadDecryptWorker::do_decrypt_final_detached_out(dec, tag, &mut last)?; + let m = dec.do_decrypt_final_detached_out(tag, &mut last)?; Ok([&pt[..n], &last[..m]].concat()) } @@ -93,28 +88,31 @@ fn the_key_type_a_mode_requires_is_the_constructor_constant() { } /// The key-side encryptor is the mode's own type built from the same material, so with the same -/// RNG it must produce the same nonce, ciphertext and tag as the static constructor. +/// RNG it must produce the same nonce, ciphertext and tag as the worker built from the bare key +/// state through the mode trait the key forwards to. #[test] -fn create_encryptor_matches_do_encrypt_init_rng() { +fn create_encryptor_matches_the_key_state_route() { let km = material::<32>(); let key = AES256Key::::from_key_material(&km).expect("a valid key"); + let state = >::key_state(AES256Internal::new(&km).unwrap()); - let (mut enc_static, nonce_static) = - AES_GCM_256::::do_encrypt_init_rng(&km, &mut rng()).expect("static init"); + let (mut enc_state, nonce_state) = + >::encrypt_init(&state, &mut rng()) + .expect("key-state init"); let (mut enc_key, nonce_key) = key.create_encryptor(&mut rng()).expect("key-side init"); - assert_eq!(nonce_static, nonce_key); + assert_eq!(nonce_state, nonce_key); - let mut ct_static = vec![0u8; MSG.len()]; + let mut ct_state = vec![0u8; MSG.len()]; let mut ct_key = vec![0u8; MSG.len()]; - enc_static.do_update_aad(AAD).unwrap(); + enc_state.do_update_aad(AAD).unwrap(); enc_key.do_update_aad(AAD).unwrap(); - enc_static.do_encrypt_out(MSG, &mut ct_static).unwrap(); + enc_state.do_encrypt_out(MSG, &mut ct_state).unwrap(); enc_key.do_encrypt_out(MSG, &mut ct_key).unwrap(); - let (_, _, tag_static) = enc_static.do_encrypt_final_detached().unwrap(); + let (_, _, tag_state) = enc_state.do_encrypt_final_detached().unwrap(); let (_, _, tag_key) = enc_key.do_encrypt_final_detached().unwrap(); - assert_eq!(ct_static, ct_key); - assert_eq!(tag_static, tag_key); + assert_eq!(ct_state, ct_key); + assert_eq!(tag_state, tag_key); } #[test] @@ -134,35 +132,33 @@ fn create_decryptor_round_trips_and_detects_tampering() { } /// The one-shots on the role traits are written over `create_encryptor` and the streaming -/// methods, so they must match the cipher traits' key-taking one-shots byte for byte. +/// methods, so they must match the message streamed by hand byte for byte, in both layouts. #[test] -fn key_one_shots_match_the_static_one_shots() { +fn key_one_shots_match_the_streaming_methods() { let km = material::<16>(); let key = AES128Key::::from_key_material(&km).expect("a valid key"); - type E = AES_GCM_128; - type D = AES_GCM_128; + type E<'k> = AES_GCM_128<'k, Encrypting>; + type D<'k> = AES_GCM_128<'k, Decrypting>; + + // The reference: streamed by hand, detached, under the pinned nonce. + let (nonce_s, ct_s, tag_s) = encrypt_via_key::<16, _>(&key); + let mut inline_s = ct_s.clone(); + inline_s.extend_from_slice(&tag_s); // Inline tag. - let mut ct_static = vec![0u8; E::encrypt_out_len(MSG.len())]; - let (nonce_s, n_s) = - E::encrypt_with_aad_rng_out(&km, &mut rng(), AAD, MSG, &mut ct_static).unwrap(); let mut ct_key = vec![0u8; E::encrypt_out_len(MSG.len())]; let (n_k, nonce_k) = key.encrypt_with_aad_out(&mut rng(), AAD, MSG, &mut ct_key).unwrap(); - assert_eq!((nonce_s, n_s), (nonce_k, n_k)); - assert_eq!(ct_static, ct_key); + assert_eq!((nonce_s, inline_s.len()), (nonce_k, n_k)); + assert_eq!(inline_s, ct_key); let mut pt = vec![0u8; D::decrypt_out_len(ct_key.len())]; let m = key.decrypt_with_aad_out(&nonce_k, AAD, &ct_key, &mut pt).unwrap(); assert_eq!(&pt[..m], MSG); - assert_eq!(D::decrypt_with_aad(&km, &nonce_k, AAD, &ct_key).unwrap(), MSG); assert_eq!(key.decrypt_with_aad(&nonce_k, AAD, &ct_key).unwrap(), MSG); // Detached tag. - let mut ct_s = vec![0u8; E::encrypt_detached_out_len(MSG.len())]; - let (nonce_s, n_s, tag_s) = - E::encrypt_detached_rng_out(&km, &mut rng(), AAD, MSG, &mut ct_s).unwrap(); let (nonce_k, ct_k, tag_k) = key.encrypt_detached(&mut rng(), AAD, MSG).unwrap(); - assert_eq!((nonce_s, n_s), (nonce_k, ct_k.len())); + assert_eq!((nonce_s, ct_s.len()), (nonce_k, ct_k.len())); assert_eq!((ct_s, tag_s), (ct_k.clone(), tag_k)); let mut pt = vec![0u8; D::decrypt_detached_out_len(ct_k.len())]; @@ -188,34 +184,46 @@ fn key_one_shots_match_the_static_one_shots() { )); } -/// The decryptor built from the key accepts what the static encryptor produced, and vice versa, -/// at every key length: one more check that the two paths are the same cipher. +/// The decryptor built from the key accepts what a worker built from the bare key state +/// produced, and vice versa, at every key length: one more check that the two routes are the +/// same cipher. #[test] -fn key_side_and_static_paths_interoperate_at_every_key_length() { - fn check(key: &K) +fn the_key_and_the_key_state_route_interoperate_at_every_key_length() { + fn check(key: &K) where + P: ElectronicCodeBook + ModeNames + Clone, K: AeadEncryptionKey + AeadDecryptionKey, - E: AEADCipherEncryptor, - D: AEADCipherDecryptor, { - let km = material::(); - - let (nonce, ct, tag) = E::encrypt_detached(&km, AAD, MSG).expect("static one-shot"); + let state = >::key_state(P::new(&material::()).unwrap()); + + let (mut enc, nonce) = + >::encrypt_init(&state, &mut rng()).unwrap(); + enc.do_update_aad(AAD).unwrap(); + let mut ct = vec![0u8; MSG.len()]; + enc.do_encrypt_out(MSG, &mut ct).unwrap(); + let (_, _, tag) = enc.do_encrypt_final_detached().unwrap(); let pt = decrypt_via_key::(key, &nonce, &ct, &tag).expect("a valid tag"); assert_eq!(pt, MSG); let (nonce, ct, tag) = encrypt_via_key::(key); - let pt = D::decrypt_detached(&km, &nonce, AAD, &ct, &tag).expect("static one-shot"); + let mut dec = >::decrypt_init(&state, &nonce); + dec.do_update_aad(AAD).unwrap(); + let mut pt = vec![0u8; ct.len()]; + let n = dec.do_decrypt_out(&ct, &mut pt).unwrap(); + let mut last = [0u8; 16]; + let m = dec.do_decrypt_final_detached_out(&tag, &mut last).unwrap(); + pt.truncate(n); + pt.extend_from_slice(&last[..m]); assert_eq!(pt, MSG); } - check::<16, _, AES_GCM_128, AES_GCM_128>( + check::<16, AES128Internal, _>( &AES128Key::::from_key_material(&material::<16>()).unwrap(), ); - check::<24, _, AES_GCM_192, AES_GCM_192>( + check::<24, AES192Internal, _>( &AES192Key::::from_key_material(&material::<24>()).unwrap(), ); - check::<32, _, AES_GCM_256, AES_GCM_256>( + check::<32, AES256Internal, _>( &AES256Key::::from_key_material(&material::<32>()).unwrap(), ); } @@ -297,6 +305,35 @@ fn a_cloned_key_is_the_same_key() { assert_eq!(encrypt_via_key::<16, _>(©), (nonce, ct, tag)); } +/// Equality is over the expanded state, so it follows the material: the same bytes under the same +/// mode compare equal, as does a clone, and different bytes do not. Both shapes of state are +/// covered: the bare schedule (`CTR`) and GCM's, which adds `H`. Keys have no `Debug`, hence +/// `assert!` over `==` rather than `assert_eq!`. +#[test] +fn keys_compare_equal_exactly_when_their_material_does() { + let other = KeyMaterial::<16>::from_bytes_as_type(&[0x55; 16], KeyType::SymmetricCipherKey) + .expect("a valid key"); + + let key = AES128Key::::from_key_material(&material::<16>()).unwrap(); + assert!(key == key.clone()); + assert!(key == AES128Key::::from_key_material(&material::<16>()).unwrap()); + assert!(key != AES128Key::::from_key_material(&other).unwrap()); + + let key = AES128Key::::from_key_material(&material::<16>()).unwrap(); + assert!(key == key.clone()); + assert!(key != AES128Key::::from_key_material(&other).unwrap()); + + // The longer schedules are a different plane width; the comparison is over all of it. + let key = AES256Key::::from_key_material(&material::<32>()).unwrap(); + assert!(key == AES256Key::::from_key_material(&material::<32>()).unwrap()); + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(material::<32>().ref_to_bytes()); + bytes[31] ^= 1; + let last_byte_differs = + KeyMaterial::<32>::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).unwrap(); + assert!(key != AES256Key::::from_key_material(&last_byte_differs).unwrap()); +} + /// Lengths either side of the 64-byte chunk the provided in-place bodies copy through, plus the /// empty message and a long one. const IN_PLACE_LENS: [usize; 9] = [0, 1, 15, 16, 63, 64, 65, 200, 1000]; @@ -365,11 +402,10 @@ fn supplied_nonce_forms_match_the_drawing_forms() { assert_eq!(buf, ct); let mut enc = key.create_encryptor_nonce(&nonce); - w::AeadEncryptWorker::do_update_aad(&mut enc, AAD).unwrap(); + enc.do_update_aad(AAD).unwrap(); let mut streamed = vec![0u8; msg.len()]; - let n = w::AeadEncryptWorker::do_encrypt_out(&mut enc, msg, &mut streamed).unwrap(); - let (_, tag_s) = - w::AeadEncryptWorker::do_encrypt_final_detached_out(enc, &mut [0u8; 16]).unwrap(); + let n = enc.do_encrypt_out(msg, &mut streamed).unwrap(); + let (_, tag_s) = enc.do_encrypt_final_detached_out(&mut [0u8; 16]).unwrap(); assert_eq!((&streamed[..n], tag_s), (&ct[..], tag)); assert_eq!(key.decrypt_detached(&nonce, AAD, &ct, &tag).unwrap(), msg); diff --git a/crypto/aes/tests/mode_key_tests.rs b/crypto/aes/tests/mode_key_tests.rs new file mode 100644 index 000000000..2dd64495f --- /dev/null +++ b/crypto/aes/tests/mode_key_tests.rs @@ -0,0 +1,180 @@ +//! The mode-bound AES keys for the block, padded, stream and fixed-frame modes: the construction +//! suite and the shared framework's key runner for each, and what tells the modes apart on the +//! key path -- an `ECB` key takes no RNG, a padded ECB key's init data is empty, and a +//! `CBC` key's in-place one-shot is the `CBC` key's `encrypt_out`. +//! +//! The GCM, CCM, CMAC and GMAC keys have their own files. + +use bouncycastle_aes::hazmat::{AES128Internal, AES256Internal}; +use bouncycastle_aes::{AES128Key, AES256Key}; +use bouncycastle_cipher::modes::hazmat::ecb::ECB; +use bouncycastle_cipher::modes::{Blocks, CBC, CCM_Packet, CFB, CFB8, CTR, ModeNames}; +use bouncycastle_cipher::padding::{NoPadding, PKCS7}; +use bouncycastle_core::hazmat::{EcbEncryptionKey, Mode}; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, SymmetricDecryptionKey, + SymmetricEncryptionKey, +}; +use bouncycastle_core::traits::BlockCipherEncryptor; +use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; +use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; +use bouncycastle_core_test_framework::symmetric_ciphers::{ + TestFrameworkStreamCipher, TestFrameworkSymmetricCipher, +}; +use bouncycastle_rng::FixedSeedRNG; +use bouncycastle_rng::HashDRBG_SHA512; + +/// A 32-byte frame for the fixed-frame CCM key, within the framework's seed buffer. +type Packet = CCM_Packet<12, 16, 64, 32>; + +fn material() -> KeyMaterial { + 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") +} + +fn key128>() -> AES128Key { + AES128Key::from_key_material(&material::<16>()).expect("a valid key") +} + +fn key256>() -> AES256Key { + AES256Key::from_key_material(&material::<32>()).expect("a valid key") +} + +fn rng() -> HashDRBG_SHA512 { + HashDRBG_SHA512::new_from_os() +} + +/// The construction suite -- key type, binding, strength and `generate` -- for the mode `M` at +/// both key lengths, against the name the permutation gives that mode. +fn construction_suite(name_128: &'static str, name_256: &'static str) +where + M: Mode + Mode, +{ + let framework = TestFrameworkFromKeyMaterial::new(); + framework.test::<16, AES128Key>(name_128, SecurityStrength::_128bit); + framework.test::<32, AES256Key>(name_256, SecurityStrength::_256bit); +} + +#[test] +fn every_mode_key_enforces_the_material_policy() { + construction_suite::>(AES128Internal::CBC_ALG_NAME, AES256Internal::CBC_ALG_NAME); + construction_suite::>(AES128Internal::CBC_ALG_NAME, AES256Internal::CBC_ALG_NAME); + construction_suite::>( + AES128Internal::CBC_ALG_NAME, + AES256Internal::CBC_ALG_NAME, + ); + construction_suite::>(AES128Internal::ECB_ALG_NAME, AES256Internal::ECB_ALG_NAME); + construction_suite::>(AES128Internal::ECB_ALG_NAME, AES256Internal::ECB_ALG_NAME); + construction_suite::(AES128Internal::CFB_ALG_NAME, AES256Internal::CFB_ALG_NAME); + construction_suite::(AES128Internal::CFB8_ALG_NAME, AES256Internal::CFB8_ALG_NAME); + construction_suite::(AES128Internal::CTR_ALG_NAME, AES256Internal::CTR_ALG_NAME); + construction_suite::(AES128Internal::CCM_ALG_NAME, AES256Internal::CCM_ALG_NAME); +} + +#[test] +fn cbc_blocks_keys_pass_the_block_cipher_framework() { + let framework = TestFrameworkBlockCipher::new(); + framework.test_key::<16, 16, 16, _>(&key128::>(), &mut rng()); + framework.test_key::<32, 16, 16, _>(&key256::>(), &mut rng()); +} + +#[test] +fn ecb_blocks_keys_pass_the_block_cipher_framework() { + let framework = TestFrameworkBlockCipher::new(); + framework.test_ecb_key::<16, 16, _>(&key128::>()); + framework.test_ecb_key::<32, 16, _>(&key256::>()); +} + +#[test] +fn padded_keys_pass_the_symmetric_cipher_framework() { + let framework = TestFrameworkSymmetricCipher::new(); + framework.test_key::<16, 16, 16, _>(&key128::>(), &mut rng()); + framework.test_key::<32, 16, 16, _>(&key256::>(), &mut rng()); + framework.test_key::<16, 0, 16, _>(&key128::>(), &mut rng()); + framework.test_key::<32, 0, 16, _>(&key256::>(), &mut rng()); +} + +/// `NoPadding` turns the adapters into an alignment check: the framework is told so, and asserts +/// that every other length is refused. +#[test] +fn no_padding_keys_pass_the_symmetric_cipher_framework() { + let mut framework = TestFrameworkSymmetricCipher::new(); + framework.required_alignment = 16; + framework.test_key::<16, 16, 16, _>(&key128::>(), &mut rng()); + framework.test_key::<32, 16, 16, _>(&key256::>(), &mut rng()); + framework.test_key::<16, 0, 16, _>(&key128::>(), &mut rng()); + framework.test_key::<32, 0, 16, _>(&key256::>(), &mut rng()); +} + +#[test] +fn stream_mode_keys_pass_the_stream_cipher_framework() { + let framework = TestFrameworkStreamCipher::new(); + framework.test_key::<16, 16, _>(&key128::(), &mut rng()); + framework.test_key::<32, 16, _>(&key256::(), &mut rng()); + framework.test_key::<16, 16, _>(&key128::(), &mut rng()); + framework.test_key::<32, 16, _>(&key256::(), &mut rng()); + framework.test_key::<16, 12, _>(&key128::(), &mut rng()); + framework.test_key::<32, 12, _>(&key256::(), &mut rng()); +} + +/// The fixed-frame key's workers and one-shots take exactly the frame, so the framework is told +/// the one length they accept. +#[test] +fn ccm_frame_keys_pass_the_aead_framework() { + let mut framework = TestFrameworkAEADCipher::new(); + framework.fixed_message_len = Some(32); + framework.test_key::<16, 12, 16, 16, _>(&key128::(), &mut rng()); + framework.test_key::<32, 12, 16, 16, _>(&key256::(), &mut rng()); +} + +/// ECB has no init data, so the `ECB` key makes its workers with no RNG and nothing to +/// return, and the `ECB` key, on the symmetric path, takes an RNG it never draws from and +/// returns `[u8; 0]`. The padded ciphertext is the block pair's encryption of the padded block. +#[test] +fn ecb_keys_take_no_init_data() { + let key = key128::>(); + let mut padded_hello = *b"hello\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + let mut enc = key.create_encryptor(); + enc.do_encrypt_blocks_inplace(core::slice::from_mut(&mut padded_hello)).unwrap(); + let mut again = *b"hello\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + assert_eq!(key.encrypt_inplace(&mut again).unwrap(), 16); + assert_eq!(again, padded_hello, "the worker and the one-shot are the same codebook"); + + let padded_key = key128::>(); + let mut out = [0u8; 16]; + // A seedless RNG panics on any draw, so finishing is the proof that none is made. + let (written, init): (usize, [u8; 0]) = + padded_key.encrypt_out(&mut FixedSeedRNG::new([]), b"hello", &mut out).unwrap(); + assert_eq!((written, init), (16, [])); + assert_eq!(out, padded_hello, "PKCS7 over ECB is ECB of the padded block"); + assert_eq!(padded_key.decrypt(&init, &out).unwrap(), b"hello"); +} + +/// Under a pinned IV, the `CBC` key's in-place one-shot and the `CBC` key's +/// `encrypt_out` are the same bytes: `NoPadding` is an alignment check over the same mode. +#[test] +fn cbc_blocks_in_place_agrees_with_cbc_no_padding_out() { + let iv = [0xA5u8; 16]; + let msg: [u8; 48] = core::array::from_fn(|i| (i * 7 + 3) as u8); + + let mut in_place = msg; + let (n, iv_blocks) = + key128::>().encrypt_inplace(&mut FixedSeedRNG::new(iv), &mut in_place).unwrap(); + assert_eq!((n, iv_blocks), (48, iv)); + + let mut out = [0u8; 48]; + let (m, iv_np) = + key128::>().encrypt_out(&mut FixedSeedRNG::new(iv), &msg, &mut out).unwrap(); + assert_eq!((m, iv_np), (48, iv)); + assert_eq!(out, in_place); + + // ...and each key's decryptor opens the other's ciphertext. + key128::>().decrypt_inplace(&iv, &mut out).unwrap(); + assert_eq!(out, msg); + let mut back = [0u8; 64]; + let k = key128::>().decrypt_out(&iv, &in_place, &mut back).unwrap(); + assert_eq!(&back[..k], &msg[..]); +} diff --git a/crypto/aes/tests/padded_alias_tests.rs b/crypto/aes/tests/padded_alias_tests.rs index 4dc7c5ffc..9536cad02 100644 --- a/crypto/aes/tests/padded_alias_tests.rs +++ b/crypto/aes/tests/padded_alias_tests.rs @@ -1,35 +1,47 @@ -//! Tests for the padded AES-CBC and AES-ECB aliases. +//! The CBC and ECB aliases with their padding parameter. //! //! The aliases are only type aliases, so what is worth testing is that both parameters actually //! select: the direction picks the encryptor or the decryptor, and the padding scheme changes the //! behaviour rather than being decorative. The mode and the padding layer are tested in their own -//! crates; this checks the wiring between them. Which type each alias resolves to is pinned in -//! `alias_tests.rs`. +//! crates; this checks the wiring between them, through the keys that hand the aliases out. Which +//! type each alias resolves to is pinned in `alg_name_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::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CBC; +use bouncycastle_cipher::modes::hazmat::ecb::ECB; use bouncycastle_cipher::padding::{NoPadding, PKCS7}; -use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_rng::HashDRBG_SHA512; -fn key() -> KeyMaterial { +fn material() -> KeyMaterial { 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") } -/// Every key length round-trips through its alias, at lengths that need padding and lengths that -/// do not. +fn key_for>() -> K { + K::from_key_material(&material::()).expect("a valid key") +} + +fn rng() -> HashDRBG_SHA512 { + HashDRBG_SHA512::new_from_os() +} + #[test] fn every_key_length_round_trips() { - fn check(name: &str) + fn check(name: &str) where - Enc: SymmetricCipherEncryptor, - Dec: SymmetricCipherDecryptor, + K: FromKeyMaterial + + SymmetricEncryptionKey + + SymmetricDecryptionKey, { + let key = key_for::(); 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) = key.encrypt(&mut rng(), &plaintext).expect("encryption"); // PKCS#7 always adds at least one byte, and rounds up to a whole block. assert_eq!( @@ -38,125 +50,110 @@ 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 = key.decrypt(&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, AES128Key>>("CBC-128"); + check::<24, 16, AES192Key>>("CBC-192"); + check::<32, 16, AES256Key>>("CBC-256"); + check::<16, 0, AES128Key>>("ECB-128"); + check::<24, 0, AES192Key>>("ECB-192"); + check::<32, 0, AES256Key>>("ECB-256"); } -/// The padding parameter must actually select the scheme, not merely be carried around. -/// -/// `PKCS7` accepts any length and always grows the message; `NoPadding` accepts only whole blocks -/// and never grows it. Checking both against the same alias, key and plaintext is what proves the -/// 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>, + Pkcs7Key: FromKeyMaterial<16> + SymmetricEncryptionKey<16, IV_LEN, 16>, + NoPadKey: FromKeyMaterial<16> + SymmetricEncryptionKey<16, IV_LEN, 16>, { + let pkcs7_key = key_for::<16, Pkcs7Key>(); + let nopad_key = key_for::<16, NoPadKey>(); + // 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) = pkcs7_key.encrypt(&mut rng(), &aligned).expect("PKCS7 accepts it"); + let (_, nopad) = nopad_key.encrypt(&mut rng(), &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!(pkcs7_key.encrypt(&mut rng(), unaligned).is_ok(), "{name}: PKCS7 pads it"); assert!( - NoPadEnc::encrypt(&key::<16>(), unaligned).is_err(), + nopad_key.encrypt(&mut rng(), 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, AES128Key>, AES128Key>>("CBC"); + check::<0, AES128Key>, AES128Key>>("ECB"); } -/// A ciphertext made under one scheme must not decrypt cleanly under the other. -/// -/// This is the practical reason the scheme is named in the type: the two are not interchangeable, -/// and without the type parameter nothing would stop a caller pairing them. #[test] fn the_two_schemes_are_not_interchangeable() { let aligned = [0x5Au8; 16]; let (iv, pkcs7) = - AES_CBC_128::::encrypt(&key::<16>(), &aligned).expect("encryption"); + key_for::<16, AES128Key>>().encrypt(&mut rng(), &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) + let as_nopad = key_for::<16, AES128Key>>() + .decrypt(&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"); + let correct = key_for::<16, AES128Key>>().decrypt(&iv, &pkcs7).expect("decryption"); assert_eq!(correct, aligned); } -/// The IV is generated per encryption, so the same plaintext gives different ciphertext. #[test] fn each_cbc_encryption_gets_a_fresh_iv() { + let key = key_for::<16, AES128Key>>(); + let mut rng = rng(); 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) = key.encrypt(&mut rng, &plaintext).unwrap(); assert!(seen.insert(iv), "IV repeated across encryptions"); - let back = AES_CBC_128::::decrypt(&key::<16>(), &iv, &ct).unwrap(); + let back = key.decrypt(&iv, &ct).unwrap(); assert_eq!(back, plaintext); } } -/// ECB has no IV, so the init data is an empty array and the ciphertext is exactly the padded -/// plaintext with nothing prepended. That is the difference from the CBC aliases, and it comes from -/// the `INIT_DATA_LEN = 0` the projection is given. #[test] fn ecb_has_no_iv() { - let (no_iv, ciphertext) = - AES_ECB_128::::encrypt(&key::<16>(), b"hello").expect("encryption"); + let key = key_for::<16, AES128Key>>(); + let (no_iv, ciphertext) = key.encrypt(&mut rng(), 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 = key.decrypt(&no_iv, &ciphertext).unwrap(); assert_eq!(recovered, b"hello"); } -/// Padding does not fix ECB: identical plaintext blocks still give identical ciphertext blocks, and -/// the same message under the same key always gives the same ciphertext. The aliases carry the -/// warning; this is the test that it is warranted. #[test] 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(); + key_for::<16, AES128Key>>().encrypt(&mut rng(), &[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 key = key_for::<16, AES128Key>>(); + let (_, a) = key.encrypt(&mut rng(), b"hello").unwrap(); + let (_, b) = key.encrypt(&mut rng(), b"hello").unwrap(); assert_eq!(a, b, "the same message encrypts the same way every time"); } -/// The allocating streaming wrappers `do_encrypt` / `do_decrypt` return exactly what their `_out` -/// counterparts write -- `do_*_out_len` bytes, including none when a piece is wholly buffered or -/// held back -- and a message streamed through them round-trips. #[test] fn the_allocating_streaming_wrappers_match_the_out_versions() { - type Enc = AES_CBC_128; - type Dec = AES_CBC_128; + let key = key_for::<16, AES128Key>>(); let plaintext: Vec = (0u8..40).collect(); // Piece lengths 5, 11, 1, 20, 0, 3: CBC releases a block only once it is full, so these @@ -164,7 +161,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) = key.create_encryptor(&mut rng()).unwrap(); let mut ciphertext = Vec::new(); for (range, expected) in pieces.into_iter().zip(expected_released) { let piece = &plaintext[range]; @@ -180,11 +177,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!(key.decrypt(&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 = key.create_decryptor(&iv); 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 87ef9e46c..cc9bd127f 100644 --- a/crypto/aes/tests/sp800_38a_cbc_tests.rs +++ b/crypto/aes/tests/sp800_38a_cbc_tests.rs @@ -11,16 +11,16 @@ //! # Driving the IV //! //! There is no API for supplying an IV -- see the crate docs. Encryption is therefore driven -//! through [`BlockCipherEncryptor::do_encrypt_init_rng`] with a [`FixedSeedRNG`] whose stream is -//! the vector's IV, and the test asserts the returned init data really is that IV before comparing -//! any ciphertext. Decryption takes the IV directly, as init data. - -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cbc; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +//! through the key's [`BlockEncryptionKey::create_encryptor`] with a [`FixedSeedRNG`] whose +//! stream is the vector's IV, and the test asserts the returned init data really is that IV before +//! comparing any ciphertext. Decryption takes the IV directly, as init data. + +use bouncycastle_aes::hazmat::AES128Internal; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{Blocks, CBC}; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; @@ -92,32 +92,24 @@ fn key_material(hex_str: &str) -> KeyMaterial { /// /// 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: FromKeyMaterial + BlockEncryptionKey, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); 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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let (mut enc, got_iv) = key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); assert_eq!(got_iv, iv, "{section}: the pinned RNG should produce the vector's IV"); let mut data = flat(&PLAINTEXTS); enc.do_encrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); assert_eq!(data, flat(expected), "{section}: four blocks in one call"); // One block at a time. - let (mut enc, _) = Cbc::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); for (i, (p, c)) in pt.iter().zip(ct.iter()).enumerate() { let mut got = *p; enc.do_encrypt_blocks_inplace(got.as_chunks_mut().0).unwrap(); @@ -129,25 +121,23 @@ 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: FromKeyMaterial + BlockDecryptionKey, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(ciphertext); - 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 = key.create_decryptor(&iv); 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 = key.create_decryptor(&iv); 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 +145,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 = key.create_decryptor(&iv); 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 +156,32 @@ where #[test] fn f_2_1_cbc_aes128_encrypt() { - check_encrypt::("F.2.1", KEY_128, &CIPHERTEXTS_128); + check_encrypt::>, 16>("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::>, 16>("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::>, 24>("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::>, 24>("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::>, 32>("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::>, 32>("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. @@ -209,18 +199,15 @@ fn cbc_differs_from_ecb_by_the_iv() { // The raw permutation on P1 alone is the ECB answer from F.1.1. let mut ecb = block(PLAINTEXTS[0]); - >::encrypt_block( + >::encrypt_block( &>::new(&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::new(iv), - ) - .unwrap(); + let key_object = AES128Key::>::from_key_material(&key).unwrap(); + let (mut enc, _) = key_object.create_encryptor(&mut FixedSeedRNG::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 3b9a9bf5d..b62aa3ec8 100644 --- a/crypto/aes/tests/sp800_38a_cfb8_tests.rs +++ b/crypto/aes/tests/sp800_38a_cfb8_tests.rs @@ -26,20 +26,19 @@ //! # Driving the IV //! //! There is no API for supplying an IV -- see the crate docs. Encryption is therefore driven -//! through [`SymmetricCipherEncryptor::do_encrypt_init_rng`] with a [`FixedSeedRNG`] whose stream is -//! the vector's IV, and the test asserts the returned init data really is that IV before comparing -//! any ciphertext. Decryption takes the IV directly, as init data. - -use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cfb8; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +//! through the key's [`SymmetricEncryptionKey::create_encryptor`] with a [`FixedSeedRNG`] whose +//! stream is the vector's IV, and the test asserts the returned init data really is that IV before +//! comparing any ciphertext. Decryption takes the IV directly, as init data. + +use bouncycastle_aes::hazmat::AES128Internal; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CFB8; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; @@ -135,22 +134,20 @@ 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: 'static, + K: FromKeyMaterial + SymmetricEncryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); 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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let (mut enc, got_iv) = key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); assert_eq!(got_iv, iv, "{section}: the pinned RNG should produce the vector's IV"); let mut data = plaintext.clone(); @@ -162,18 +159,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: 'static, + K: FromKeyMaterial + SymmetricDecryptionKey, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); 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(); + let mut dec = key.create_decryptor(&iv); let mut data = ciphertext.clone(); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -183,38 +181,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(); + key.decrypt_inplace(&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::, 16>("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::, 16>("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::, 24>("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::, 24>("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::, 32>("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::, 32>("F.3.12", KEY_256, CIPHERTEXT_256); } /// The spec's tabulated **Input Blocks** are the shift register and its **Output Blocks** are diff --git a/crypto/aes/tests/sp800_38a_cfb_tests.rs b/crypto/aes/tests/sp800_38a_cfb_tests.rs index a9e316f9f..6521efb60 100644 --- a/crypto/aes/tests/sp800_38a_cfb_tests.rs +++ b/crypto/aes/tests/sp800_38a_cfb_tests.rs @@ -28,20 +28,19 @@ //! # Driving the IV //! //! There is no API for supplying an IV -- see the crate docs. Encryption is therefore driven -//! through [`SymmetricCipherEncryptor::do_encrypt_init_rng`] with a [`FixedSeedRNG`] whose stream is -//! the vector's IV, and the test asserts the returned init data really is that IV before comparing -//! any ciphertext. Decryption takes the IV directly, as init data. +//! through the key's [`SymmetricEncryptionKey::create_encryptor`] with a [`FixedSeedRNG`] whose +//! stream is the vector's IV, and the test asserts the returned init data really is that IV before +//! comparing any ciphertext. Decryption takes the IV directly, as init data. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::Cfb; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::CFB; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; @@ -136,21 +135,19 @@ 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: 'static, + K: FromKeyMaterial + SymmetricEncryptionKey, + for<'k> K::Encryptor<'k>: StreamCipherEncryptor, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(expected); let init = || { - let (enc, got_iv) = Cfb::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let (enc, got_iv) = key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); assert_eq!(got_iv, iv, "{section}: the pinned RNG should produce the vector's IV"); enc }; @@ -185,25 +182,25 @@ 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: 'static, + K: FromKeyMaterial + SymmetricDecryptionKey, + for<'k> K::Decryptor<'k>: StreamCipherDecryptor, { - let key = key_material::(key_hex); + let key = K::from_key_material(&key_material::(key_hex)).expect("a valid key"); let iv = block(IV); let pt = blocks(&PLAINTEXTS); let ct = blocks(ciphertext); - 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 = key.create_decryptor(&iv); 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 = key.create_decryptor(&iv); for (i, (c, p)) in ct.iter().zip(pt.iter()).enumerate() { let mut got = *c; dec.do_decrypt_inplace(&mut got).unwrap(); @@ -211,7 +208,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 = key.create_decryptor(&iv); 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 +218,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 = key.create_decryptor(&iv); let mut data = flat(ciphertext); for piece in data.chunks_mut(chunk) { dec.do_decrypt_inplace(piece).unwrap(); @@ -232,32 +229,32 @@ where #[test] fn f_3_13_cfb128_aes128_encrypt() { - check_encrypt::("F.3.13", KEY_128, &CIPHERTEXTS_128); + check_encrypt::, 16>("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::, 16>("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::, 24>("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::, 24>("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::, 32>("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::, 32>("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. @@ -283,7 +280,7 @@ fn check_output_blocks( ciphertexts: &[&str; 4], output_blocks: &[&str; 4], ) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook, { let key = key_material::(key_hex); let perm = P::new(&key).expect("a valid key"); @@ -348,13 +345,9 @@ 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 = AES128Key::::from_key_material(&key_material::<16>(KEY_128)).unwrap(); let iv = block(IV); - let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let (mut enc, got_iv) = key.create_encryptor(&mut FixedSeedRNG::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..11a59e12a 100644 --- a/crypto/aes/tests/sp800_38a_ecb_tests.rs +++ b/crypto/aes/tests/sp800_38a_ecb_tests.rs @@ -18,7 +18,7 @@ use bouncycastle_aes::AES_BLOCK_LEN; use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_hex as hex; diff --git a/crypto/aes/tests/sp800_38c_tests.rs b/crypto/aes/tests/sp800_38c_tests.rs index 64df33527..c0b49a00e 100644 --- a/crypto/aes/tests/sp800_38c_tests.rs +++ b/crypto/aes/tests/sp800_38c_tests.rs @@ -17,30 +17,55 @@ //! decryption-verification process of CCM is straightforward to construct"), so the decryption //! direction is checked by round-tripping each vector's own `C` back to its `P`. -use bouncycastle_aes::AES128Key; use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::{CCM, CcmDecryptor, CcmEncryptor, ModeNames}; +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{ + CCM, CCM_Packet, CcmPacketDecryptor, CcmPacketEncryptor, ModeNames, +}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::{AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook}; +use bouncycastle_core::hazmat::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, +}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::symmetric_key::FromKeyMaterial; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, + FromKeyMaterial, +}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_hex as hex; use bouncycastle_rng::FixedSeedRNG; +use bouncycastle_rng::HashDRBG_SHA512; /// 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 { +fn material(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") } +/// The appendix key bound to the fixed-frame pair: its streams are [`CcmPacketEncryptor`] / +/// [`CcmPacketDecryptor`] at `(NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN)`, and its one-shots take exactly +/// `DATA_LEN` bytes. +type FrameKey = + AES128Key>; + +fn frame_key() +-> FrameKey { + FrameKey::from_key_material(&material::<16>(APPENDIX_C_KEY)).expect("the appendix key") +} + +fn os_rng() -> HashDRBG_SHA512 { + HashDRBG_SHA512::new_from_os() +} + fn buffer_len_error(r: Result) -> Option { match r { Err(SymmetricCipherError::OutputBufferTooSmall(needed)) => Some(needed), @@ -49,17 +74,17 @@ fn buffer_len_error(r: Result) -> Option { } /// Drives one Appendix C example through every entry point: the mode's in-place one-shots, the -/// fixed-frame pair's any-length one-shots in the appendix's own inline layout, and, if `sweep`, -/// the pair's streams at the example's frame `DATA_LEN` in several chunkings. +/// fixed-frame key's one-shots in the appendix's own inline layout, and, if `sweep`, the key's +/// streams in several chunkings. The frame is the example's own `Alen` and `Plen`, since the +/// key's one-shots are bound to it; the appendix is AES-128 only. /// /// `c` is the appendix's whole `C` string; it is split at `plaintext.len()` into the ciphertext and /// the tag, so a mistake in either half is caught, and so is a mistake in where the split belongs. fn check_vector< - const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, + const AAD_LEN: usize, const DATA_LEN: usize, - P: ElectronicCodeBook + ModeNames + Clone, >( name: &str, key_hex: &str, @@ -72,16 +97,15 @@ fn check_vector< sweep: bool, ) { type Mode = CCM; - type Enc = - CcmEncryptor; - type Dec = - CcmDecryptor; - let k = key::(key_hex); - let perm = P::new(&k).expect("the appendix key"); + let k = material::<16>(key_hex); + let perm = AES128Internal::new(&k).expect("the appendix key"); + let key = FrameKey::::from_key_material(&k) + .expect("the appendix key"); 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"); + assert_eq!(aad.len(), AAD_LEN, "{name}: the frame's AAD capacity is the example's Alen"); assert_eq!(plaintext.len(), DATA_LEN, "{name}: the frame is the example's Plen"); let c = hex::decode(c_hex).expect("valid hex C"); @@ -104,19 +128,14 @@ fn check_vector< .expect("decryption"); assert_eq!(buf, plaintext, "{name}: detached round trip"); - // --- the appendix's own inline `ciphertext || tag` layout, through the pair's one-shots --- + // --- the appendix's own inline `ciphertext || tag` layout, through the key's one-shots --- let mut inline = vec![0u8; plaintext.len() + TAG_LEN]; - let n = Enc::::encrypt_with_aad_nonce_out( - &k, &nonce, aad, &plaintext, &mut inline, - ) - .expect("encryption"); + let n = + key.encrypt_with_aad_nonce_out(&nonce, aad, &plaintext, &mut inline).expect("encryption"); assert_eq!(n, c.len(), "{name}: inline output length"); assert_eq!(inline, c, "{name}: the whole C string of Appendix C"); let mut recovered = vec![0u8; plaintext.len()]; - let n = Dec::::decrypt_with_aad_out( - &k, &nonce, aad, &c, &mut recovered, - ) - .expect("decryption"); + let n = key.decrypt_with_aad_out(&nonce, aad, &c, &mut recovered).expect("decryption"); assert_eq!(n, plaintext.len()); assert_eq!(recovered, plaintext, "{name}: inline round trip"); @@ -125,9 +144,7 @@ fn check_vector< // Sec 3 says CCM is not a streaming mode, and the pair handles that by fixing the frame in // the type; given that, the chunking must be invisible, exactly as for the other modes. for chunk in [1usize, 2, 3, 7, 16, 17] { - let mut enc = - Enc::::do_encrypt_init_nonce(&k, &nonce) - .expect("streaming init"); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(aad).expect("aad"); let mut streamed = vec![0u8; DATA_LEN]; let mut written = 0; @@ -138,9 +155,7 @@ fn check_vector< assert_eq!(streamed, want_ct, "{name}: ciphertext, streamed in {chunk}-byte chunks"); assert_eq!(&streamed_tag, want_tag, "{name}: tag, streamed in {chunk}-byte chunks"); - let mut dec = - Dec::::do_decrypt_init(&k, &nonce) - .expect("streaming init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).expect("aad"); let mut opened = vec![0u8; DATA_LEN]; let mut written = 0; @@ -158,7 +173,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, 4, AES128Internal>( + check_vector::<7, 4, 8, 4>( "C.1", APPENDIX_C_KEY, "10111213141516", @@ -176,7 +191,7 @@ fn appendix_c1() { /// "minimum number of '0' bits, possibly none" is none. #[test] fn appendix_c2() { - check_vector::<16, 8, 6, 16, AES128Internal>( + check_vector::<8, 6, 16, 16>( "C.2", APPENDIX_C_KEY, "1011121314151617", @@ -194,7 +209,7 @@ fn appendix_c2() { /// the payload spans two counter blocks. #[test] fn appendix_c3() { - check_vector::<16, 12, 8, 24, AES128Internal>( + check_vector::<12, 8, 20, 24>( "C.3", APPENDIX_C_KEY, "101112131415161718191a1b", @@ -226,7 +241,7 @@ fn appendix_c4() { } assert_eq!(aad.len(), 65536, "Alen = 524288 bits"); - check_vector::<16, 13, 14, 32, AES128Internal>( + check_vector::<13, 14, 65536, 32>( "C.4", APPENDIX_C_KEY, "101112131415161718191a1b1c", @@ -254,74 +269,44 @@ fn framework(data_len: usize) -> TestFrameworkAEADCipher { framework } -/// The whole [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] contract, through the shared -/// framework, for the fixed-frame [`CcmEncryptor`] / [`CcmDecryptor`] pair. +/// The whole [`AeadEncryptionKey`] / [`AeadDecryptionKey`] contract, through the shared +/// framework, for a key bound to [`CCM_Packet`], whose streams are the fixed-frame +/// [`CcmPacketEncryptor`] / [`CcmPacketDecryptor`] pair. /// /// `DATA_LEN` is 240, well above the few multiples of the tag the suite's one-shots try, so the -/// one-shots are exercised at every length up to it and the streaming methods at exactly it. -/// `AAD_LEN` is 64, above the suite's 20-byte AAD. `FINAL_LEN` is the tag. +/// one-shots and the streaming methods are exercised at exactly it and refused at every other +/// length up to it. `AAD_LEN` is 64, above the suite's 20-byte AAD. `FINAL_LEN` is the tag. #[test] fn framework_streaming_contract() { - framework(240).test_encryptor_decryptor::< - 16, - 12, - 16, - 16, - CcmEncryptor, - CcmDecryptor, - >(); + framework(240).test_key::<16, 12, 16, 16, _>(&frame_key::<12, 16, 64, 240>(), &mut os_rng()); } -/// The same, for the other two AES key lengths and a short tag, so the framework's error and -/// key-policy checks run against every parameterization the CLI and the aliases expose. +/// The same, for the other two AES key lengths and a short tag, so the framework's error checks +/// run against every parameterization the CLI and the aliases expose. #[test] fn framework_streaming_contract_other_parameter_sets() { - framework(240).test_encryptor_decryptor::< - 24, - 12, - 16, - 16, - CcmEncryptor, - CcmDecryptor, - >(); - framework(240).test_encryptor_decryptor::< - 32, - 12, - 16, - 16, - CcmEncryptor, - CcmDecryptor, - >(); + let mut rng = os_rng(); + framework(240).test_key::<24, 12, 16, 16, _>( + &AES192Key::>::generate(&mut rng).expect("a generated key"), + &mut rng, + ); + framework(240).test_key::<32, 12, 16, 16, _>( + &AES256Key::>::generate(&mut rng).expect("a generated key"), + &mut rng, + ); // 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. - framework(240).test_encryptor_decryptor::< - 16, - 13, - 8, - 8, - CcmEncryptor, - CcmDecryptor, - >(); + framework(240).test_key::<16, 13, 8, 8, _>(&frame_key::<13, 8, 64, 240>(), &mut rng); // The empty frame: a message that is nothing but its AAD and tag. - framework(0).test_encryptor_decryptor::< - 16, - 12, - 16, - 16, - CcmEncryptor, - CcmDecryptor, - >(); + framework(0).test_key::<16, 12, 16, 16, _>(&frame_key::<12, 16, 64, 0>(), &mut rng); } /// The fixed-frame pair's streams reproduce Appendix C.3 byte for byte, driven with the -/// appendix's nonce through `do_encrypt_init_rng` and a fixed-output RNG. C.3's payload is 24 +/// appendix's nonce through `create_encryptor` and a fixed-output RNG. C.3's payload is 24 /// bytes 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; - - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 8, 20, 24>(); let nonce_bytes = hex::decode("101112131415161718191a1b").unwrap(); let aad = hex::decode("000102030405060708090a0b0c0d0e0f10111213").unwrap(); let plaintext = hex::decode("202122232425262728292a2b2c2d2e2f3031323334353637").unwrap(); @@ -329,11 +314,11 @@ fn the_fixed_frame_pair_agrees_with_the_direct_api_on_appendix_c3() { hex::decode("e3b201a9f5b71a7a9b1ceaeccd97e70b6176aad9a4428aa5484392fbc1b09951").unwrap(); let (want_ct, want_tag) = c.split_at(plaintext.len()); - // The trait generates the nonce; feed it Appendix C.3's so the answer is comparable, and check - // it came back, so an implementation that ignored the RNG could not pass silently. + // The key draws the nonce; feed it Appendix C.3's so the answer is comparable, and check it + // came back, so an implementation that ignored the RNG could not pass silently. let nonce_seed: [u8; 12] = nonce_bytes.clone().try_into().expect("12-byte nonce"); let mut rng = FixedSeedRNG::new(nonce_seed); - let (mut enc, nonce) = Enc::do_encrypt_init_rng(&k, &mut rng).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut rng).expect("init"); assert_eq!(&nonce[..], &nonce_bytes[..], "the generated nonce must come from the RNG"); // Chunk both phases, and check `update_out_len`'s promise that every byte is released at once. @@ -353,7 +338,7 @@ fn the_fixed_frame_pair_agrees_with_the_direct_api_on_appendix_c3() { assert_eq!(&ct[..], want_ct, "C.3 ciphertext via the trait"); assert_eq!(&tag[..], want_tag, "C.3 tag via the trait"); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(&aad).expect("aad"); let mut pt = Vec::new(); for piece in want_ct.chunks(5) { @@ -373,14 +358,14 @@ fn the_fixed_frame_pair_agrees_with_the_direct_api_on_appendix_c3() { // `ciphertext || tag`, with the tag as the final's output, and the decryptor takes the tag // back off its end. let mut rng = FixedSeedRNG::new(nonce_seed); - let (mut enc, nonce) = Enc::do_encrypt_init_rng(&k, &mut rng).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut rng).expect("init"); enc.do_update_aad(&aad).expect("aad"); let mut inline = vec![0u8; 24]; enc.do_encrypt_out(&plaintext, &mut inline).expect("update"); let (last, last_len) = enc.do_encrypt_final().expect("final"); inline.extend_from_slice(&last[..last_len]); assert_eq!(&inline[..], &c[..], "C.3 `C` via the inline do_encrypt_final"); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(&aad).expect("aad"); let mut pt = Vec::new(); for piece in c.chunks(5) { @@ -398,11 +383,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); + let key = frame_key::<12, 16, 32, 32>(); - let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut os_rng()).expect("init"); let mut out = [0xEEu8; 33]; match enc.do_encrypt_out(&[0u8; 33], &mut out) { Err(SymmetricCipherError::StateError(msg)) => assert!( @@ -414,14 +397,14 @@ fn the_adapters_refuse_more_than_the_declared_lengths() { assert_eq!(out, [0u8; 33], "a refused update must leave the output buffer zeroed"); // In two calls that together overflow, the first must succeed and the second be refused. - let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut os_rng()).expect("init"); assert_eq!(enc.do_encrypt_out(&[0u8; 20], &mut out).expect("fits"), 20); assert!(matches!( enc.do_encrypt_out(&[0u8; 13], &mut out), Err(SymmetricCipherError::StateError(_)) )); - let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut os_rng()).expect("init"); match enc.do_update_aad(&[0u8; 33]) { Err(SymmetricCipherError::GenericError(msg)) => { assert!(msg.contains("AAD_LEN"), "the refusal must name AAD_LEN, got: {msg}") @@ -431,7 +414,7 @@ fn the_adapters_refuse_more_than_the_declared_lengths() { // The decryptor's bound is `DATA_LEN + TAG_LEN`, the frame with an inline tag. let nonce = [0x24u8; 12]; - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); let mut pt = [0xEEu8; 49]; match dec.do_decrypt_out(&[0u8; 49], &mut pt) { Err(SymmetricCipherError::StateError(msg)) => assert!( @@ -441,13 +424,13 @@ fn the_adapters_refuse_more_than_the_declared_lengths() { other => panic!("expected StateError, got {other:?}"), } assert_eq!(pt, [0u8; 49], "a refused update must leave the output buffer zeroed"); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); assert_eq!(dec.do_decrypt_out(&[0u8; 40], &mut pt).expect("fits"), 32); assert!(matches!( dec.do_decrypt_out(&[0u8; 9], &mut pt), Err(SymmetricCipherError::StateError(_)) )); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); assert!(matches!(dec.do_update_aad(&[0u8; 33]), Err(SymmetricCipherError::GenericError(_)))); } @@ -458,13 +441,11 @@ 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); + let key = frame_key::<12, 16, 48, 7>(); let aad = b"header"; let message = b"payload"; - let (mut enc, nonce) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).expect("init"); let mut sealed = [0u8; 7]; enc.do_encrypt_out(&[], &mut sealed).expect("an empty update is a no-op"); enc.do_update_aad(aad).expect("the AAD phase is still open after an empty update"); @@ -479,13 +460,12 @@ fn an_empty_update_does_not_close_the_aad_phase() { // The AAD really was absorbed: the one-shot 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 = - Enc::encrypt_with_aad_nonce_out(&k, &nonce, aad, message, &mut expected).expect("one-shot"); + let n = key.encrypt_with_aad_nonce_out(&nonce, aad, message, &mut expected).expect("one-shot"); assert_eq!(n, 23); assert_eq!(&sealed[..], &expected[..7]); assert_eq!(&tag[..], &expected[7..]); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); let mut opened = [0u8; 7]; dec.do_decrypt_out(&[], &mut opened).expect("an empty update is a no-op"); dec.do_update_aad(aad).expect("the AAD phase is still open after an empty update"); @@ -505,23 +485,19 @@ 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); + let key = frame_key::<12, 16, 32, 32>(); let aad = [0x11u8; 32]; let message = [0x5Au8; 32]; let nonce_seed = [0x24u8; 12]; - let (mut enc, nonce) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::new(nonce_seed)).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut FixedSeedRNG::new(nonce_seed)).expect("init"); enc.do_update_aad(&aad).expect("AAD exactly filling the capacity is accepted"); let mut sealed = [0u8; 48]; assert_eq!(enc.do_encrypt_out(&message, &mut sealed).expect("exactly DATA_LEN"), 32); let (tag, _) = enc.do_encrypt_final().expect("final"); sealed[32..].copy_from_slice(&tag); - let (mut enc, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::new(nonce_seed)).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::new(nonce_seed)).expect("init"); enc.do_update_aad(&aad[..20]).expect("part"); enc.do_update_aad(&aad[20..]).expect("exactly fills the remaining capacity"); let mut split = [0u8; 48]; @@ -531,14 +507,14 @@ fn exact_lengths_are_accepted_whole_and_split() { split[32..].copy_from_slice(&tag); assert_eq!(split, sealed, "the chunking must not change the answer"); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(&aad).expect("aad"); let mut opened = [0u8; 32]; assert_eq!(dec.do_decrypt_out(&sealed, &mut opened).expect("the whole frame"), 32); dec.do_decrypt_final().expect("tag check"); assert_eq!(opened, message); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(&aad[..20]).expect("part"); dec.do_update_aad(&aad[20..]).expect("the rest"); let mut opened = [0u8; 32]; @@ -558,12 +534,10 @@ 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); + let key = frame_key::<12, 16, 8, 64>(); // AAD past AAD_LEN is refused, although it would fit in DATA_LEN. - let (mut enc, _) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut os_rng()).expect("init"); assert!(matches!(enc.do_update_aad(&[0u8; 9]), Err(SymmetricCipherError::GenericError(_)))); // A full AAD_LEN of AAD and a full DATA_LEN of payload together, far more than AAD_LEN alone, @@ -571,7 +545,7 @@ fn the_aad_capacity_and_the_payload_length_are_independent() { // none, each agreeing with the direct API. let message = [0x5Au8; 64]; for aad in [&[0x11u8; 8][..], &[0x11u8; 3][..], &[][..]] { - let (mut enc, nonce) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).expect("init"); enc.do_update_aad(aad).expect("within AAD_LEN"); let mut sealed = [0u8; 64]; enc.do_encrypt_out(&message, &mut sealed).expect("exactly DATA_LEN"); @@ -579,11 +553,10 @@ fn the_aad_capacity_and_the_payload_length_are_independent() { let mut direct = [0u8; 64]; let (_, direct_tag) = - Enc::encrypt_detached_nonce_out(&k, &nonce, aad, &message, &mut direct) - .expect("one-shot"); + key.encrypt_detached_nonce_out(&nonce, aad, &message, &mut direct).expect("one-shot"); assert_eq!((sealed, tag), (direct, direct_tag), "aad of {} bytes", aad.len()); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).expect("within AAD_LEN"); let mut opened = [0u8; 64]; dec.do_decrypt_out(&sealed, &mut opened).expect("exactly DATA_LEN"); @@ -597,19 +570,17 @@ 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); + let key = frame_key::<12, 16, 32, 32>(); let message = [0x5Au8; 32]; - let (mut enc, nonce) = Enc::do_encrypt_init(&k).expect("init"); + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).expect("init"); let mut inline = [0u8; 48]; enc.do_encrypt_out(&message, &mut inline).expect("the frame"); let (tag, _) = enc.do_encrypt_final().expect("final"); inline[32..].copy_from_slice(&tag); // Inline: 40 bytes release the 32 of payload and hold 8 of tag; the last 8 release nothing. - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); let mut out = [0u8; 32]; assert_eq!(dec.do_decrypt_out_len(40), 32); assert_eq!( @@ -622,7 +593,7 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { assert_eq!(n, 0, "nothing is left to release"); // One byte past the frame with its tag is refused, and the final still verifies. - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_decrypt_out(&inline, &mut out).expect("the whole frame"); assert!(matches!( dec.do_decrypt_out(&[0u8; 1], &mut []), @@ -632,7 +603,7 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { // Detached, the 16 bytes held back after the payload have nowhere to go: the frame is // exactly DATA_LEN, so this `C` is malformed. - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_decrypt_out(&inline, &mut out).expect("the whole frame"); let mut nothing = [0xEEu8; 16]; assert!(matches!( @@ -642,7 +613,7 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { assert_eq!(nothing, [0u8; 16], "the detached final only zeroes its buffer"); // ...and exactly DATA_LEN is the detached frame. - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); let mut out = [0u8; 32]; dec.do_decrypt_out(&inline[..32], &mut out).expect("the frame"); assert_eq!(dec.do_decrypt_final_detached_out(&tag, &mut nothing).expect("tag check"), 0); @@ -650,93 +621,127 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { assert_eq!(out, message); } -/// The one-shots take the whole message, so `DATA_LEN` and `AAD_LEN` do not bind them: at the -/// frame's length they agree with the mode's in-place one-shot byte for byte, and at any other -/// length, with AAD past the capacity, they still round trip and agree, on every one of the nine -/// forms -- detached and inline, with and without a caller's RNG, with and without AAD, and the -/// three decrypting forms. A failed tag leaves the output zeroized. The streaming methods stay -/// bound; see `the_adapters_refuse_more_than_the_declared_lengths`. +/// The frame key's one-shots are written over its streams, so `DATA_LEN` and `AAD_LEN` bind +/// them: at the frame's length, with AAD at and under the capacity, they agree with the declared-length +/// key's in-place one-shots byte for byte, on every form -- detached and inline, drawing and +/// supplied-nonce, with and without AAD, and the three decrypting forms -- and at any other +/// length, or with AAD past the capacity, every form is refused. A failed tag leaves the output +/// zeroized. The declared-length key, [`CCM`], is the one that takes any length. #[test] -fn trait_one_shots_take_any_length_and_agree_with_the_in_place_one_shot() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); - let perm = AES128Internal::new(&k).unwrap(); +fn packet_key_one_shots_take_the_frame_and_agree_with_the_declared_length_key() { + let key = frame_key::<12, 16, 48, 48>(); + let declared = AES128Key::>::from_key_material(&material::<16>(APPENDIX_C_KEY)) + .expect("the appendix key"); let rng = || FixedSeedRNG::new([0x24u8; 12]); - // The frame, one byte either side of it, nothing, and well past it; AAD at, under and past - // the capacity. - for (msg_len, aad_len) in [(48usize, 48usize), (47, 0), (49, 49), (0, 5), (1000, 200)] { + // The frame, with AAD at and under the capacity, and with none. + for (msg_len, aad_len) in [(48usize, 48usize), (48, 3), (48, 0)] { let msg = vec![0xA5u8; msg_len]; let aad = vec![0x3Cu8; aad_len]; let what = format!("{msg_len}-byte payload, {aad_len}-byte AAD"); let mut detached = vec![0u8; msg_len]; - let (nonce, written, tag) = - Enc::encrypt_detached_rng_out(&k, &mut rng(), &aad, &msg, &mut detached) - .unwrap_or_else(|e| panic!("{what}: {e:?}")); + let (written, nonce, tag) = key + .encrypt_detached_out(&mut rng(), &aad, &msg, &mut detached) + .unwrap_or_else(|e| panic!("{what}: {e:?}")); assert_eq!(written, msg_len); let mut direct = msg.clone(); let direct_tag = - CCM::<12, 16>::encrypt_detached_in_place_nonce(&perm, &nonce, &aad, &mut direct) - .expect("in place"); + declared.encrypt_detached_in_place_nonce(&nonce, &aad, &mut direct).expect("in place"); assert_eq!((&detached, tag), (&direct, direct_tag), "{what}: detached agrees"); let mut inline = vec![0u8; msg_len + 16]; - let (nonce_i, written_i) = - Enc::encrypt_with_aad_rng_out(&k, &mut rng(), &aad, &msg, &mut inline) - .unwrap_or_else(|e| panic!("{what}: {e:?}")); + let (written_i, nonce_i) = key + .encrypt_with_aad_out(&mut rng(), &aad, &msg, &mut inline) + .unwrap_or_else(|e| panic!("{what}: {e:?}")); assert_eq!((nonce_i, written_i), (nonce, msg_len + 16)); assert_eq!(&inline[..msg_len], &detached[..], "{what}: inline is detached plus tag"); assert_eq!(&inline[msg_len..], &tag); let mut pt = vec![0xEEu8; msg_len]; assert_eq!( - Dec::decrypt_detached_out(&k, &nonce, &aad, &detached, &tag, &mut pt).expect("open"), + key.decrypt_detached_out(&nonce, &aad, &detached, &tag, &mut pt).expect("open"), msg_len ); assert_eq!(pt, msg, "{what}: detached round trip"); pt.fill(0xEE); assert_eq!( - Dec::decrypt_with_aad_out(&k, &nonce, &aad, &inline, &mut pt).expect("open"), + key.decrypt_with_aad_out(&nonce, &aad, &inline, &mut pt).expect("open"), msg_len ); assert_eq!(pt, msg, "{what}: inline round trip"); - - // The default-DRBG forms and the no-AAD forms, each opened by the matching decrypt. - let (n2, w2, t2) = Enc::encrypt_detached_out(&k, &aad, &msg, &mut detached).unwrap(); - assert_eq!(w2, msg_len); - Dec::decrypt_detached_out(&k, &n2, &aad, &detached, &t2, &mut pt).expect("open"); - let (n3, w3) = Enc::encrypt_with_aad_out(&k, &aad, &msg, &mut inline).unwrap(); + declared.decrypt_detached_in_place(&nonce, &aad, &mut direct, &tag).expect("in place"); + assert_eq!(direct, msg, "{what}: in-place round trip"); + + // The supplied-nonce forms, each opened by the matching decrypt. + let (w2, t2) = key.encrypt_detached_nonce_out(&nonce, &aad, &msg, &mut detached).unwrap(); + assert_eq!((w2, t2), (msg_len, tag)); + key.decrypt_detached_out(&nonce, &aad, &detached, &t2, &mut pt).expect("open"); + let w3 = key.encrypt_with_aad_nonce_out(&nonce, &aad, &msg, &mut inline).unwrap(); assert_eq!(w3, msg_len + 16); - Dec::decrypt_with_aad_out(&k, &n3, &aad, &inline, &mut pt).expect("open"); - let (n4, w4) = Enc::encrypt_rng_out(&k, &mut rng(), &msg, &mut inline).unwrap(); - assert_eq!((n4, w4), (nonce, msg_len + 16)); - assert_eq!(Dec::decrypt_out_len(w4), msg_len); - pt.fill(0xEE); - assert_eq!(Dec::decrypt_out(&k, &n4, &inline, &mut pt).expect("open"), msg_len); - assert_eq!(pt, msg, "{what}: no-AAD round trip"); - let (n5, w5) = Enc::encrypt_out(&k, &msg, &mut inline).unwrap(); - assert_eq!(w5, msg_len + 16); - Dec::decrypt_out(&k, &n5, &inline, &mut pt).expect("open"); + key.decrypt_with_aad_out(&nonce, &aad, &inline, &mut pt).expect("open"); + } + + // One byte either side of the frame, nothing, well past it, and AAD past the capacity: every + // form refuses, and the declared-length key does not. + for (msg_len, aad_len) in [(47usize, 0usize), (49, 0), (0, 5), (1000, 200), (48, 49)] { + let msg = vec![0xA5u8; msg_len]; + let aad = vec![0x3Cu8; aad_len]; + let what = format!("{msg_len}-byte payload, {aad_len}-byte AAD"); + let nonce = [0x24u8; 12]; + + let mut detached = vec![0u8; msg_len]; + assert!( + key.encrypt_detached_out(&mut rng(), &aad, &msg, &mut detached).is_err(), + "{what}: encrypt_detached_out" + ); + assert!( + key.encrypt_detached_nonce_out(&nonce, &aad, &msg, &mut detached).is_err(), + "{what}: encrypt_detached_nonce_out" + ); + let mut inline = vec![0u8; msg_len + 16]; + assert!( + key.encrypt_with_aad_out(&mut rng(), &aad, &msg, &mut inline).is_err(), + "{what}: encrypt_with_aad_out" + ); + assert!( + key.encrypt_with_aad_nonce_out(&nonce, &aad, &msg, &mut inline).is_err(), + "{what}: encrypt_with_aad_nonce_out" + ); + + let mut direct = msg.clone(); + let tag = declared + .encrypt_detached_in_place_nonce(&nonce, &aad, &mut direct) + .unwrap_or_else(|e| panic!("{what}: the declared-length key takes any length: {e:?}")); + let n = declared.encrypt_with_aad_nonce_out(&nonce, &aad, &msg, &mut inline).unwrap(); + assert_eq!(n, msg_len + 16); + let mut pt = vec![0xEEu8; msg_len]; + assert!( + key.decrypt_detached_out(&nonce, &aad, &direct, &tag, &mut pt).is_err(), + "{what}: decrypt_detached_out" + ); + assert!( + key.decrypt_with_aad_out(&nonce, &aad, &inline, &mut pt).is_err(), + "{what}: decrypt_with_aad_out" + ); } // A failed tag leaves nothing behind, on all three decrypting forms. let msg = [0xA5u8; 48]; let mut inline = [0u8; 64]; - let (nonce, _) = Enc::encrypt_with_aad_out(&k, b"hdr", &msg, &mut inline).unwrap(); + let (_, nonce) = key.encrypt_with_aad_out(&mut os_rng(), b"hdr", &msg, &mut inline).unwrap(); let mut forged = inline; forged[0] ^= 1; let mut pt = [0xEEu8; 48]; assert!(matches!( - Dec::decrypt_with_aad_out(&k, &nonce, b"hdr", &forged, &mut pt), + key.decrypt_with_aad_out(&nonce, b"hdr", &forged, &mut pt), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(pt, [0u8; 48]); pt.fill(0xEE); assert!( matches!( - Dec::decrypt_out(&k, &nonce, &inline, &mut pt), + key.decrypt_with_aad_out(&nonce, &[], &inline, &mut pt), Err(SymmetricCipherError::AEADTagCheckFailed) ), "the AAD is missing" @@ -744,10 +749,16 @@ fn trait_one_shots_take_any_length_and_agree_with_the_in_place_one_shot() { assert_eq!(pt, [0u8; 48]); pt.fill(0xEE); assert!(matches!( - Dec::decrypt_detached_out(&k, &nonce, b"hdr", &inline[..48], &[0u8; 16], &mut pt), + key.decrypt_detached_out(&nonce, b"hdr", &inline[..48], &[0u8; 16], &mut pt), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(pt, [0u8; 48]); + let mut buf = inline[..48].to_vec(); + assert!(matches!( + declared.decrypt_detached_in_place(&nonce, b"hdr", &mut buf, &[0u8; 16]), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); + assert_eq!(buf, [0u8; 48]); } /// `B0` commits to `DATA_LEN`, so every final must check that exactly that much payload was @@ -762,17 +773,14 @@ fn trait_one_shots_take_any_length_and_agree_with_the_in_place_one_shot() { /// 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); + let key = frame_key::<12, 16, 16, 8>(); let aad = b"header"; let frame = [0x5Au8; 8]; let nonce_seed = [0x24u8; 12]; // An encryptor fed `supplied` of the 8 declared bytes. let enc = |supplied: usize| { - let (mut enc, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::new(nonce_seed)).expect("init"); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::new(nonce_seed)).expect("init"); enc.do_update_aad(aad).expect("aad"); let mut out = [0u8; 8]; enc.do_encrypt_out(&frame[..supplied], &mut out).expect("update"); @@ -824,8 +832,7 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { assert_eq!((n, tag3), (0, tag)); let mut ct = [0u8; 8]; { - let (mut e, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::new(nonce_seed)).expect("init"); + let (mut e, _) = key.create_encryptor(&mut FixedSeedRNG::new(nonce_seed)).expect("init"); e.do_update_aad(aad).expect("aad"); e.do_encrypt_out(&frame, &mut ct).expect("update"); e.do_encrypt_final().expect("final"); @@ -836,7 +843,7 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { // A decryptor fed the first `supplied` bytes of `inline`. let dec = |supplied: usize| { - let mut dec = Dec::do_decrypt_init(&k, &nonce_seed).expect("init"); + let mut dec = key.create_decryptor(&nonce_seed); dec.do_update_aad(aad).expect("aad"); let mut out = [0u8; 8]; dec.do_decrypt_out(&inline[..supplied], &mut out).expect("update"); @@ -908,8 +915,8 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { /// A `C` of exactly `TAG_LEN` octets is *not* too short: it is the empty payload of Sec 5.3's /// footnote, and must authenticate. /// -/// Both inline entry points -- the decryptor's `decrypt_with_aad_out` and its `do_decrypt_final` -/// -- must report the same malformed input with the same variant, +/// Both inline entry points -- the key's `decrypt_with_aad_out` and the decryptor's +/// `do_decrypt_final` -- must report the same malformed input with the same variant, /// [`SymmetricCipherError::DecryptionFailed`], which is what /// [`SymmetricCipherDecryptor::do_decrypt_final`] specifies for a malformed ciphertext; a caller /// telling "malformed" from "inauthentic" must not get a different answer depending on which one it @@ -917,9 +924,7 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { #[test] fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { // The empty frame, so that every byte of a short `C` is a (missing) tag byte. - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 48, 0>(); let nonce = [0u8; 12]; let mut out = [0u8; 16]; let mut nothing = [0u8; 0]; @@ -928,12 +933,12 @@ fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { let short = vec![0u8; len]; assert!( matches!( - Dec::decrypt_with_aad_out(&k, &nonce, &[], &short, &mut out), + key.decrypt_with_aad_out(&nonce, &[], &short, &mut out), Err(SymmetricCipherError::DecryptionFailed) ), "a {len}-byte C cannot carry a 16-byte tag (decrypt_with_aad_out)" ); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_decrypt_out(&short, &mut nothing).expect("held back as a possible tag"); assert!( matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::DecryptionFailed)), @@ -943,32 +948,33 @@ fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { // Exactly TAG_LEN: an empty payload plus its tag, which must verify, on both. let mut inline = [0u8; 16]; - let n = Enc::encrypt_with_aad_nonce_out(&k, &nonce, &[], &[], &mut inline).expect("encryption"); + let n = key.encrypt_with_aad_nonce_out(&nonce, &[], &[], &mut inline).expect("encryption"); assert_eq!(n, 16); - assert_eq!( - Dec::decrypt_with_aad_out(&k, &nonce, &[], &inline, &mut out).expect("decryption"), - 0 - ); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + assert_eq!(key.decrypt_with_aad_out(&nonce, &[], &inline, &mut out).expect("decryption"), 0); + let mut dec = key.create_decryptor(&nonce); assert_eq!(dec.do_decrypt_out(&inline, &mut nothing).expect("the tag"), 0); assert_eq!(dec.do_decrypt_final().expect("an empty frame still verifies").1, 0); } -/// A truncated `C` on a frame that is not empty, where the streaming final and the one-shots -/// part ways. The streaming decryptor knows the frame: `do_decrypt_out` releases the payload as -/// it arrives, up to `DATA_LEN`, holds back only what follows as the tag, and the final reports -/// anything short of `DATA_LEN + TAG_LEN` as malformed. The one-shots take the whole message and -/// know no frame: a `C` shorter than the tag is malformed, and anything longer is a shorter -/// message whose tag does not verify. `decrypt_out_len` is `C` less a tag, as for any AEAD. +/// A truncated `C` on a frame that is not empty, where the frame key's stream and the declared-length +/// key's one-shots part ways. The streaming decryptor knows the frame: `do_decrypt_out` releases +/// the payload as it arrives, up to `DATA_LEN`, holds back only what follows as the tag, and the +/// final reports anything short of `DATA_LEN + TAG_LEN` as malformed. The declared-length key's one-shots +/// take the whole message and know no frame: a `C` shorter than the tag is malformed, and +/// anything longer is a shorter message whose tag does not verify. `decrypt_out_len` is `C` less +/// a tag, as for any AEAD. #[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<'k> = CcmPacketEncryptor<'k, AES128Internal, 16, 12, 16, 16, DATA_LEN>; + type Dec<'k> = CcmPacketDecryptor<'k, AES128Internal, 16, 12, 16, 16, DATA_LEN>; + let key = frame_key::<12, 16, 16, DATA_LEN>(); + let declared = AES128Key::>::from_key_material(&material::<16>(APPENDIX_C_KEY)) + .expect("the appendix 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"); + let (n, nonce) = + key.encrypt_with_aad_out(&mut os_rng(), b"hdr", &frame, &mut sealed).expect("seal"); assert_eq!(n, DATA_LEN + 16); assert_eq!(Dec::decrypt_out_len(DATA_LEN + 16), DATA_LEN); @@ -984,17 +990,11 @@ fn a_short_inline_ciphertext_is_rejected_the_same_way_for_a_non_empty_frame() { }; let mut pt = vec![0u8; Dec::decrypt_out_len(len)]; assert!( - one_shot_says(Dec::decrypt_with_aad_out(&k, &nonce, b"hdr", short, &mut pt)), + one_shot_says(declared.decrypt_with_aad_out(&nonce, b"hdr", short, &mut pt)), "a {len}-byte C is too short for a tag, or a shorter message with the wrong one \ (decrypt_with_aad_out)" ); - let mut pt = vec![0u8; Dec::decrypt_out_len(len)]; - assert!( - one_shot_says(Dec::decrypt_out(&k, &nonce, short, &mut pt)), - "a {len}-byte C is too short for a tag, or a shorter message with the wrong one \ - (decrypt_out)" - ); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"hdr").expect("aad"); let mut pt = vec![0u8; dec.do_decrypt_out_len(len)]; dec.do_decrypt_out(short, &mut pt).expect("the payload is released, the rest held"); @@ -1004,83 +1004,55 @@ fn a_short_inline_ciphertext_is_rejected_the_same_way_for_a_non_empty_frame() { ); } - // ...and the accepted length, through the same three, so the loop's bound is not off by one. + // ...and the accepted length, through both, so the loop's bound is not off by one. let mut pt = vec![0u8; Dec::decrypt_out_len(sealed.len())]; - assert_eq!(Dec::decrypt_with_aad_out(&k, &nonce, b"hdr", &sealed, &mut pt).expect("open"), 32); + assert_eq!(declared.decrypt_with_aad_out(&nonce, b"hdr", &sealed, &mut pt).expect("open"), 32); + assert_eq!(&pt[..], &frame[..]); + assert_eq!(key.decrypt_with_aad_out(&nonce, b"hdr", &sealed, &mut pt).expect("open"), 32); assert_eq!(&pt[..], &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 = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 0, 24>(); let nonce = [0u8; 12]; let plaintext = [0xAAu8; 24]; let mut too_small = [0u8; 23]; assert_eq!( - buffer_len_error(Enc::encrypt_detached_nonce_out( - &k, - &nonce, - &[], - &plaintext, - &mut too_small - )), + buffer_len_error(key.encrypt_detached_nonce_out(&nonce, &[], &plaintext, &mut too_small)), Some(24) ); let mut too_small = [0u8; 39]; assert_eq!( - buffer_len_error(Enc::encrypt_with_aad_nonce_out( - &k, - &nonce, - &[], - &plaintext, - &mut too_small - )), + buffer_len_error(key.encrypt_with_aad_nonce_out(&nonce, &[], &plaintext, &mut too_small)), Some(40) ); let mut ct = [0u8; 40]; - Enc::encrypt_with_aad_nonce_out(&k, &nonce, &[], &plaintext, &mut ct).expect("encryption"); + key.encrypt_with_aad_nonce_out(&nonce, &[], &plaintext, &mut ct).expect("encryption"); let mut too_small = [0u8; 23]; assert_eq!( - buffer_len_error(Dec::decrypt_with_aad_out(&k, &nonce, &[], &ct, &mut too_small)), + buffer_len_error(key.decrypt_with_aad_out(&nonce, &[], &ct, &mut too_small)), Some(24) ); } -/// A key of the wrong [`KeyType`] is rejected by every entry point, in both directions, and by -/// the mode-bound key's constructor. +/// The construction suite for both keys: the type, binding and strength policy of +/// `from_key_material`, which is the one place a key of the wrong [`KeyType`] is refused. #[test] -fn a_non_cipher_key_is_rejected() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - 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_nonce_out(&wrong, &[0u8; 12], &[], &[], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Enc::do_encrypt_init_nonce(&wrong, &[0u8; 12]), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::decrypt_with_aad_out(&wrong, &[0u8; 12], &[], &[0u8; 16], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::do_decrypt_init(&wrong, &[0u8; 12]), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - AES128Key::>::from_key_material(&wrong), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); +fn the_keys_enforce_the_material_policy() { + let framework = TestFrameworkFromKeyMaterial::new(); + framework.test::<16, AES128Key>>( + AES128Internal::CCM_ALG_NAME, + SecurityStrength::_128bit, + ); + framework.test::<16, FrameKey<12, 16, 0, 0>>( + AES128Internal::CCM_ALG_NAME, + SecurityStrength::_128bit, + ); } /// The direction is in the type, so the wrong direction's method is a **compile** error rather @@ -1092,18 +1064,16 @@ fn a_non_cipher_key_is_rejected() { /// `compile_fail` cannot express. #[test] fn each_direction_has_its_own_methods() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 8, 4>(); let nonce = [0x55u8; 12]; - let mut enc = Enc::do_encrypt_init_nonce(&k, &nonce).expect("encrypt init"); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(b"aad").expect("aad"); let mut data = [0u8; 4]; enc.do_encrypt_out(&[1u8, 2, 3, 4], &mut data).expect("encrypt update"); let (_, _, tag) = enc.do_encrypt_final_detached().expect("encrypt final"); - let mut dec = Dec::do_decrypt_init(&k, &nonce).expect("decrypt init"); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").expect("aad"); let mut back = [0u8; 4]; dec.do_decrypt_out(&data, &mut back).expect("decrypt update"); @@ -1113,36 +1083,36 @@ fn each_direction_has_its_own_methods() { // ---- memory ------------------------------------------------------------------------------ -/// Pins the sizes: the fixed-frame pair is the mode's packet state (264/296/328 B for -/// AES-128/192/256, independent of `NONCE_LEN`/`TAG_LEN`) plus the `AAD_LEN` buffer and a few -/// words of bookkeeping, independent of `DATA_LEN`. +/// Pins the sizes: the fixed-frame pair is the mode's `Ccm` state over a borrowed schedule (96 B +/// at every key length, independent of `NONCE_LEN`/`TAG_LEN`) plus the `AAD_LEN` buffer and a +/// few words of bookkeeping, independent of `DATA_LEN`. #[test] fn sizes_match_the_documented_memory_table() { use core::mem::size_of; // The 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, - "the packet state, the AAD buffer, its length and the phase flag" + 96 + 64 + 16, + "the `Ccm` state, 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" ); // 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!(enc_64, size_of::>()); - assert_eq!(enc_64, size_of::>()); - // The key schedule is the difference between the key lengths. - assert_eq!(size_of::>(), enc_64 + 32); - assert_eq!(size_of::>(), enc_64 + 64); + assert_eq!(enc_64, size_of::>()); + assert_eq!(enc_64, size_of::>()); + // The key schedule stays in the key, so the key length makes no difference. + assert_eq!(size_of::>(), enc_64); + assert_eq!(size_of::>(), enc_64); // 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); } @@ -1153,7 +1123,7 @@ fn sizes_match_the_documented_memory_table() { fn payload_longer_than_the_q_limit_is_refused() { type Mode = CCM<13, 14>; assert_eq!(Mode::MAX_PAYLOAD_LEN, 65535, "2^16 - 1 is the largest payload q = 2 can encode"); - let perm = AES128Internal::new(&key::<16>(APPENDIX_C_KEY)).unwrap(); + let perm = AES128Internal::new(&material::<16>(APPENDIX_C_KEY)).unwrap(); let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; let mut buf = vec![0u8; 65536]; assert!( @@ -1187,19 +1157,14 @@ fn check_progressive_aad< c: &str, chunk: usize, ) { - type Enc = - CcmEncryptor; - type Dec = - CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_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 want_tag: &[u8; TAG_LEN] = want_tag.try_into().unwrap(); - let mut enc = - Enc::::do_encrypt_init_nonce(&k, &nonce).unwrap(); + let mut enc = key.create_encryptor_nonce(&nonce); for piece in aad.chunks(chunk) { enc.do_update_aad(piece).unwrap(); } @@ -1209,8 +1174,7 @@ 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 = - Dec::::do_decrypt_init(&k, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); for piece in aad.chunks(chunk) { dec.do_update_aad(piece).unwrap(); } @@ -1254,22 +1218,19 @@ fn progressive_aad_matches_appendix_c() { /// The supplied-nonce forms take the caller's nonce to the same Sec 6.1 as the mode's in-place /// one-shot, so they agree with it byte for byte. They are also how a protocol with its own -/// nonce discipline reaches CCM through the generic traits: IEEE 802.11 CCMP's 13-byte nonce and -/// 8-byte tag sit below the 12-byte floor a generated nonce needs, and compile here because -/// nothing draws one. +/// nonce discipline reaches CCM through the key: IEEE 802.11 CCMP's 13-byte nonce and 8-byte +/// tag. #[test] fn supplied_nonce_forms_agree_with_the_in_place_one_shot_at_any_a1_nonce_length() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); - let perm = AES128Internal::new(&k).unwrap(); + let key = frame_key::<13, 8, 32, 48>(); + let perm = AES128Internal::new(&material::<16>(APPENDIX_C_KEY)).unwrap(); let nonce = [0x11u8; 13]; let aad = [0x3Cu8; 32]; let frame = [0xA5u8; 48]; let mut detached = [0u8; 48]; let (n, tag) = - Enc::encrypt_detached_nonce_out(&k, &nonce, &aad, &frame, &mut detached).expect("seal"); + key.encrypt_detached_nonce_out(&nonce, &aad, &frame, &mut detached).expect("seal"); assert_eq!(n, 48); let mut direct = frame; let direct_tag = @@ -1278,14 +1239,14 @@ fn supplied_nonce_forms_agree_with_the_in_place_one_shot_at_any_a1_nonce_length( assert_eq!((detached, tag), (direct, direct_tag), "the two routes to Sec 6.1 agree"); let mut inline = [0u8; 56]; assert_eq!( - Enc::encrypt_with_aad_nonce_out(&k, &nonce, &aad, &frame, &mut inline).expect("seal"), + key.encrypt_with_aad_nonce_out(&nonce, &aad, &frame, &mut inline).expect("seal"), 56 ); assert_eq!(&inline[..48], &detached); assert_eq!(&inline[48..], &tag); // The streaming constructor, on the frame. - let mut enc = Enc::do_encrypt_init_nonce(&k, &nonce).expect("init"); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(&aad).expect("aad"); let mut streamed = [0u8; 48]; assert_eq!(enc.do_encrypt_out(&frame, &mut streamed).expect("released"), 48); @@ -1295,16 +1256,13 @@ fn supplied_nonce_forms_agree_with_the_in_place_one_shot_at_any_a1_nonce_length( // Opened by the decryptor at the same parameters, and not under another nonce. let mut opened = [0u8; 48]; assert_eq!( - Dec::decrypt_detached_out(&k, &nonce, &aad, &detached, &tag, &mut opened).expect("open"), + key.decrypt_detached_out(&nonce, &aad, &detached, &tag, &mut opened).expect("open"), 48 ); assert_eq!(opened, frame); - assert_eq!( - Dec::decrypt_with_aad_out(&k, &nonce, &aad, &inline, &mut opened).expect("open"), - 48 - ); + assert_eq!(key.decrypt_with_aad_out(&nonce, &aad, &inline, &mut opened).expect("open"), 48); assert!(matches!( - Dec::decrypt_detached_out(&k, &[0x12u8; 13], &aad, &detached, &tag, &mut opened), + key.decrypt_detached_out(&[0x12u8; 13], &aad, &detached, &tag, &mut opened), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(opened, [0u8; 48]); diff --git a/crypto/aes/tests/suspend_tests.rs b/crypto/aes/tests/suspend_tests.rs index 3a0e529ac..b278c00b8 100644 --- a/crypto/aes/tests/suspend_tests.rs +++ b/crypto/aes/tests/suspend_tests.rs @@ -1,39 +1,55 @@ //! Suspend-and-resume round trips through the AES aliases. //! //! The impls live on the generic modes in `bouncycastle-cipher`, where they are tested over a -//! toy permutation; what is checked here is that each alias, at each key length, reaches them with -//! the right key type. The engine itself has no state and nothing to suspend: a resumed mode -//! 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. +//! toy permutation; what is checked here is that each alias, at each key length, made from its +//! key, reaches them and resumes from the key's state. The engine itself has no state and nothing +//! to suspend: a resumed mode 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::{ AES_CBC_128, AES_CBC_192, AES_CBC_256, AES_CCM_128, AES_CCM_192, AES_CCM_256, 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_CTR_256, AES_GCM_128, AES_GCM_192, AES_GCM_256, AES128Key, AES192Key, AES256Key, }; use bouncycastle_cipher::Encrypting; +use bouncycastle_cipher::modes::hazmat::ecb::ECB; +use bouncycastle_cipher::modes::{CBC, CCM_Packet, CFB, CFB8, CTR, GCM}; use bouncycastle_cipher::padding::PKCS7; +use bouncycastle_core::hazmat::ModeKey; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{ + AeadEncryptionKey, FromKeyMaterial, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ AEADCipherEncryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; +use bouncycastle_rng::HashDRBG_SHA512; -fn key() -> KeyMaterial { +fn material() -> KeyMaterial { KeyMaterial::::from_bytes_as_type(&[0x42; K], KeyType::SymmetricCipherKey).unwrap() } -fn round_trip( +/// The key under a mode, at key length `K`; its workers borrow it, and resume takes its state. +fn key_for>() -> Key { + Key::from_key_material(&material::()).expect("a valid key") +} + +fn rng() -> HashDRBG_SHA512 { + HashDRBG_SHA512::new_from_os() +} + +fn round_trip<'k, const N: usize, C>( cipher: C, + key: &'k C::Key, finish: impl Fn(C) -> Vec, ) -> Vec where - C: SuspendableKeyed> + Clone, + C: SuspendableKeyed<'k, N> + Clone, { - let key = key::(); - TestFrameworkSuspendableKeyedState::new().test(&cipher, &key); - let resumed = C::from_suspended(cipher.clone().suspend(), &key).unwrap(); + TestFrameworkSuspendableKeyedState::new().test(&cipher, key); + let resumed = C::from_suspended(cipher.clone().suspend(), key).unwrap(); let original_output = finish(cipher); assert_eq!(original_output, finish(resumed), "the resumed cipher must continue identically"); original_output @@ -43,65 +59,76 @@ where /// 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) => {{ - type CbcEnc = $cbc; - let (mut cbc, _) = CbcEnc::do_encrypt_init(&key::<$k>()).unwrap(); + ($k:literal, $key:ident, $cbc:ident, $ecb:ident, $cfb:ident, $cfb8:ident, $ctr:ident, $gcm:ident, $ccm:ident) => {{ + type CbcEnc<'k> = $cbc<'k, Encrypting, PKCS7>; + let key = key_for::<$k, $key>>(); + let (mut cbc, _) = key.create_encryptor(&mut rng()).unwrap(); cbc.do_encrypt_out(&[0x11u8; 20], &mut [0u8; 16]).unwrap(); - round_trip::<{ CbcEnc::SUSPENDED_STATE_LEN }, $k, _>(cbc, |mut e| { + round_trip::<{ CbcEnc::SUSPENDED_STATE_LEN }, _>(cbc, key.state(), |mut e| { let mut out = [0u8; 16]; e.do_encrypt_out(&[0x22u8; 12], &mut out).unwrap(); let (last, n) = e.do_encrypt_final().unwrap(); [out.as_slice(), &last[..n]].concat() }); - type EcbEnc = $ecb; - let (mut ecb, _) = EcbEnc::do_encrypt_init(&key::<$k>()).unwrap(); + type EcbEnc<'k> = $ecb<'k, Encrypting, PKCS7>; + let key = key_for::<$k, $key>>(); + let (mut ecb, _) = key.create_encryptor(&mut rng()).unwrap(); ecb.do_encrypt_out(&[0x11u8; 20], &mut [0u8; 16]).unwrap(); - round_trip::<{ EcbEnc::SUSPENDED_STATE_LEN }, $k, _>(ecb, |e| { + round_trip::<{ EcbEnc::SUSPENDED_STATE_LEN }, _>(ecb, key.state(), |e| { let (last, n) = e.do_encrypt_final().unwrap(); last[..n].to_vec() }); - let (mut cfb, _) = $cfb::::do_encrypt_init(&key::<$k>()).unwrap(); + let key = key_for::<$k, $key>(); + let (mut cfb, _) = key.create_encryptor(&mut rng()).unwrap(); cfb.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); - round_trip::<{ $cfb::::SUSPENDED_STATE_LEN }, $k, _>(cfb, |mut e| { + round_trip::<{ $cfb::::SUSPENDED_STATE_LEN }, _>(cfb, key.state(), |mut e| { let mut data = [0x22u8; 25]; e.do_encrypt_inplace(&mut data).unwrap(); data.to_vec() }); - let (mut cfb8, _) = $cfb8::::do_encrypt_init(&key::<$k>()).unwrap(); + let key = key_for::<$k, $key>(); + let (mut cfb8, _) = key.create_encryptor(&mut rng()).unwrap(); cfb8.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); - round_trip::<{ $cfb8::::SUSPENDED_STATE_LEN }, $k, _>(cfb8, |mut e| { - let mut data = [0x22u8; 9]; - e.do_encrypt_inplace(&mut data).unwrap(); - data.to_vec() - }); + round_trip::<{ $cfb8::::SUSPENDED_STATE_LEN }, _>( + cfb8, + key.state(), + |mut e| { + let mut data = [0x22u8; 9]; + e.do_encrypt_inplace(&mut data).unwrap(); + data.to_vec() + }, + ); - let (mut ctr, _) = $ctr::::do_encrypt_init(&key::<$k>()).unwrap(); + let key = key_for::<$k, $key>(); + let (mut ctr, _) = key.create_encryptor(&mut rng()).unwrap(); ctr.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); - round_trip::<{ $ctr::::SUSPENDED_STATE_LEN }, $k, _>(ctr, |mut e| { + round_trip::<{ $ctr::::SUSPENDED_STATE_LEN }, _>(ctr, key.state(), |mut e| { let mut data = [0x22u8; 25]; e.do_encrypt_inplace(&mut data).unwrap(); data.to_vec() }); - let (mut gcm, _) = $gcm::::do_encrypt_init(&key::<$k>()).unwrap(); + let key = key_for::<$k, $key>(); + let (mut gcm, _) = key.create_encryptor(&mut rng()).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| { + round_trip::<{ $gcm::::SUSPENDED_STATE_LEN }, _>(gcm, key.state(), |mut e| { let mut out = [0u8; 25]; e.do_encrypt_out(&[0x22u8; 25], &mut out).unwrap(); let (tag, n) = e.do_encrypt_final().unwrap(); [out.as_slice(), &tag[..n]].concat() }); - type CcmEnc = $ccm; - let mut ccm = CcmEnc::do_encrypt_init_nonce(&key::<$k>(), &[0x24u8; 12]).unwrap(); + type CcmEnc<'k> = $ccm<'k, Encrypting, 12, 16, 64, 32>; + let key = key_for::<$k, $key>>(); + let mut ccm = key.create_encryptor_nonce(&[0x24u8; 12]); ccm.do_update_aad(b"header").unwrap(); let mut head = [0u8; 7]; ccm.do_encrypt_out(&[0x11u8; 7], &mut head).unwrap(); - round_trip::<{ CcmEnc::SUSPENDED_STATE_LEN }, $k, _>(ccm, |mut e| { + round_trip::<{ CcmEnc::SUSPENDED_STATE_LEN }, _>(ccm, key.state(), |mut e| { let mut out = [0u8; 25]; e.do_encrypt_out(&[0x22u8; 25], &mut out).unwrap(); let (tag, n) = e.do_encrypt_final().unwrap(); @@ -113,23 +140,23 @@ 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 + 16, AES128Key, AES_CBC_128, AES_ECB_128, AES_CFB_128, AES_CFB8_128, AES_CTR_128, + AES_GCM_128, AES_CCM_128 ); } #[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 + 24, AES192Key, AES_CBC_192, AES_ECB_192, AES_CFB_192, AES_CFB8_192, AES_CTR_192, + AES_GCM_192, AES_CCM_192 ); } #[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 + 32, AES256Key, AES_CBC_256, AES_ECB_256, AES_CFB_256, AES_CFB8_256, AES_CTR_256, + AES_GCM_256, AES_CCM_256 ); } diff --git a/crypto/ascon/benches/ascon_benches.rs b/crypto/ascon/benches/ascon_benches.rs index f710f8940..0f7dd4b21 100644 --- a/crypto/ascon/benches/ascon_benches.rs +++ b/crypto/ascon/benches/ascon_benches.rs @@ -2,13 +2,13 @@ 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::AsconAEAD128Key; use bouncycastle_ascon::AsconCXof128; use bouncycastle_ascon::AsconHash256; use bouncycastle_ascon::AsconXof128; -use bouncycastle_cipher::Encrypting; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherEncryptor, HashOneShot, RNG, XOF}; +use bouncycastle_core::symmetric_key::{AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core::traits::{HashOneShot, RNG, XOF}; const DATA_LEN: usize = 16 * 1024; @@ -19,8 +19,9 @@ fn random_data(len: usize) -> Vec { } fn bench_aead128_encrypt(c: &mut Criterion) { - let key = + let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); + let key = AsconAEAD128Key::from_key_material(&material).unwrap(); let nonce = [0x24u8; 16]; let data = random_data(DATA_LEN); let mut out = vec![0u8; DATA_LEN + 16]; @@ -28,16 +29,9 @@ fn bench_aead128_encrypt(c: &mut Criterion) { let mut group = c.benchmark_group("ascon::Ascon_AEAD128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); - group.bench_function(format!("{DATA_LEN} bytes -- ::encrypt()"), |b| { + group.bench_function(format!("{DATA_LEN} bytes -- encrypt_with_aad_nonce_out"), |b| { b.iter(|| { - Ascon_AEAD128::::encrypt_with_aad_nonce_out( - &key, - &nonce, - &[], - black_box(&data), - &mut out, - ) - .unwrap(); + key.encrypt_with_aad_nonce_out(&nonce, &[], black_box(&data), &mut out).unwrap(); black_box(&out); }) }); diff --git a/crypto/ascon/src/ascon_aead128.rs b/crypto/ascon/src/ascon_aead128.rs index 2e45c78ef..69e019a0e 100644 --- a/crypto/ascon/src/ascon_aead128.rs +++ b/crypto/ascon/src/ascon_aead128.rs @@ -22,11 +22,11 @@ use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::{KeyMaterialError, SuspendableError, SymmetricCipherError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::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}; @@ -81,35 +81,43 @@ impl Phase { /// so the wrong direction is a compile error. One instance performs one operation under one /// (key, nonce) pair. /// -/// Both directions implement [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] and, through them, -/// [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] -- which is the AEAD with no -/// associated data and the tag inline: +/// Both directions are made by [`AsconAEAD128Key`], and implement [`AEADCipherEncryptor`] / +/// [`AEADCipherDecryptor`] and, through them, [`SymmetricCipherEncryptor`] / +/// [`SymmetricCipherDecryptor`] -- which is the AEAD with no associated data and the tag inline: /// /// ``` -/// use bouncycastle_ascon::Ascon_AEAD128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_ascon::{Ascon_AEAD128, AsconAEAD128Key}; /// use bouncycastle_cipher::{Decrypting, Encrypting}; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; /// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_rng::HashDRBG_SHA512; /// /// type Enc = Ascon_AEAD128; /// type Dec = Ascon_AEAD128; /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .expect("a 16-byte symmetric cipher key"); +/// let key = AsconAEAD128Key::from_key_material(&material).expect("a valid key"); /// /// let message = b"hello"; +/// let (mut enc, nonce) = key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).unwrap(); /// let mut ciphertext = [0u8; 5 + 16]; // Enc::encrypt_out_len(5): ciphertext || tag -/// let (nonce, written) = Enc::encrypt_out(&key, message, &mut ciphertext).expect("encryption"); -/// assert_eq!(written, 21); +/// let written = enc.do_encrypt_out(message, &mut ciphertext).expect("encryption"); +/// let (tag, tag_len) = enc.do_encrypt_final().expect("the tag"); +/// ciphertext[written..].copy_from_slice(&tag[..tag_len]); /// +/// let mut dec = key.create_decryptor(&nonce); /// let mut plaintext = [0u8; 5]; // Dec::decrypt_out_len(21) -/// let n = Dec::decrypt_out(&key, &nonce, &ciphertext, &mut plaintext).expect("decryption"); +/// let n = dec.do_decrypt_out(&ciphertext, &mut plaintext).expect("decryption"); +/// dec.do_decrypt_final().expect("the tag verifies"); /// assert_eq!(&plaintext[..n], message); /// ``` /// /// The encrypting side implements [`AEADCipherEncryptor`] and [`SymmetricCipherEncryptor`]; the -/// nonce is drawn by `do_encrypt_init` and returned, or supplied to `do_encrypt_init_nonce` by a -/// caller who takes on its uniqueness. `FINAL_LEN` is `TAG_LEN`: nothing is held back, so the +/// nonce is drawn by the key's `create_encryptor` and returned, or supplied to +/// `create_encryptor_nonce` by a caller who takes on its uniqueness. `FINAL_LEN` is `TAG_LEN`: +/// nothing is held back, so the /// inline [`SymmetricCipherEncryptor::do_encrypt_final`] writes only the tag, and the detached /// [`AEADCipherEncryptor::do_encrypt_final_detached_out`] only zeroes its buffer. /// @@ -121,12 +129,13 @@ 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_cipher::Decrypting; +/// # use bouncycastle_ascon::AsconAEAD128Key; /// # use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// # use bouncycastle_core::symmetric_key::{AeadDecryptionKey, FromKeyMaterial}; /// # use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; -/// # let key = KeyMaterial::<16>::from_bytes_as_type(&[7; 16], KeyType::SymmetricCipherKey).unwrap(); -/// let mut dec = Ascon_AEAD128::::do_decrypt_init(&key, &[0u8; 16]).unwrap(); +/// # let material = KeyMaterial::<16>::from_bytes_as_type(&[7; 16], KeyType::SymmetricCipherKey).unwrap(); +/// # let key = AsconAEAD128Key::from_key_material(&material).unwrap(); +/// let mut dec = key.create_decryptor(&[0u8; 16]); /// dec.do_encrypt_out(&[0u8; 4], &mut [0u8; 4]); // no such method on the decrypting type /// ``` #[allow(non_camel_case_types)] @@ -163,19 +172,6 @@ impl Clone for Ascon_AEAD128 { } } -/// Draw a fresh, unique 128-bit nonce from the library's default OS-seeded DRBG. -/// -/// The one-shot APIs of main's cipher framework generate the init data / nonce internally, so -/// Ascon's per-encryption nonce-uniqueness requirement (SP 800-232 R3) is satisfied by sourcing -/// each nonce from a CSPRNG. A caller who manages its own nonces uses -/// [`AEADCipherEncryptor::do_encrypt_init_nonce`] instead. -fn fresh_nonce() -> Result<[u8; NONCE_LEN], SymmetricCipherError> { - let mut rng = HashDRBG_SHA512::new_from_os(); - let mut nonce = [0u8; NONCE_LEN]; - rng.next_bytes_out(&mut nonce)?; - Ok(nonce) -} - 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 @@ -200,16 +196,10 @@ impl Ascon_AEAD128 { 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)?; + /// Step 1 from already-validated key words: what [`AsconAEAD128Key`] calls per message. + fn from_key_words(key_words: &[u64; 2], nonce: &[u8; NONCE_LEN]) -> Self { let mut key_secret: Secret<[u64; 2]> = Secret::new(); - *key_secret = key_words; + *key_secret = *key_words; let mut state: Secret = Secret::new(); // Initialization (SP 800-232 §4.1.1 step 1 / Eq. 15-17): S = IV||K||N, then Ascon-p[12], @@ -223,7 +213,7 @@ impl Ascon_AEAD128 { state[3] ^= key_words[0]; state[4] ^= key_words[1]; - Ok(Ascon_AEAD128 { + Ascon_AEAD128 { key: key_secret, state, pos: 0, @@ -231,7 +221,7 @@ impl Ascon_AEAD128 { held: [0u8; TAG_LEN], held_len: 0, _dir: PhantomData, - }) + } } /// Read the value of state byte `pos` (0 = LSB of word 0, ..., 15 = MSB of word 1). @@ -441,23 +431,7 @@ impl Algorithm for Ascon_AEAD128 { const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } -impl SymmetricCipherEncryptor for Ascon_AEAD128 { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { - let nonce = fresh_nonce()?; - Ok((Self::new(key, &nonce)?, nonce)) - } - - fn do_encrypt_init_rng( - key: &KeyMaterial, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { - let mut nonce = [0u8; NONCE_LEN]; - rng.next_bytes_out(&mut nonce)?; - Ok((Self::new(key, &nonce)?, nonce)) - } - +impl SymmetricCipherEncryptor for Ascon_AEAD128 { /// Ascon-AEAD128 never buffers: every byte given is a byte returned. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -489,18 +463,11 @@ 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, - nonce: &[u8; NONCE_LEN], - ) -> Result { - Self::new(key, nonce) - } - /// Nothing is ever held back to flush, so `ciphertext` is left zeroed. fn do_encrypt_final_detached_out( self, @@ -511,14 +478,7 @@ impl AEADCipherEncryptor for Ascon_AEAD128 } } -impl SymmetricCipherDecryptor for Ascon_AEAD128 { - fn do_decrypt_init( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { - Self::new(key, nonce) - } - +impl SymmetricCipherDecryptor for Ascon_AEAD128 { /// Everything but the last `TAG_LEN` bytes seen so far is released. fn do_decrypt_out_len(&self, input_len: usize) -> usize { (self.held_len + input_len).saturating_sub(TAG_LEN) @@ -574,7 +534,7 @@ 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 +605,95 @@ impl Ascon_AEAD128 { } } +/// An Ascon-AEAD128 key: the 128-bit key validated once, from which the encryptors and +/// decryptors for each message are made. The key words live in a [`Secret`], zeroized on drop; +/// there is no `Debug` and no way to read them back. +/// +/// ``` +/// use bouncycastle_ascon::AsconAEAD128Key; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +/// use bouncycastle_rng::HashDRBG_SHA512; +/// +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +/// .expect("a 16-byte symmetric cipher key"); +/// let key = AsconAEAD128Key::from_key_material(&material).expect("a valid Ascon-AEAD128 key"); +/// +/// // The nonce is drawn from `rng` and returned with the ciphertext; the tag is detached. +/// let (nonce, ciphertext, tag) = key.encrypt_detached(&mut rng, b"header", b"attack at dawn").unwrap(); +/// let plaintext = key.decrypt_detached(&nonce, b"header", &ciphertext, &tag).unwrap(); +/// assert_eq!(plaintext, b"attack at dawn"); +/// ``` +pub struct AsconAEAD128Key { + key: Secret<[u64; 2]>, +} + +impl Clone for AsconAEAD128Key { + fn clone(&self) -> Self { + Self { key: self.key.clone() } + } +} + +// `Secret`'s equality: every byte is inspected, so the time taken does not say where two keys +// differ. +impl PartialEq for AsconAEAD128Key { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for AsconAEAD128Key {} + +impl FromKeyMaterial for AsconAEAD128Key { + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + /// # Errors + /// * [`KeyMaterialError::InvalidKeyType`] if the key is not a `SymmetricCipherKey`. + /// * [`KeyMaterialError::InvalidLength`] if the key is not 16 bytes long. + /// * [`KeyMaterialError::SecurityStrength`] if the key carries a strength below 128 bits. + fn from_key_material(key: &KeyMaterial) -> Result { + let mut key_secret: Secret<[u64; 2]> = Secret::new(); + *key_secret = Ascon_AEAD128::::checked_key(key)?; + Ok(Self { key: key_secret }) + } +} + +impl AeadEncryptionKey for AsconAEAD128Key { + type Encryptor<'k> + = Ascon_AEAD128 + where + Self: 'k; + + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + let mut nonce = [0u8; NONCE_LEN]; + rng.next_bytes_out(&mut nonce)?; + Ok((Ascon_AEAD128::from_key_words(&self.key, &nonce), nonce)) + } + + /// Under a nonce the caller supplies, who takes on the obligation that it never repeat + /// under this key (SP 800-232 R3). + fn create_encryptor_nonce<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor<'k> { + Ascon_AEAD128::from_key_words(&self.key, nonce) + } +} + +impl AeadDecryptionKey for AsconAEAD128Key { + type Decryptor<'k> + = Ascon_AEAD128 + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k> { + Ascon_AEAD128::from_key_words(&self.key, nonce) + } +} + impl Debug for Ascon_AEAD128 { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Ascon-AEAD128 (key/state masked)") @@ -684,10 +733,12 @@ const AEAD128_STATE_TAG: u8 = 0x04; 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; +impl<'k> SuspendableKeyed<'k, SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN> + for Ascon_AEAD128 +{ + // The key is never part of the serialized state; the key object the worker was made from is + // re-supplied, already validated. + type Key = AsconAEAD128Key; fn suspend(self) -> [u8; SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN] { self.suspend_as(ENC_DIR_BITS) @@ -696,7 +747,7 @@ impl SuspendableKeyed for Ascon_AEA /// A state suspended by the decrypting side is [`SuspendableError::InvalidData`] here. fn from_suspended( serialized_state: [u8; SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN], - key: &Self::Key, + key: &'k Self::Key, ) -> Result { Self::resume_as(serialized_state, key, ENC_DIR_BITS) } @@ -704,8 +755,10 @@ 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; +impl<'k> SuspendableKeyed<'k, SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN> + for Ascon_AEAD128 +{ + type Key = AsconAEAD128Key; fn suspend(self) -> [u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN] { let mut out = [0u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN]; @@ -720,7 +773,7 @@ impl SuspendableKeyed for Ascon_AEA /// [`SuspendableError::InvalidData`]. fn from_suspended( serialized_state: [u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN], - key: &Self::Key, + key: &'k Self::Key, ) -> Result { // infallible: the slice is exactly CIPHER_STATE_LEN bytes by construction. let cipher_state: [u8; CIPHER_STATE_LEN] = @@ -758,7 +811,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: &AsconAEAD128Key, dir_bits: u8, ) -> Result { // infallible: check_lib_ver returns a slice of exactly CIPHER_STATE_LEN - 3 = 43 bytes. @@ -789,9 +842,8 @@ impl Ascon_AEAD128 { return Err(SuspendableError::InvalidData); } - let key_words = Self::checked_key(key).map_err(|_| SuspendableError::InvalidData)?; let mut key_secret = Secret::<[u64; 2]>::new(); - *key_secret = key_words; + *key_secret = *key.key; Ok(Ascon_AEAD128 { key: key_secret, diff --git a/crypto/ascon/src/lib.rs b/crypto/ascon/src/lib.rs index ab04f6c1e..4d936ff58 100644 --- a/crypto/ascon/src/lib.rs +++ b/crypto/ascon/src/lib.rs @@ -17,7 +17,7 @@ //! (August 2025), which can be found here: //! //! - [`Ascon-AEAD128`](Ascon_AEAD128) — authenticated encryption (128-bit key/nonce/tag, 128-bit -//! single-key security). +//! single-key security), used through [`AsconAEAD128Key`]. //! - [`Ascon-Hash256`](AsconHash256) — hash function (256-bit digest, 128-bit security). //! - [`Ascon-XOF128`](AsconXof128) — extendable-output function. //! - [`Ascon-CXOF128`](AsconCXof128) — customized extendable-output function. @@ -43,50 +43,53 @@ //! assert_eq!(&out[..], &digest[..]); //! ``` //! -//! Authenticated encryption (one-shot, under a nonce the caller manages): +//! Authenticated encryption (one-shot, under a nonce the caller manages), through the key: //! ``` -//! use bouncycastle_ascon::Ascon_AEAD128; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_ascon::AsconAEAD128Key; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AsconAEAD128Key::from_key_material(&material).unwrap(); //! let nonce = [1u8; 16]; // MUST be unique per encryption under a given key //! let ad = b"associated data"; //! let plaintext = b"secret message"; //! //! let mut ct = vec![0u8; plaintext.len() + 16]; // ciphertext || 16-byte tag -//! let n = Ascon_AEAD128::::encrypt_with_aad_nonce_out(&key, &nonce, ad, plaintext, &mut ct).unwrap(); +//! let n = key.encrypt_with_aad_nonce_out(&nonce, ad, plaintext, &mut ct).unwrap(); //! ct.truncate(n); //! //! let mut pt = vec![0u8; ct.len() - 16]; -//! let m = Ascon_AEAD128::::decrypt_with_aad_out(&key, &nonce, ad, &ct, &mut pt).unwrap(); +//! let m = key.decrypt_with_aad_out(&nonce, ad, &ct, &mut pt).unwrap(); //! pt.truncate(m); //! assert_eq!(&pt, plaintext); //! ``` //! -//! Authenticated encryption (streaming, detached tag). The decryptor holds back the last 16 -//! bytes it has seen, in case they are an inline tag, so `do_decrypt_final_detached_out` is -//! where they come out: +//! Authenticated encryption (streaming, detached tag), with the nonce drawn from the caller's +//! RNG. The decryptor holds back the last 16 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_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_ascon::AsconAEAD128Key; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, //! }; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AsconAEAD128Key::from_key_material(&material).unwrap(); //! //! let plaintext = b"secret message!!"; -//! let (mut enc, nonce) = Ascon_AEAD128::::do_encrypt_init(&key).unwrap(); +//! let (mut enc, nonce) = key.create_encryptor(&mut rng).unwrap(); //! enc.do_update_aad(b"associated data").unwrap(); //! let mut ciphertext = [0u8; 16]; //! enc.do_encrypt_out(plaintext, &mut ciphertext).unwrap(); //! let mut final_buf = [0u8; 16]; //! let (_, tag) = enc.do_encrypt_final_detached_out(&mut final_buf).unwrap(); //! -//! let mut dec = Ascon_AEAD128::::do_decrypt_init(&key, &nonce).unwrap(); +//! let mut dec = key.create_decryptor(&nonce); //! dec.do_update_aad(b"associated data").unwrap(); //! let mut recovered = [0u8; 16]; //! let n = dec.do_decrypt_out(&ciphertext, &mut recovered).unwrap(); // 0: all 16 held back @@ -95,34 +98,27 @@ //! assert_eq!(&recovered, plaintext); //! ``` //! -//! For the inline `ciphertext || tag` layout that most wire formats and files use, the pair is -//! also a [`bouncycastle_core::traits::SymmetricCipherEncryptor`] / -//! [`bouncycastle_core::traits::SymmetricCipherDecryptor`], which covers the no-AAD case -- -//! streaming, or through its `encrypt_out` / `decrypt_out` one-shots -- and -//! [`bouncycastle_core::traits::AEADCipherEncryptor::encrypt_with_aad_out`] / -//! [`bouncycastle_core::traits::AEADCipherDecryptor::decrypt_with_aad_out`] are the one-shots with AAD: +//! For the inline `ciphertext || tag` layout that most wire formats and files use, the key's +//! `encrypt_with_aad_out` / `decrypt_with_aad_out` are the one-shots, with an empty AAD when +//! there is none, and the workers are also a +//! [`bouncycastle_core::traits::SymmetricCipherEncryptor`] / +//! [`bouncycastle_core::traits::SymmetricCipherDecryptor`] pair for streaming it: //! ``` -//! use bouncycastle_ascon::Ascon_AEAD128; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_ascon::AsconAEAD128Key; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{ -//! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, -//! }; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x42u8; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let key = AsconAEAD128Key::from_key_material(&material).unwrap(); //! let plaintext = b"secret message!!"; //! -//! // No AAD: just a symmetric cipher. -//! let mut inline = [0u8; 32]; // Ascon_AEAD128::::encrypt_out_len(16) -//! let (nonce, len) = Ascon_AEAD128::::encrypt_out(&key, plaintext, &mut inline).unwrap(); +//! let mut inline = [0u8; 32]; // plaintext.len() + 16 +//! let (len, nonce) = key.encrypt_with_aad_out(&mut rng, b"aad", plaintext, &mut inline).unwrap(); //! assert_eq!(len, plaintext.len() + 16); // ciphertext || tag //! let mut recovered = [0u8; 16]; -//! let n = Ascon_AEAD128::::decrypt_out(&key, &nonce, &inline[..len], &mut recovered).unwrap(); -//! assert_eq!(&recovered[..n], plaintext); -//! -//! // With AAD. -//! let (nonce, len) = Ascon_AEAD128::::encrypt_with_aad_out(&key, b"aad", plaintext, &mut inline).unwrap(); -//! let n = Ascon_AEAD128::::decrypt_with_aad_out(&key, &nonce, b"aad", &inline[..len], &mut recovered).unwrap(); +//! let n = key.decrypt_with_aad_out(&nonce, b"aad", &inline[..len], &mut recovered).unwrap(); //! assert_eq!(&recovered[..n], plaintext); //! ``` //! @@ -144,6 +140,7 @@ //! //! | Type | In-memory size (bytes) | Suspended state size (bytes) | //! |------|-------------------------|-------------------------------| +//! | [`AsconAEAD128Key`] | 16 | | //! | [`Ascon_AEAD128`] | 96 | [`SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN`] (46) | //! | [`Ascon_AEAD128`] | 96 | [`SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN`] (63) | //! | [`AsconHash256`] | 64 | [`SUSPENDED_ASCON_HASH256_STATE_LEN`] (53) | @@ -156,26 +153,29 @@ //! //! Per-call stack usage is separate from that and does not depend on the data length either. //! Measured as the deepest frame chain below each entry point in the release build (x86-64 Linux, -//! rustc 1.95.0, return addresses included): +//! rustc 1.97.0-nightly, return addresses included; `bench_ascon_mem_usage` has the harness and +//! the method): //! //! | Ascon-AEAD128 entry point | Encrypting | Decrypting | //! |---|---|---| -//! | `do_encrypt_init_rng` / `do_decrypt_init` | 536 B | 504 B | -//! | `do_update_aad` | 104 B | 104 B | +//! | `AsconAEAD128Key::from_key_material` (the key check, once per key) | 24 B | 24 B | +//! | `create_encryptor` / `create_decryptor` | 120 B | 48 B | +//! | `do_update_aad` | 48 B | 48 B | //! | `do_encrypt_out` / `do_decrypt_out` | 176 B | 208 B | -//! | `do_encrypt_final` / `do_decrypt_final` (tag inline) | 176 B | 208 B | -//! | `do_encrypt_final_detached_out` / `do_decrypt_final_detached_out` | 176 B | 272 B | -//! | `encrypt_with_aad_rng_out` / `decrypt_with_aad_out` (one-shot) | 648 B | 888 B | +//! | `do_encrypt_final` / `do_decrypt_final` (tag inline) | 112 B | 256 B | +//! | `do_encrypt_final_detached_out` / `do_decrypt_final_detached_out` | 112 B | 288 B | +//! | `encrypt_with_aad_out` / `decrypt_with_aad_out` (one-shot) | 520 B | 640 B | //! -//! The encrypting figures do not include the caller's RNG; `do_encrypt_init` and the one-shots -//! without `_rng` draw the nonce from the library's default DRBG, whose stack is not counted here. +//! The encrypting `create_encryptor` figure includes the call into the caller's RNG but not the +//! RNG's own stack; the one-shots are the role traits' provided bodies, which copy the message +//! through a 64-byte chunk on the stack, and the decrypting one holds the tag back as well. //! //! | Sponge entry point | Ascon-Hash256 | Ascon-XOF128 | Ascon-CXOF128 | //! |---|---|---|---| -//! | `with_customization` | | | 200 B | -//! | `do_update` | 72 B | 72 B | 72 B | -//! | `do_final_out` / `do_output_out` | 152 B | 104 B | 104 B | -//! | `hash_out` / `xof_out` (one-shot) | 152 B | 168 B | 168 B | +//! | `with_customization` | | | 192 B | +//! | `do_update` | 64 B | 64 B | 64 B | +//! | `do_final_out` / `do_output_out` | 144 B | 96 B | 96 B | +//! | `hash_out` / `xof_out` (one-shot) | 144 B | 160 B | 160 B | //! //! The sponges' `new` copies a precomputed state and runs no permutation. //! @@ -191,11 +191,10 @@ //! caller that needs a partial-byte final block should reach for SHA-3, which supports one. //! - **Decryption tag check failure:** a ciphertext decryption whose finalization returns //! `Err(SymmetricCipherError::AEADTagCheckFailed)` must be treated as tampered, and the entire -//! plaintext rejected. The one-shot APIs -//! ([`bouncycastle_core::traits::AEADCipherDecryptor::decrypt_detached_out`], -//! [`bouncycastle_core::traits::AEADCipherDecryptor::decrypt_with_aad_out`] and -//! [`bouncycastle_core::traits::SymmetricCipherDecryptor::decrypt_out`]) zeroize their output -//! buffer before returning that error. The streaming API (`do_decrypt_out` followed by +//! plaintext rejected. The key's one-shots +//! ([`bouncycastle_core::symmetric_key::AeadDecryptionKey::decrypt_detached_out`] and +//! [`bouncycastle_core::symmetric_key::AeadDecryptionKey::decrypt_with_aad_out`]) zeroize +//! their output buffer before returning that error. The streaming API (`do_decrypt_out` followed by //! [`bouncycastle_core::traits::AEADCipherDecryptor::do_decrypt_final_detached_out`] or //! [`bouncycastle_core::traits::SymmetricCipherDecryptor::do_decrypt_final`]) does not: plaintext //! bytes are necessarily written to the caller's buffer *before* the tag can be checked, so an @@ -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, AsconAEAD128Key, 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 2ba7f7ce9..68bd3a461 100644 --- a/crypto/ascon/tests/aead128_tests.rs +++ b/crypto/ascon/tests/aead128_tests.rs @@ -9,17 +9,20 @@ //! 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_NAME, Ascon_AEAD128, AsconAEAD128Key}; 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::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_hex as hex; +use bouncycastle_rng::HashDRBG_SHA512; // All embedded vectors use this fixed key/nonce (the NIST LWC KAT convention). const KEY: [u8; 16] = [ @@ -82,11 +85,26 @@ fn key_material(key: &[u8; 16]) -> KeyMaterial<16> { km } +fn aead_key(key: &[u8; 16]) -> AsconAEAD128Key { + AsconAEAD128Key::from_key_material(&key_material(key)).expect("a valid key") +} + +/// Equality follows the material: the same bytes compare equal, as does a clone, and different +/// bytes do not. The key has no `Debug`, hence `assert!` over `==`. +#[test] +fn keys_compare_equal_exactly_when_their_material_does() { + let key = aead_key(&[0x42; 16]); + assert!(key == key.clone()); + assert!(key == aead_key(&[0x42; 16])); + let mut last_byte_differs = [0x42; 16]; + last_byte_differs[15] ^= 1; + assert!(key != aead_key(&last_byte_differs)); +} + fn enc_oneshot(key: &[u8; 16], nonce: &[u8; 16], ad: &[u8], pt: &[u8]) -> Vec { - let km = key_material(key); + let km = aead_key(key); let mut out = vec![0u8; Ascon_AEAD128::::encrypt_out_len(pt.len())]; - let n = Ascon_AEAD128::::encrypt_with_aad_nonce_out(&km, nonce, ad, pt, &mut out) - .unwrap(); + let n = km.encrypt_with_aad_nonce_out(nonce, ad, pt, &mut out).unwrap(); out.truncate(n); out } @@ -97,9 +115,9 @@ fn dec_oneshot( ad: &[u8], ct: &[u8], ) -> Result, SymmetricCipherError> { - let km = key_material(key); + let km = aead_key(key); let mut out = vec![0u8; Ascon_AEAD128::::decrypt_out_len(ct.len())]; - let n = Ascon_AEAD128::::decrypt_with_aad_out(&km, nonce, ad, ct, &mut out)?; + let n = km.decrypt_with_aad_out(nonce, ad, ct, &mut out)?; out.truncate(n); Ok(out) } @@ -107,8 +125,8 @@ fn dec_oneshot( /// Streams `pt` through an encryptor in `chunk`-byte pieces; inline layout, so the tag is the /// final's output. fn enc_chunked(key: &[u8; 16], nonce: &[u8; 16], ad: &[u8], pt: &[u8], chunk: usize) -> Vec { - let km = key_material(key); - let mut enc = Ascon_AEAD128::::do_encrypt_init_nonce(&km, nonce).unwrap(); + let km = aead_key(key); + let mut enc = km.create_encryptor_nonce(nonce); enc.do_update_aad(ad).unwrap(); let mut out = Vec::with_capacity(pt.len() + 16); for piece in pt.chunks(chunk.max(1)) { @@ -130,8 +148,8 @@ fn dec_chunked( ct: &[u8], chunk: usize, ) -> Result, SymmetricCipherError> { - let km = key_material(key); - let mut dec = Ascon_AEAD128::::do_decrypt_init(&km, nonce)?; + let km = aead_key(key); + let mut dec = km.create_decryptor(nonce); dec.do_update_aad(ad)?; let mut out = Vec::with_capacity(ct.len()); for piece in ct.chunks(chunk.max(1)) { @@ -220,10 +238,10 @@ fn aead_chunked_aad_matches_one_shot() { let pt = pattern(30); let ad = pattern(40); let ct_ref = enc_oneshot(&KEY, &NONCE, &ad, &pt); - let km = key_material(&KEY); + let km = aead_key(&KEY); for &chunk in CHUNK_SIZES.iter() { - let mut e = Ascon_AEAD128::::do_encrypt_init_nonce(&km, &NONCE).unwrap(); + let mut e = km.create_encryptor_nonce(&NONCE); for piece in ad.chunks(chunk) { e.do_update_aad(piece).unwrap(); } @@ -240,14 +258,14 @@ fn aead_chunked_aad_matches_one_shot() { /// each buffer and holds the newest 16 bytes back, so the inline final finds the tag. #[test] fn inplace_streaming_matches_one_shot() { - let km = key_material(&KEY); + let km = aead_key(&KEY); for &pt_len in PT_SIZES.iter() { let pt = pattern(pt_len); let ad = pattern(20); let ct_ref = enc_oneshot(&KEY, &NONCE, &ad, &pt); for &chunk in CHUNK_SIZES.iter() { - let mut e = Ascon_AEAD128::::do_encrypt_init_nonce(&km, &NONCE).unwrap(); + let mut e = km.create_encryptor_nonce(&NONCE); e.do_update_aad(&ad).unwrap(); let mut buf = pt.clone(); for piece in buf.chunks_mut(chunk) { @@ -257,7 +275,7 @@ fn inplace_streaming_matches_one_shot() { buf.extend_from_slice(&last[..n]); assert_eq!(buf, ct_ref, "in-place encrypt mismatch (pt_len={pt_len}, chunk={chunk})"); - let mut d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let mut d = km.create_decryptor(&NONCE); d.do_update_aad(&ad).unwrap(); let mut buf = ct_ref.clone(); let mut back = Vec::with_capacity(pt_len); @@ -270,7 +288,7 @@ fn inplace_streaming_matches_one_shot() { assert_eq!(back, pt, "in-place decrypt mismatch (pt_len={pt_len}, chunk={chunk})"); // The two decrypt forms interleave, since they share the hold-back. - let mut d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let mut d = km.create_decryptor(&NONCE); d.do_update_aad(&ad).unwrap(); let mut back = Vec::with_capacity(pt_len); let mut buf = ct_ref.clone(); @@ -297,7 +315,7 @@ fn inplace_streaming_matches_one_shot() { #[test] fn aead_streaming_chunk_sweep() { - let km = key_material(&KEY); + let km = aead_key(&KEY); for pt_len in 0..=40 { let pt = pattern(pt_len); for ad_len in [0, 1, 15, 16, 17, 33] { @@ -309,8 +327,7 @@ fn aead_streaming_chunk_sweep() { for &chunk in [1, 2, 7, 15, 16, 17, 31, 32, 1024].iter() { // Detached layout: the body streams out chunk by chunk, the tag comes from the // final. - let mut e = - Ascon_AEAD128::::do_encrypt_init_nonce(&km, &NONCE).unwrap(); + let mut e = km.create_encryptor_nonce(&NONCE); e.do_update_aad(&ad).unwrap(); let mut out = Vec::with_capacity(pt_len); for piece in pt.chunks(chunk) { @@ -323,7 +340,7 @@ fn aead_streaming_chunk_sweep() { assert_eq!(out, ct_ref_body, "pt_len={pt_len} ad_len={ad_len} chunk={chunk}"); assert_eq!(tag, tag_ref, "pt_len={pt_len} ad_len={ad_len} chunk={chunk}"); - let mut d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let mut d = km.create_decryptor(&NONCE); d.do_update_aad(&ad).unwrap(); let mut back = Vec::with_capacity(pt_len); for piece in ct_ref_body.chunks(chunk) { @@ -342,9 +359,9 @@ fn aead_streaming_chunk_sweep() { #[test] fn detached_final_rejects_wrong_tag() { - let km = key_material(&KEY); + let km = aead_key(&KEY); let pt = pattern(20); - let mut d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let mut d = km.create_decryptor(&NONCE); let mut buf = vec![0u8; pt.len()]; d.do_decrypt_out(&pt, &mut buf).unwrap(); let wrong_tag = [0xFFu8; 16]; @@ -409,9 +426,9 @@ fn aead_tamper_leaves_no_plaintext_in_output_buffer() { let mut tampered = ct.clone(); tampered[0] ^= 0x01; - let km = key_material(&KEY); + let km = aead_key(&KEY); let mut out = vec![0xAAu8; pt.len()]; - let n = Ascon_AEAD128::::decrypt_with_aad_out(&km, &NONCE, ad, &tampered, &mut out); + let n = km.decrypt_with_aad_out(&NONCE, ad, &tampered, &mut out); assert!(matches!(n, Err(SymmetricCipherError::AEADTagCheckFailed))); assert!(out.iter().all(|&b| b == 0), "output buffer must be zeroized on tag failure"); } @@ -419,9 +436,9 @@ fn aead_tamper_leaves_no_plaintext_in_output_buffer() { #[test] fn aead_short_ciphertext_is_error() { let short = [0u8; 8]; // shorter than the 16-byte tag - let km = key_material(&KEY); + let km = aead_key(&KEY); let mut out = [0xEEu8; 16]; - match Ascon_AEAD128::::decrypt_with_aad_out(&km, &NONCE, &[], &short, &mut out) { + match km.decrypt_with_aad_out(&NONCE, &[], &short, &mut out) { Err(SymmetricCipherError::DecryptionFailed) => {} other => panic!("expected DecryptionFailed for short ciphertext, got {other:?}"), } @@ -448,11 +465,11 @@ fn aead_is_deterministic_and_nonce_sensitive() { #[test] fn aead_debug_display_are_masked() { - let km = key_material(&KEY); - let e = Ascon_AEAD128::::do_encrypt_init_nonce(&km, &NONCE).unwrap(); + let km = aead_key(&KEY); + let e = km.create_encryptor_nonce(&NONCE); assert!(format!("{e:?}").contains("masked")); assert!(format!("{e}").contains("masked")); - let d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let d = km.create_decryptor(&NONCE); assert!(format!("{d:?}").contains("masked")); assert!(format!("{d}").contains("masked")); } @@ -461,17 +478,21 @@ fn aead_debug_display_are_masked() { /* Trait conformance (shared core-test-framework) */ /* -------------------------------------------------------------------------- */ -/// Exercises [`AEADCipherEncryptor`]/[`AEADCipherDecryptor`] on the streaming pair -/// [`Ascon_AEAD128`]/[`Ascon_AEAD128`]: `update_out_len` -/// correctness, chunking-independence of both AAD and data, the AAD-after-data `StateError`, and -/// tamper detection, all against the generic conformance suite rather than hand-written here. -/// -/// [`AEADCipherEncryptor`]: bouncycastle_core::traits::AEADCipherEncryptor -/// [`AEADCipherDecryptor`]: bouncycastle_core::traits::AEADCipherDecryptor +/// Exercises the AEAD role pair on [`AsconAEAD128Key`] and the streaming pair it hands out, +/// [`Ascon_AEAD128`]/[`Ascon_AEAD128`]: `update_out_len` correctness, +/// chunking-independence of both AAD and data, the AAD-after-data `StateError`, and tamper +/// detection, all against the generic conformance suite rather than hand-written here. #[test] -fn aead128_encryptor_decryptor_trait_framework() { +fn aead128_key_framework() { TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::<16, 16, 16, 16, Ascon_AEAD128, Ascon_AEAD128>(); + .test_key::<16, 16, 16, 16, _>(&aead_key(&KEY), &mut HashDRBG_SHA512::new_from_os()); +} + +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. +#[test] +fn aead128_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::<16, AsconAEAD128Key>(ASCON_AEAD128_NAME, SecurityStrength::_128bit); } /// The two tag layouts must agree byte for byte: `direct_ciphertext || direct_tag`, produced by @@ -483,7 +504,7 @@ fn aead128_encryptor_decryptor_trait_framework() { fn aead128_tagged_and_direct_layouts_agree() { use bouncycastle_rng::FixedSeedRNG; - let km = key_material(&KEY); + let km = aead_key(&KEY); let aad = b"tagged-layout-aad"; for pt_len in [0usize, 1, 15, 16, 17, 40] { let pt = pattern(pt_len); @@ -491,8 +512,7 @@ fn aead128_tagged_and_direct_layouts_agree() { // detached tag, streamed let (mut direct_enc, direct_nonce) = - Ascon_AEAD128::::do_encrypt_init_rng(&km, &mut FixedSeedRNG::new(pinned)) - .unwrap(); + km.create_encryptor(&mut FixedSeedRNG::new(pinned)).unwrap(); direct_enc.do_update_aad(aad).unwrap(); let mut direct_ct = vec![0u8; pt.len()]; direct_enc.do_encrypt_out(&pt, &mut direct_ct).unwrap(); @@ -504,8 +524,7 @@ fn aead128_tagged_and_direct_layouts_agree() { // inline tag, streamed let (mut tagged_enc, tagged_nonce) = - Ascon_AEAD128::::do_encrypt_init_rng(&km, &mut FixedSeedRNG::new(pinned)) - .unwrap(); + km.create_encryptor(&mut FixedSeedRNG::new(pinned)).unwrap(); tagged_enc.do_update_aad(aad).unwrap(); let mut tagged_out = vec![0u8; Ascon_AEAD128::::encrypt_out_len(pt.len())]; let written = tagged_enc.do_encrypt_out(&pt, &mut tagged_out).unwrap(); @@ -520,26 +539,19 @@ fn aead128_tagged_and_direct_layouts_agree() { // inline tag, one-shot: its own generated nonce, so what must match is the round trip // and the length, not the bytes. let mut one_shot = vec![0u8; Ascon_AEAD128::::encrypt_out_len(pt.len())]; - let (one_nonce, one_len) = - Ascon_AEAD128::::encrypt_with_aad_out(&km, aad, &pt, &mut one_shot) - .unwrap(); + let (one_len, one_nonce) = km + .encrypt_with_aad_out(&mut HashDRBG_SHA512::new_from_os(), aad, &pt, &mut one_shot) + .unwrap(); assert_eq!(one_len, tagged_out.len(), "pt_len {pt_len}: one-shot writes the same length"); let mut one_back = vec![0u8; Ascon_AEAD128::::decrypt_out_len(one_len)]; - let one_n = Ascon_AEAD128::::decrypt_with_aad_out( - &km, - &one_nonce, - aad, - &one_shot[..one_len], - &mut one_back, - ) - .unwrap(); + let one_n = + km.decrypt_with_aad_out(&one_nonce, aad, &one_shot[..one_len], &mut one_back).unwrap(); assert_eq!(&one_back[..one_n], &pt[..], "pt_len {pt_len}: one-shot round trip"); // ...and all of it decrypts back, each through its own view. The decryptor holds the // last 16 bytes back either way; detached, `do_decrypt_final_detached_out` releases // them. - let mut direct_dec = - Ascon_AEAD128::::do_decrypt_init(&km, &direct_nonce).unwrap(); + let mut direct_dec = km.create_decryptor(&direct_nonce); direct_dec.do_update_aad(aad).unwrap(); let mut direct_pt = vec![0u8; direct_ct.len()]; let got = direct_dec.do_decrypt_out(&direct_ct, &mut direct_pt).unwrap(); @@ -550,8 +562,7 @@ fn aead128_tagged_and_direct_layouts_agree() { direct_pt[got..].copy_from_slice(&last[..last_len]); assert_eq!(direct_pt, pt, "pt_len {pt_len}: direct decrypt round trip"); - let mut tagged_dec = - Ascon_AEAD128::::do_decrypt_init(&km, &tagged_nonce).unwrap(); + let mut tagged_dec = km.create_decryptor(&tagged_nonce); tagged_dec.do_update_aad(aad).unwrap(); let mut tagged_pt = vec![0u8; tagged_out.len()]; let got = tagged_dec.do_decrypt_out(&tagged_out, &mut tagged_pt).unwrap(); @@ -561,25 +572,19 @@ fn aead128_tagged_and_direct_layouts_agree() { assert_eq!(&tagged_pt[..got], &pt[..], "pt_len {pt_len}: tagged decrypt round trip"); let mut one_pt = vec![0u8; Ascon_AEAD128::::decrypt_out_len(tagged_out.len())]; - let n = Ascon_AEAD128::::decrypt_with_aad_out( - &km, &tagged_nonce, aad, &tagged_out, &mut one_pt, - ) - .unwrap(); + let n = km.decrypt_with_aad_out(&tagged_nonce, aad, &tagged_out, &mut one_pt).unwrap(); assert_eq!(&one_pt[..n], &pt[..], "pt_len {pt_len}: streamed ciphertext, one-shot decrypt"); } } -/// With no associated data, the pair used purely as a [`SymmetricCipherEncryptor`] / -/// [`SymmetricCipherDecryptor`] -- nonce driven to the KAT's by a fixed RNG -- reproduces the -/// embedded NIST LWC vectors' `ciphertext || tag`, and decrypts them back. -/// -/// [`SymmetricCipherEncryptor`]: bouncycastle_core::traits::SymmetricCipherEncryptor -/// [`SymmetricCipherDecryptor`]: bouncycastle_core::traits::SymmetricCipherDecryptor +/// With no associated data, the key's inline one-shots with an empty AAD -- nonce driven to the +/// KAT's by a fixed RNG -- reproduce the embedded NIST LWC vectors' `ciphertext || tag`, and +/// decrypt them back. #[test] -fn aead128_symmetric_cipher_view_matches_kat() { +fn aead128_no_aad_one_shots_match_kat() { use bouncycastle_rng::FixedSeedRNG; - let km = key_material(&KEY); + let km = aead_key(&KEY); // The NIST LWC AEAD KAT convention uses Key == Nonce == 000102…0F (i.e. KEY for both). let kat_nonce = KEY; let mut tested = 0; @@ -589,18 +594,13 @@ fn aead128_symmetric_cipher_view_matches_kat() { assert!(dh(ad_hex).is_empty()); let mut ct = vec![0u8; Ascon_AEAD128::::encrypt_out_len(pt.len())]; - let (nonce, n) = Ascon_AEAD128::::encrypt_rng_out( - &km, - &mut FixedSeedRNG::new(kat_nonce), - &pt, - &mut ct, - ) - .unwrap(); + let (n, nonce) = + km.encrypt_with_aad_out(&mut FixedSeedRNG::new(kat_nonce), &[], &pt, &mut ct).unwrap(); assert_eq!(nonce, kat_nonce); - assert_eq!(&ct[..n], &expected[..], "pt {pt_hex}: SymmetricCipherEncryptor view vs KAT"); + assert_eq!(&ct[..n], &expected[..], "pt {pt_hex}: inline one-shot vs KAT"); - let recovered = Ascon_AEAD128::::decrypt(&km, &kat_nonce, &expected).unwrap(); - assert_eq!(recovered, pt, "pt {pt_hex}: SymmetricCipherDecryptor view vs KAT"); + let recovered = km.decrypt_with_aad(&kat_nonce, &[], &expected).unwrap(); + assert_eq!(recovered, pt, "pt {pt_hex}: inline one-shot decrypt vs KAT"); tested += 1; } assert!(tested >= 2, "the embedded KATs must include no-AD vectors"); @@ -618,19 +618,20 @@ fn aead128_suspendable_keyed_state() { let pt = pattern(40); let ad = b"suspend-ad"; let ct_ref = enc_oneshot(&KEY, &NONCE, ad, &pt); - let km = key_material(&KEY); + let key = aead_key(&KEY); - // Encrypt part of the plaintext, suspend, resume with the re-supplied key, finish, and confirm - // the output matches a one-shot encryption. The key is never part of the serialized state. - let mut e = Ascon_AEAD128::::do_encrypt_init_nonce(&km, &NONCE).unwrap(); + // Encrypt part of the plaintext, suspend, resume over the re-supplied key object, finish, and + // confirm the output matches a one-shot encryption. The key is never part of the serialized + // state. + let mut e = key.create_encryptor_nonce(&NONCE); e.do_update_aad(ad).unwrap(); let mut out = vec![0u8; pt.len() + 16]; e.do_encrypt_out(&pt[..18], &mut out[..18]).unwrap(); - TestFrameworkSuspendableKeyedState::new().test(&e, &km); + TestFrameworkSuspendableKeyedState::new().test(&e, &key); let serialized = e.clone().suspend(); - let mut resumed = Ascon_AEAD128::::from_suspended(serialized, &km).unwrap(); + let mut resumed = Ascon_AEAD128::::from_suspended(serialized, &key).unwrap(); resumed.do_encrypt_out(&pt[18..], &mut out[18..pt.len()]).unwrap(); let (last, n) = resumed.do_encrypt_final().unwrap(); out[pt.len()..].copy_from_slice(&last[..n]); @@ -640,7 +641,7 @@ fn aead128_suspendable_keyed_state() { let mut busted = serialized; busted[3] ^= 0xFF; assert!(matches!( - Ascon_AEAD128::::from_suspended(busted, &km), + Ascon_AEAD128::::from_suspended(busted, &key), Err(SuspendableError::InvalidData) )); @@ -650,7 +651,7 @@ fn aead128_suspendable_keyed_state() { let mut bad_state = serialized; bad_state[last] = 200; assert!(matches!( - Ascon_AEAD128::::from_suspended(bad_state, &km), + Ascon_AEAD128::::from_suspended(bad_state, &key), Err(SuspendableError::InvalidData) )); @@ -659,7 +660,7 @@ fn aead128_suspendable_keyed_state() { inconsistent[pos_offset] = 3; // pos = 3 inconsistent[last] = 0; // encrypting, Init assert!(matches!( - Ascon_AEAD128::::from_suspended(inconsistent, &km), + Ascon_AEAD128::::from_suspended(inconsistent, &key), Err(SuspendableError::InvalidData) )); @@ -667,22 +668,22 @@ fn aead128_suspendable_keyed_state() { let mut bad_pos = serialized; bad_pos[pos_offset] = 16; assert!(matches!( - Ascon_AEAD128::::from_suspended(bad_pos, &km), + Ascon_AEAD128::::from_suspended(bad_pos, &key), Err(SuspendableError::InvalidData) )); // The decryptor: part of the ciphertext in (all but 16 bytes released), suspend, resume, // the rest in, and the inline final checks the tag. - let mut d = Ascon_AEAD128::::do_decrypt_init(&km, &NONCE).unwrap(); + let mut d = key.create_decryptor(&NONCE); d.do_update_aad(ad).unwrap(); let mut back = vec![0u8; ct_ref.len()]; let n1 = d.do_decrypt_out(&ct_ref[..30], &mut back).unwrap(); assert_eq!(n1, 14); - TestFrameworkSuspendableKeyedState::new().test(&d, &km); + TestFrameworkSuspendableKeyedState::new().test(&d, &key); let d_state = d.clone().suspend(); - let mut d2 = Ascon_AEAD128::::from_suspended(d_state, &km).unwrap(); + let mut d2 = Ascon_AEAD128::::from_suspended(d_state, &key).unwrap(); let n2 = d2.do_decrypt_out(&ct_ref[30..], &mut back[n1..]).unwrap(); let (_, n3) = d2.do_decrypt_final().unwrap(); assert_eq!(n3, 0, "the final releases nothing: what it held was the tag"); @@ -693,7 +694,7 @@ fn aead128_suspendable_keyed_state() { let mut bad_held = d_state; bad_held[SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN - 1] = 17; assert!(matches!( - Ascon_AEAD128::::from_suspended(bad_held, &km), + Ascon_AEAD128::::from_suspended(bad_held, &key), Err(SuspendableError::InvalidData) )); @@ -702,13 +703,13 @@ fn aead128_suspendable_keyed_state() { let mut enc_as_dec = [0u8; SUSPENDED_ASCON_AEAD128_DECRYPTOR_STATE_LEN]; enc_as_dec[..SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN].copy_from_slice(&serialized); assert!(matches!( - Ascon_AEAD128::::from_suspended(enc_as_dec, &km), + Ascon_AEAD128::::from_suspended(enc_as_dec, &key), Err(SuspendableError::InvalidData) )); let mut dec_as_enc = [0u8; SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN]; dec_as_enc.copy_from_slice(&d_state[..SUSPENDED_ASCON_AEAD128_ENCRYPTOR_STATE_LEN]); assert!(matches!( - Ascon_AEAD128::::from_suspended(dec_as_enc, &km), + Ascon_AEAD128::::from_suspended(dec_as_enc, &key), Err(SuspendableError::InvalidData) )); } diff --git a/crypto/ascon/tests/ascon_bc-test-data.rs b/crypto/ascon/tests/ascon_bc-test-data.rs index b83552f95..797648f04 100644 --- a/crypto/ascon/tests/ascon_bc-test-data.rs +++ b/crypto/ascon/tests/ascon_bc-test-data.rs @@ -8,14 +8,15 @@ //! 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, AsconAEAD128Key}; 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::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, HashOneShot, SymmetricCipherDecryptor, SymmetricCipherEncryptor, XOF, @@ -100,6 +101,10 @@ fn key_material(key: &[u8; 16]) -> KeyMaterial<16> { km } +fn aead_key(key: &[u8; 16]) -> AsconAEAD128Key { + AsconAEAD128Key::from_key_material(&key_material(key)).expect("a valid key") +} + #[test] fn ascon_aead128_kat() { let Some(contents) = bc_test_data(TEST_DATA_DIR, "asconaead128/LWC_AEAD_KAT_128_128.txt") @@ -111,7 +116,7 @@ fn ascon_aead128_kat() { assert!(!cases.is_empty(), "no AEAD cases parsed"); for case in &cases { - let key = key_material(&to_16(&decode_hex(field(case, &["Key", "K"])), "key")); + let key = aead_key(&to_16(&decode_hex(field(case, &["Key", "K"])), "key")); let nonce = to_16(&decode_hex(field(case, &["Nonce", "N"])), "nonce"); let ad = decode_hex(field(case, &["AD", "A"])); let pt = decode_hex(field(case, &["PT", "P"])); @@ -119,27 +124,23 @@ fn ascon_aead128_kat() { // One-shot encrypt. let mut ct = vec![0u8; Ascon_AEAD128::::encrypt_out_len(pt.len())]; - let n = Ascon_AEAD128::::encrypt_with_aad_nonce_out( - &key, &nonce, &ad, &pt, &mut ct, - ) - .unwrap(); + let n = key.encrypt_with_aad_nonce_out(&nonce, &ad, &pt, &mut ct).unwrap(); ct.truncate(n); assert_eq!(ct, expected_ct, "encrypt mismatch (Count {})", field(case, &["Count"])); // One-shot decrypt round-trip. let mut pt_out = vec![0u8; Ascon_AEAD128::::decrypt_out_len(expected_ct.len())]; - let m = Ascon_AEAD128::::decrypt_with_aad_out( - &key, &nonce, &ad, &expected_ct, &mut pt_out, - ) - .expect("decrypt should authenticate"); + let m = key + .decrypt_with_aad_out(&nonce, &ad, &expected_ct, &mut pt_out) + .expect("decrypt should authenticate"); pt_out.truncate(m); assert_eq!(pt_out, pt, "decrypt mismatch (Count {})", field(case, &["Count"])); // Byte-at-a-time streaming encrypt/decrypt. - let mut enc = Ascon_AEAD128::::do_encrypt_init_nonce(&key, &nonce).unwrap(); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(&ad).unwrap(); let mut stream_ct = Vec::with_capacity(expected_ct.len()); @@ -159,7 +160,7 @@ fn ascon_aead128_kat() { field(case, &["Count"]) ); - let mut dec = Ascon_AEAD128::::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(&ad).unwrap(); let mut stream_pt = Vec::with_capacity(pt.len()); diff --git a/crypto/ascon/tests/ascon_wycheproof.rs b/crypto/ascon/tests/ascon_wycheproof.rs index cdd82cd6e..798a001f3 100644 --- a/crypto/ascon/tests/ascon_wycheproof.rs +++ b/crypto/ascon/tests/ascon_wycheproof.rs @@ -9,15 +9,14 @@ //! 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, AsconAEAD128Key, KEY_LEN, NONCE_LEN, TAG_LEN}; 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::{ - AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, -}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; 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 @@ -33,6 +32,10 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { key } +fn aead_key(bytes: &[u8]) -> AsconAEAD128Key { + AsconAEAD128Key::from_key_material(&cipher_key(bytes)).expect("a wycheproof key") +} + #[test] fn wycheproof_ascon_aead128() { let Some(doc) = wycheproof_json("ascon_sp800_232_aead128_test.json") else { return }; @@ -48,7 +51,7 @@ fn wycheproof_ascon_aead128() { for test in group.get("tests").and_then(Value::as_array).expect("tests") { let tc_id = test.get("tcId").and_then(Value::as_u64).expect("tcId"); - let key = cipher_key(&hex_field(test, "key", tc_id)); + let key = aead_key(&hex_field(test, "key", tc_id)); let nonce: [u8; NONCE_LEN] = hex_field(test, "iv", tc_id).try_into().expect("a 16-byte nonce"); let aad = hex_field(test, "aad", tc_id); @@ -57,18 +60,15 @@ fn wycheproof_ascon_aead128() { let mut pt = vec![0xEEu8; Ascon_AEAD128::::decrypt_out_len(ct_and_tag.len())]; - let decrypted = Ascon_AEAD128::::decrypt_with_aad_out( - &key, &nonce, &aad, &ct_and_tag, &mut pt, - ); + let decrypted = key.decrypt_with_aad_out(&nonce, &aad, &ct_and_tag, &mut pt); match test.get("result").and_then(Value::as_str).expect("result") { "valid" => { let mut out = vec![0u8; Ascon_AEAD128::::encrypt_out_len(msg.len())]; - let n = Ascon_AEAD128::::encrypt_with_aad_nonce_out( - &key, &nonce, &aad, &msg, &mut out, - ) - .unwrap_or_else(|e| panic!("tcId {tc_id}: encrypt failed: {e:?}")); + let n = key + .encrypt_with_aad_nonce_out(&nonce, &aad, &msg, &mut out) + .unwrap_or_else(|e| panic!("tcId {tc_id}: encrypt failed: {e:?}")); assert_eq!(&out[..n], &ct_and_tag[..], "tcId {tc_id}: ct || tag"); let n = decrypted.unwrap_or_else(|e| panic!("tcId {tc_id}: decrypt: {e:?}")); diff --git a/crypto/cipher/src/lib.rs b/crypto/cipher/src/lib.rs index 3b17b89d3..ec8ea7c13 100644 --- a/crypto/cipher/src/lib.rs +++ b/crypto/cipher/src/lib.rs @@ -13,31 +13,37 @@ //! # Suspending and resuming execution //! //! Every mode and adapter implements `SuspendableKeyed`, so a message in progress can be suspended -//! to a byte array and resumed later with the re-supplied key. The length of that array is the +//! to a byte array and resumed later over the re-supplied key state, which a key object exposes +//! through `bouncycastle_core::hazmat::ModeKey::state`. The length of that array is the //! type's `SUSPENDED_STATE_LEN`, and a wrong length is a compile error; the mechanism is //! [`bouncycastle_utils::suspendable_state`]. A suspended state holds everything the message in //! progress depends on except the key -- a chaining block, live keystream, a running MAC -- so //! protect it as the plaintext it governs, and never resume one state twice. //! //! ``` -//! use bouncycastle_cipher::modes::Cbc; +//! use bouncycastle_cipher::modes::{Blocks, CBC, Cbc}; //! use bouncycastle_cipher::Encrypting; +//! use bouncycastle_core::hazmat::{BlockEncryptMode, ElectronicCodeBook, Mode}; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherEncryptor, SuspendableKeyed}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! # mod toy { include!("../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; //! -//! type ToyCbc = Cbc; +//! type ToyCbc<'k> = Cbc<'k, ToyBlockCipher, Encrypting, 16>; //! const STATE_LEN: usize = ToyCbc::SUSPENDED_STATE_LEN; //! +//! // The mode's key state, as a mode-bound key such as `AES128Key>` holds it. //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! let (mut enc, _iv) = ToyCbc::do_encrypt_init(&key).unwrap(); +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); +//! let (mut enc, _iv) = CBC::::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()).unwrap(); //! let mut first = [[0x11u8; 16]]; //! enc.do_encrypt_blocks_inplace(&mut first).unwrap(); //! -//! // Suspending consumes the cipher. The key is not in the state and is re-supplied to resume. -//! let state: [u8; STATE_LEN] = enc.suspend(); -//! let mut enc = ToyCbc::from_suspended(state, &key).unwrap(); +//! // Suspending consumes the cipher. The key is not in the suspended state; resume borrows the +//! // key state again, as `create_encryptor` did. +//! let suspended: [u8; STATE_LEN] = enc.suspend(); +//! let mut enc = ToyCbc::from_suspended(suspended, &state).unwrap(); //! let mut second = [[0x22u8; 16]]; //! enc.do_encrypt_blocks_inplace(&mut second).unwrap(); //! ``` @@ -93,8 +99,8 @@ mod sealed { /// /// ```text /// pub type AES_CBC_128 = ::Select< -/// PaddedBlockCipherEncryptor, Pad, 16, 16, 16>, -/// PaddedBlockCipherDecryptor, Pad, 16, 16, 16>, +/// PaddedBlockCipherEncryptor, Pad, 16, 16>, +/// PaddedBlockCipherDecryptor, Pad, 16, 16>, /// >; /// ``` /// @@ -113,12 +119,19 @@ mod sealed { pub trait Direction: sealed::Sealed { /// `Enc` for [`Encrypting`], `Dec` for [`Decrypting`]. type Select; + + /// The bits that mark this direction in a suspended call-state byte, above the low nibble a + /// mode keeps for its phase: `0x10` encrypting, `0x20` decrypting. A state resumed under the + /// other marker is refused, so an encryptor's state never continues as a decryptor. + const STATE_BITS: u8; } impl Direction for Encrypting { type Select = Enc; + const STATE_BITS: u8 = 0x10; } impl Direction for Decrypting { type Select = Dec; + const STATE_BITS: u8 = 0x20; } diff --git a/crypto/cipher/src/modes/cbc.rs b/crypto/cipher/src/modes/cbc.rs index 8cddae314..f9931ffc8 100644 --- a/crypto/cipher/src/modes/cbc.rs +++ b/crypto/cipher/src/modes/cbc.rs @@ -9,40 +9,49 @@ //! the forward cipher operations cannot be performed in parallel". //! //! This implementation uses that: decryption walks the ciphertext four blocks at a time through -//! [`ElectronicCodeBook::decrypt_4blocks`], then any remaining pair through -//! [`ElectronicCodeBook::decrypt_2blocks`], then the last block singly. A bit-sliced engine +//! [`BlockPermutation::decrypt_4blocks`], then any remaining pair through +//! [`BlockPermutation::decrypt_2blocks`], then the last block singly. A bit-sliced engine //! computes two or four blocks (AES, on `u32` or `u64` planes) for barely more than the cost of //! one. Encryption cannot, and does not. //! //! # 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<'k, P, Encrypting, ..>`](Cbc) implements +//! [`BlockCipherEncryptor`] and nothing else, and [`Cbc<'k, P, Decrypting, ..>`](Cbc) implements //! [`BlockCipherDecryptor`] and nothing else. -//! The IV is generated and returned; there is no API for supplying one. +//! The IV is drawn from the RNG and returned; there is no API for supplying one. +//! +//! The pair is reached through a mode-bound key -- [`CBC`] on a key type such as +//! `bouncycastle_aes::AES128Key>` for whole blocks, [`CBC`](CBC) for the padded +//! form -- whose role traits carry the one-shots. The key holds the mode's key state and calls the +//! mode traits shown here, which is the route over the toy permutation: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{BlockDecryptMode, BlockEncryptMode, ElectronicCodeBook, Mode}; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -//! use bouncycastle_cipher::modes::Cbc; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyCbc = Cbc; +//! use bouncycastle_cipher::modes::{Blocks, CBC}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); +//! let mut rng = HashDRBG_SHA512::new_from_os(); //! -//! // 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!"; +//! // Three whole blocks: the workers take `&mut [[u8; 16]]`, so a partial block cannot be +//! // expressed. +//! let plaintext: [[u8; 16]; 3] = +//! [*b"The quick brown ", *b"fox jumps over t", *b"he lazy dog. OK!"]; //! -//! // One shot, in place: encrypts under a freshly generated IV, which is returned. +//! // In place, under a freshly drawn IV, which is returned. //! let mut data = plaintext; -//! let (_, iv) = ToyCbc::::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (mut enc, iv) = CBC::::encrypt_init(&state, &mut rng).expect("iv draw"); +//! enc.do_encrypt_blocks_inplace(&mut data).expect("encryption"); //! assert_ne!(data, plaintext); //! -//! ToyCbc::::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); +//! CBC::::decrypt_init(&state, &iv).do_decrypt_blocks_inplace(&mut data).expect("decryption"); //! assert_eq!(data, plaintext); //! ``` //! @@ -52,54 +61,35 @@ //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{BlockDecryptMode, BlockEncryptMode, ElectronicCodeBook, Mode}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -//! use bouncycastle_cipher::modes::Cbc; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyCbc = Cbc; +//! use bouncycastle_cipher::modes::{Blocks, CBC}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! //! let key = KeyMaterial128::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); //! //! let (mut encryptor, iv) = -//! ToyCbc::::do_encrypt_init(&key).expect("encrypt init"); +//! CBC::::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()).expect("iv draw"); //! let mut first = [[0xAAu8; 16]]; //! let mut rest = [[0xBBu8; 16]; 2]; //! encryptor.do_encrypt_blocks_inplace(&mut first).expect("block 1"); //! encryptor.do_encrypt_blocks_inplace(&mut rest).expect("blocks 2-3"); //! -//! let mut decryptor = ToyCbc::::do_decrypt_init(&key, &iv).expect("decrypt init"); +//! let mut decryptor = CBC::::decrypt_init(&state, &iv); //! decryptor.do_decrypt_blocks_inplace(&mut first).unwrap(); //! decryptor.do_decrypt_blocks_inplace(&mut rest).unwrap(); //! assert_eq!(first, [[0xAAu8; 16]]); //! assert_eq!(rest, [[0xBBu8; 16]; 2]); //! ``` //! -//! A length that is not a whole number of blocks is refused, not truncated: -//! -//! ``` -//! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::errors::SymmetricCipherError; -//! use bouncycastle_core::traits::BlockCipherEncryptor; -//! use bouncycastle_cipher::modes::Cbc; -//! use bouncycastle_cipher::Encrypting; -//! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! let mut data = [0u8; 20]; -//! assert_eq!( -//! Cbc::::encrypt_inplace(&key, &mut data), -//! Err(SymmetricCipherError::DataNotBlockAligned) -//! ); -//! ``` -//! //! # Suspending and resuming execution //! //! [`Cbc`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a byte -//! array and resumed later with the re-supplied key. The state is the chaining block; the -//! permutation is rebuilt from the key. The array length is `Cbc::SUSPENDED_STATE_LEN`; see [the +//! array and resumed later over the re-supplied key state. The state is the chaining block; the +//! permutation is the key's. The array length is `Cbc::SUSPENDED_STATE_LEN`; see [the //! crate docs](crate#suspending-and-resuming-execution) for an example. //! //! # 🚨 Security Considerations 🚨 @@ -116,26 +106,123 @@ //! So, while the IV need not be secret, best-practice is to authenticate it along with the ciphertext, //! or use an authenticated (AEAD) mode such as GCM. -use crate::modes::ModeNames; use crate::modes::iv::random_iv; +use crate::modes::{Blocks, ModeNames}; +use crate::padding::{PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::hazmat::{ + BlockDecryptMode, BlockEncryptMode, BlockPermutation, ElectronicCodeBook, Mode, + SymmetricDecryptMode, SymmetricEncryptMode, +}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SuspendableKeyed, + Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, BlockCipherPadding, RNG, + SuspendableKeyed, }; -use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::suspendable_state::{ LIB_VERSION_LEN, SuspendableComponent, resume_component, suspend_component, }; use core::marker::PhantomData; +/// The CBC mode type: the `M` of a mode-bound key such as `AES128Key>`. `X` is the +/// padding scheme, which puts the key on the symmetric path with the padded adapter as its +/// worker, or [`Blocks`], which puts it on the block path with [`Cbc`] itself. Its key state is +/// the keyed permutation alone: CBC derives nothing from the key. +/// +/// [`PKCS7`]: crate::padding::PKCS7 +pub struct CBC(core::marker::PhantomData); + +impl Mode

for CBC { + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl + SymmetricEncryptMode for CBC +where + P: ElectronicCodeBook + ModeNames + Clone, + Pad: BlockCipherPadding, +{ + type Encryptor<'k> + = PaddedBlockCipherEncryptor, Pad, BLOCK_LEN, BLOCK_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; BLOCK_LEN]), SymmetricCipherError> { + let iv = random_iv::(rng)?; + Ok((PaddedBlockCipherEncryptor::from_inner(Cbc::from_perm(state, iv)), iv)) + } +} + +impl + SymmetricDecryptMode for CBC +where + P: ElectronicCodeBook + ModeNames + Clone, + Pad: BlockCipherPadding, +{ + type Decryptor<'k> + = PaddedBlockCipherDecryptor, Pad, BLOCK_LEN, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; BLOCK_LEN], + ) -> Self::Decryptor<'k> { + PaddedBlockCipherDecryptor::from_inner(Cbc::from_perm(state, *init_data)) + } +} + +impl + BlockEncryptMode for CBC +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Cbc<'k, P, Encrypting, BLOCK_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; BLOCK_LEN]), SymmetricCipherError> { + let iv = random_iv::(rng)?; + Ok((Cbc::from_perm(state, iv), iv)) + } +} + +impl + BlockDecryptMode for CBC +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Cbc<'k, P, Decrypting, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; BLOCK_LEN], + ) -> Self::Decryptor<'k> { + Cbc::from_perm(state, *init_data) + } +} + /// 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<'k, _, Encrypting, _, _>` has no /// decryption methods at all -- using one in the wrong direction is a compile error rather than a /// runtime check. /// @@ -143,28 +230,33 @@ use core::marker::PhantomData; /// /// # State /// -/// Two fields: the permutation (which owns the key schedule, and is responsible for keeping it in -/// a zeroize-on-drop wrapper) and one block of chaining value. The chaining value is an IV or a +/// Two fields: a reference to the permutation, whose key schedule stays in the key, 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<'k, P, Dir, const BLOCK_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k 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

, } -impl Cbc +impl<'k, P, Dir, const BLOCK_LEN: usize> Cbc<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header and the chaining + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the version header and the chaining /// block. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = LIB_VERSION_LEN + BLOCK_LEN; + /// `C0 = IV`, over an already-keyed permutation: what the [`CBC`] mode impls build. + pub(crate) fn from_perm(perm: &'k P, iv: [u8; BLOCK_LEN]) -> Self { + Self { perm, chain: iv, _dir: PhantomData } + } + /// `Cj = CIPH_K(Pj XOR Cj-1)` in place, then `Cj` becomes the next chaining value. #[inline] fn encrypt_one(&mut self, block: &mut [u8; BLOCK_LEN]) { @@ -188,7 +280,7 @@ where self.chain = cj; } - /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::decrypt_2blocks`] call. + /// Decrypts two consecutive blocks with one [`BlockPermutation::decrypt_2blocks`] call. /// /// Writing the pair as `Cj, Cj+1` with `Cj-1` the incoming chaining value, Sec 6.2 gives /// @@ -217,7 +309,7 @@ where self.chain = cj1; } - /// Decrypts four consecutive blocks with one [`ElectronicCodeBook::decrypt_4blocks`] call. + /// Decrypts four consecutive blocks with one [`BlockPermutation::decrypt_4blocks`] call. /// /// The same argument as [`Self::decrypt_pair`], four wide: `Pj+k = CIPH^-1_K(Cj+k) XOR Cj+k-1` /// for `k = 0..4`, with `Cj-1` the incoming chaining value. No inverse cipher depends on @@ -240,10 +332,9 @@ where } } -impl Algorithm - for Cbc +impl<'k, P, Dir, const BLOCK_LEN: usize> Algorithm for Cbc<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::CBC_ALG_NAME`]. const ALG_NAME: &'static str = P::CBC_ALG_NAME; @@ -251,29 +342,13 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - BlockCipherEncryptor for Cbc +impl<'k, P, const BLOCK_LEN: usize> BlockCipherEncryptor + for Cbc<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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> { - 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; - let iv = random_iv::(rng)?; - Ok((Self { perm, chain: iv, _dir: PhantomData }, iv)) - } - + /// The implementor hook. + /// /// Strictly serial: `Cj` is the input to block `j + 1`, so there is no pair path here. See the /// module docs. Never fails: CBC has no per-IV data limit. fn do_encrypt_blocks_inplace( @@ -287,21 +362,13 @@ where } } -impl - BlockCipherDecryptor for Cbc +impl<'k, P, const BLOCK_LEN: usize> BlockCipherDecryptor + for Cbc<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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 }) - } - + /// The implementor hook. + /// /// Walks the input in fours through `decrypt_4blocks`, then pairs through `decrypt_2blocks`, /// then the at-most-one block left over: Sec 6.2's parallelism, in the units the permutation /// offers. `as_chunks_mut` splits into exactly those shapes with no runtime length check and no @@ -326,22 +393,21 @@ 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 +/// The suspended state is the chaining block `Cj-1`, in both directions; the permutation is the +/// key's, re-supplied by reference. See [`bouncycastle_utils::suspendable_state`]. +impl<'k, P, Dir, const BLOCK_LEN: usize> SuspendableComponent<'k> for Cbc<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = BLOCK_LEN; - type Key = KeyMaterial; + type Key = P; 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)?; + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + let perm = key; let mut chain = [0u8; BLOCK_LEN]; chain.copy_from_slice(state); Ok(Self { perm, chain, _dir: PhantomData }) @@ -349,18 +415,21 @@ where } /// `N` must be [`Cbc::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cbc +impl<'k, P, Dir, const BLOCK_LEN: usize, const N: usize> SuspendableKeyed<'k, N> + for Cbc<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + /// The state carries no direction: one suspended by the other direction's worker is accepted + /// and resumes over the same chaining value, which gives wrong output rather than an error. + /// Only GCM, whose state means different things per direction, refuses the other one. + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/src/modes/ccm.rs b/crypto/cipher/src/modes/ccm.rs index d4332d6ee..2ad71174c 100644 --- a/crypto/cipher/src/modes/ccm.rs +++ b/crypto/cipher/src/modes/ccm.rs @@ -12,49 +12,52 @@ //! //! # Usage Examples //! -//! CCM must know the payload length before it starts (Sec 3), and the generic traits' streaming -//! constructor is handed no lengths, so [`CcmEncryptor`] / [`CcmDecryptor`] fix the frame in the -//! type: `DATA_LEN` bytes of payload exactly, up to `AAD_LEN` of AAD. Their one-shots have the -//! whole message in hand and take any length. The nonce is drawn unless a `_nonce` form is used; -//! CCM needs it unique, not random (Sec 5.3), so a caller with a counter supplies its own. The -//! AAD is authenticated but not encrypted, and the tag is the extra output. +//! CCM must know the payload length before it starts (Sec 3). A key bound to [`CCM`] takes the +//! lengths per message through the AEAD *declared-length* role pair, and its in-place one-shots take any +//! length with no second buffer; a key bound to [`CCM_Packet`] fixes them in the type, as the +//! [`CcmPacketEncryptor`] / [`CcmPacketDecryptor`] streams it hands out do: `DATA_LEN` bytes of payload +//! exactly, up to `AAD_LEN` of AAD. The nonce is drawn unless a `_nonce` form is used; CCM needs +//! it unique, not random (Sec 5.3), so a caller with a counter supplies its own. The AAD is +//! authenticated but not encrypted, and the tag is the extra output. +//! +//! The key holds the mode's key state and calls the mode traits, which is the route over the toy +//! permutation: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; //! use bouncycastle_core::errors::SymmetricCipherError; +//! use bouncycastle_core::hazmat::{AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, Mode}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; -//! use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor}; +//! use bouncycastle_cipher::modes::CCM; //! -//! // A 12-byte nonce, a 16-byte tag, 64 bytes of AAD capacity and 32-byte frames. -//! type ToyEnc = CcmEncryptor; -//! type ToyDec = CcmDecryptor; +//! // A 12-byte nonce and a 16-byte tag. +//! type Mode12 = CCM<12, 16>; //! //! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let state = >::key_state(ToyBlockCipher::new(&key).unwrap()); //! let nonce = [0x01u8; 12]; //! let header = b"authenticated, not encrypted"; //! let message = b"any length: CCM pads internally"; //! -//! // The one-shots, in the spec's own layout (Sec 6.1 step 8): `ciphertext || tag`. -//! let ct_and_tag = ToyEnc::encrypt_with_aad_nonce(&key, &nonce, header, message).expect("encryption"); -//! let recovered = ToyDec::decrypt_with_aad(&key, &nonce, header, &ct_and_tag).expect("decryption"); -//! assert_eq!(&recovered[..], message); +//! // In place, with the tag detached. +//! let mut buf = message.to_vec(); +//! let tag = Mode12::encrypt_detached_in_place_nonce(&state, &nonce, header, &mut buf).expect("encryption"); +//! let ciphertext = buf.clone(); +//! Mode12::decrypt_detached_in_place(&state, &nonce, header, &mut buf, &tag).expect("decryption"); +//! assert_eq!(&buf[..], message); //! //! // Tampering with the ciphertext, or changing the AAD, fails the tag check. -//! let mut tampered = ct_and_tag.clone(); +//! let mut tampered = ciphertext.clone(); //! tampered[0] ^= 1; -//! assert_eq!(ToyDec::decrypt_with_aad(&key, &nonce, header, &tampered).unwrap_err(), +//! assert_eq!(Mode12::decrypt_detached_in_place(&state, &nonce, header, &mut tampered, &tag).unwrap_err(), //! SymmetricCipherError::AEADTagCheckFailed); -//! assert_eq!(ToyDec::decrypt_with_aad(&key, &nonce, b"other header", &ct_and_tag).unwrap_err(), +//! let mut other = ciphertext.clone(); +//! assert_eq!(Mode12::decrypt_detached_in_place(&state, &nonce, b"other header", &mut other, &tag).unwrap_err(), //! SymmetricCipherError::AEADTagCheckFailed); //! ``` //! -//! A mode-bound key, `AES128Key>` and the like, reaches the same pair through -//! its role traits, and its in-place one-shots take any length with no second buffer; see -//! [`CCM`]. -//! //! # CCM is not a stream cipher //! //! It does not have an indefinite-length streaming mode. @@ -73,10 +76,10 @@ //! //! # Suspending and resuming execution //! -//! [`CcmEncryptor`] and [`CcmDecryptor`] implement [`SuspendableKeyed`], so a message in progress +//! [`CcmPacketEncryptor`] and [`CcmPacketDecryptor`] implement [`SuspendableKeyed`], so a message in progress //! can be suspended to a byte array and resumed later with the re-supplied key. The state is the //! CTR half, the CBC-MAC chaining value, the payload still owed and the AAD held back so far; the -//! permutation is rebuilt from the key. The array length is each type's `SUSPENDED_STATE_LEN`; +//! permutation is reached through the re-supplied key. The array length is each type's `SUSPENDED_STATE_LEN`; //! see [the crate docs](crate#suspending-and-resuming-execution) for an example. //! //! # 🚨 Security Considerations 🚨 @@ -106,16 +109,15 @@ use crate::modes::iv::random_iv; use crate::stream::StreamCipher; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; use bouncycastle_core::hazmat::{ - AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook, KeyStream, Mode, + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, AeadDecryptMode, AeadEncryptMode, + BlockPermutation, ElectronicCodeBook, KeyStream, Mode, }; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::symmetric_key::{AeadDecryptWorker, AeadEncryptWorker}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::ct::ct_eq_bytes; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ @@ -129,33 +131,27 @@ use crate::{Decrypting, Encrypting}; /// CCM (SP 800-38C) over any [`ElectronicCodeBook`] with a 128-bit block, for Sec 3's "packet /// environment, i.e., when all of the data is available in storage before CCM is applied": the /// AAD and payload lengths are declared per message, and the nonce is the caller's. Crate-private: -/// the trait-implementing pair, [`CcmEncryptor`] / [`CcmDecryptor`], is built on it, and the +/// the trait-implementing pair, [`CcmPacketEncryptor`] / [`CcmPacketDecryptor`], is built on it, and the /// [`CCM`] mode's in-place one-shots run it over a message's own length. /// -/// `Dir` is [`Encrypting`] or [`Decrypting`]: `CcmPacket` has Sec 6.1's -/// methods and nothing else, and `CcmPacket` has Sec 6.2's. A.1's shape +/// `Dir` is [`Encrypting`] or [`Decrypting`]: `Ccm<'k, P, Encrypting, ..>` has Sec 6.1's +/// methods and nothing else, and `Ccm<'k, P, Decrypting, ..>` has Sec 6.2's. A.1's shape /// conditions on `NONCE_LEN` and `TAG_LEN` are asserted at compile time in `check_shape`, which /// every constructor runs; the pair's docs carry the `compile_fail` cases. #[derive(Clone)] -pub(crate) struct CcmPacket< +pub(crate) struct Ccm< + 'k, P, Dir, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { // 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, - Dir, - KEY_LEN, - NONCE_LEN, - BLOCK_LEN, - >, + ctr: StreamCipher, Dir, NONCE_LEN, BLOCK_LEN>, // The CBC-MAC chaining value: `Y0` once the constructor has absorbed `B0` (Sec 6.1 step 2), // then `Yi` as further blocks arrive (step 3). Bytes are XORed into it in place, so part-way // through a block it holds `Yi-1 XOR (the part of Bi seen so far)`. @@ -179,28 +175,21 @@ pub(crate) struct CcmPacket< _dir: PhantomData, } -impl< - P, - Dir, - const KEY_LEN: usize, - const BLOCK_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, -> CcmPacket +impl<'k, P, Dir, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> + Ccm<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// 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 CTR half's share of the suspended state. const CTR_STATE_LEN: usize = , + CcmKeyStream<'k, P, BLOCK_LEN, NONCE_LEN>, Dir, - KEY_LEN, NONCE_LEN, BLOCK_LEN, - > as SuspendableComponent>::STATE_LEN; + > as SuspendableComponent<'k>>::STATE_LEN; /// The largest payload this parameterization can carry, from A.1's "by definition, p<2^8q". /// @@ -238,48 +227,13 @@ where }; } - /// Begins a CCM flow: formats `B0`, absorbs it and all of `A` into the CBC-MAC, and readies the - /// counter blocks. Everything after this streams without buffering. - /// - /// `payload_len` is declared here because Appendix A.2.1 puts the payload length inside `B0`, - /// the first block the CBC-MAC absorbs. An AAD that is not all in hand at once goes through - /// the adapters, which hold it back until it is. - /// - /// * `key` must be a [`KeyType::SymmetricCipherKey`](bouncycastle_core::key_material::KeyType::SymmetricCipherKey) - /// of at least the permutation's strength. - /// * `nonce` **must not** repeat under `key`; see the module's security considerations. - /// * `aad` is authenticated but not encrypted, and may be empty. - /// * `payload_len` is the exact number of payload bytes that will follow. Supplying any other - /// amount is refused, at the update or at finalization. - /// - /// # Errors - /// [`SymmetricCipherError::KeyMaterialError`] for a key of the wrong type or strength, and - /// [`SymmetricCipherError::GenericError`] if `payload_len` exceeds A.1's `2^8q - 1`; see - /// `CcmPacket` for the table. - pub(crate) fn new( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - payload_len: usize, - ) -> Result { - // The shape check and the payload-limit check both belong to `from_perm_with_lengths`, - // 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 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); - Ok(ccm) - } - - /// As [`Self::new`], from a key schedule that has already been expanded: the constructor a - /// mode-bound key's in-place one-shots use, where the key was validated once at construction. + /// From a key schedule that has already been expanded: the constructor a mode-bound key's + /// in-place one-shots use, where the key was validated once at construction. /// /// # Errors /// [`SymmetricCipherError::GenericError`] if `payload_len` exceeds A.1's `2^8q - 1`. pub(crate) fn from_perm( - perm: P, + perm: &'k P, nonce: &[u8; NONCE_LEN], aad: &[u8], payload_len: usize, @@ -314,7 +268,7 @@ where /// # Errors /// [`SymmetricCipherError::StateError`] if `aad` would take the total past the declared AAD /// length -- which includes any non-empty AAD once the declared amount is complete, and so any - /// after [`Self::new`], which declares exactly the AAD it is given. Nothing is absorbed in that + /// after construction, which declares exactly the AAD it is given. Nothing is absorbed in that /// case. pub(crate) fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { if aad.len() > self.aad_owed { @@ -346,7 +300,7 @@ where /// than the AAD itself: formats and absorbs `B0` and, if there is any AAD, the encoding of /// its length, leaving the AAD itself to [`Self::absorb_aad`]. fn from_perm_with_lengths( - perm: P, + perm: &'k P, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, @@ -365,12 +319,12 @@ where /// The state *before* Sec 6.1 step 1: the keystream is positioned at `S1` and `payload_len` /// is owed, but nothing has been absorbed into the CBC-MAC, not even `B0`. /// - /// Crate-private, for [`CcmEncryptor`] / [`CcmDecryptor`]: the trait constructor they + /// Crate-private, for [`CcmPacketEncryptor`] / [`CcmPacketDecryptor`]: the trait constructor they /// implement is handed a key and no AAD, and `B0`'s Adata bit (A.2.2: "'0' if a=0 and '1' if /// a>0") cannot be set until the AAD is complete. They call [`Self::format_header`] when it /// is, and nothing else may touch the MAC before that. `payload_len` must already be at most /// `Self::MAX_PAYLOAD_LEN`; the adapters assert theirs at compile time. - fn from_perm_unformatted(perm: P, nonce: &[u8; NONCE_LEN], payload_len: usize) -> Self { + fn from_perm_unformatted(perm: &'k P, nonce: &[u8; NONCE_LEN], payload_len: usize) -> Self { Self::check_shape(); Self { ctr: StreamCipher::from_keystream(CcmKeyStream::from_perm(perm, nonce)), @@ -478,7 +432,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 } @@ -521,10 +475,12 @@ where } } - /// Debits `len` bytes from the payload length declared to [`Self::new`], refusing any payload - /// while declared AAD is still outstanding. + /// Whether `len` bytes of payload would be accepted now: refused while declared AAD is still + /// outstanding, or past the payload length declared at construction. Checked by the + /// `_out` workers before they copy anything into the caller's buffer, and again by + /// [`take_owed`](Self::take_owed) when the bytes are debited. #[inline] - fn take_owed(&mut self, len: usize) -> Result<(), SymmetricCipherError> { + pub(crate) fn accepts_payload(&self, len: usize) -> Result<(), SymmetricCipherError> { if len > 0 && self.aad_owed != 0 { return Err(SymmetricCipherError::StateError( "CCM was given payload before all of the declared AAD; A.2.3 puts the payload \ @@ -536,6 +492,14 @@ where "CCM was given more payload than the declared payload length, which B0 commits to", )); } + Ok(()) + } + + /// Debits `len` bytes from the payload length declared at construction, refusing any payload + /// while declared AAD is still outstanding. + #[inline] + fn take_owed(&mut self, len: usize) -> Result<(), SymmetricCipherError> { + self.accepts_payload(len)?; self.owed -= len; Ok(()) } @@ -553,9 +517,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( - &keystream.ctr_template, 0, - ); + *s0 = CcmKeyStream::::counter_block(&keystream.ctr_template, 0); keystream.perm.encrypt_block(&mut s0); // `MSB_Tlen` of a byte-aligned value is its first `TAG_LEN` bytes; A.1 makes `t` an octet @@ -570,10 +532,10 @@ 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<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> + Ccm<'k, P, Encrypting, BLOCK_LEN, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Encrypts `data` in place and authenticates it. /// @@ -598,7 +560,7 @@ where /// /// # Errors /// [`SymmetricCipherError::StateError`] if less payload was supplied than the length declared - /// to [`Self::new`] -- `B0` commits to that length, so a short message would produce a tag no + /// at construction -- `B0` commits to that length, so a short message would produce a tag no /// verifier could reproduce -- or less AAD than declared, for the same reason. pub(crate) fn do_encrypt_final(self) -> Result<[u8; TAG_LEN], SymmetricCipherError> { if self.aad_owed != 0 { @@ -613,70 +575,14 @@ where } Ok(self.finish_mac()) } - - /// One-shot generation-encryption with a **detached** tag (Sec 6.1). - /// - /// Writes `plaintext.len()` bytes of ciphertext into `ciphertext` and returns that count with - /// the tag. The entire output buffer is zeroized before the ciphertext is written, so any bytes - /// past that count will be 0. For the spec's own inline `ciphertext || tag` string, use - /// [`Self::encrypt_out`]. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, plus - /// [`Self::new`]'s errors. - pub(crate) fn encrypt_detached_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { - ciphertext.fill(0); - if ciphertext.len() < plaintext.len() { - return Err(SymmetricCipherError::OutputBufferTooSmall(plaintext.len())); - } - let mut ccm = Self::new(key, nonce, aad, plaintext.len())?; - let out = &mut ciphertext[..plaintext.len()]; - out.copy_from_slice(plaintext); - ccm.do_encrypt_inplace(out)?; - let tag = ccm.do_encrypt_final()?; - Ok((plaintext.len(), tag)) - } - - /// One-shot generation-encryption producing the spec's own output string (Sec 6.1 step 8): - /// `C = (P XOR MSB_Plen(S)) || (T XOR MSB_Tlen(S0))`, i.e. `ciphertext || tag` inline. - /// - /// `ciphertext` needs `plaintext.len() + TAG_LEN` bytes; the return is how many were written. - /// The entire output buffer is zeroized before the output is written, so any bytes past that - /// count will be 0. - /// - /// # Errors - /// As [`Self::encrypt_detached_out`]. - pub(crate) fn encrypt_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result { - ciphertext.fill(0); - let needed = plaintext.len() + TAG_LEN; - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (data, tag_out) = ciphertext[..needed].split_at_mut(plaintext.len()); - let (_, tag) = Self::encrypt_detached_out(key, nonce, aad, plaintext, data)?; - tag_out.copy_from_slice(&tag); - Ok(needed) - } } /// 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<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> + Ccm<'k, P, Decrypting, BLOCK_LEN, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Decrypts `data` in place and authenticates the recovered plaintext. /// @@ -711,7 +617,7 @@ where /// # Errors /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, and /// [`SymmetricCipherError::StateError`] if less ciphertext was supplied than the length declared - /// to [`Self::new`], or less AAD than declared. + /// at construction, or less AAD than declared. pub(crate) fn do_decrypt_final(self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError> { if self.aad_owed != 0 { return Err(SymmetricCipherError::StateError( @@ -729,83 +635,12 @@ where Err(SymmetricCipherError::AEADTagCheckFailed) } } - - /// One-shot decryption-verification with a **detached** tag (Sec 6.2). - /// - /// Returns the number of plaintext bytes written. The entire output buffer is zeroized before - /// the plaintext is written, so any bytes past that count will be 0. On failure `plaintext` is zeroized before the error is returned, so Sec 6.2's "the payload P - /// and the MAC T shall not be revealed" holds even for a caller who ignores the `Result`. - /// - /// # Errors - /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, plus - /// [`Self::new`]'s errors. - pub(crate) fn decrypt_detached_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; TAG_LEN], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - if plaintext.len() < ciphertext.len() { - return Err(SymmetricCipherError::OutputBufferTooSmall(ciphertext.len())); - } - let mut ccm = Self::new(key, nonce, aad, ciphertext.len())?; - let out = &mut plaintext[..ciphertext.len()]; - out.copy_from_slice(ciphertext); - ccm.do_decrypt_inplace(out)?; - match ccm.do_decrypt_final(tag) { - Ok(()) => Ok(ciphertext.len()), - Err(e) => { - // Sec 6.2: on INVALID the payload "shall not be revealed". A plain `fill` because - // this crate is `#![forbid(unsafe_code)]`; the store is to the caller's own buffer, - // which the caller may read after this returns, so it is not a dead store the - // optimizer is entitled to drop. - out.fill(0); - Err(e) - } - } - } - - /// One-shot decryption-verification of the spec's own output string (Sec 6.2), splitting the - /// trailing `TAG_LEN` bytes off `ciphertext` as the tag -- step 6's `LSB_Tlen(C)`. Returns the - /// number of plaintext bytes written. The entire output buffer is zeroized before the plaintext - /// is written, so any bytes past that count will be 0. - /// - /// # Errors - /// [`SymmetricCipherError::DecryptionFailed`] for Sec 6.2 step 1, "If Clen <= Tlen, then - /// return INVALID": a malformed input rather than a failed check, reported with the variant - /// [`SymmetricCipherDecryptor::do_decrypt_final`] specifies for a malformed ciphertext so that - /// every inline entry point -- this one, [`CcmDecryptor::do_decrypt_final`] and - /// [`CcmDecryptor::decrypt_with_aad_out`](AEADCipherDecryptor::decrypt_with_aad_out) -- agrees - /// on the same input. Otherwise as [`Self::decrypt_detached_out`]. - pub(crate) fn decrypt_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - let Some((data, tag)) = ciphertext.split_last_chunk::() else { - return Err(SymmetricCipherError::DecryptionFailed); - }; - Self::decrypt_detached_out(key, nonce, aad, data, tag, plaintext) - } } -impl< - P, - Dir, - const KEY_LEN: usize, - const BLOCK_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, -> Algorithm for CcmPacket +impl<'k, P, Dir, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> Algorithm + for Ccm<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::CCM_ALG_NAME`]. const ALG_NAME: &'static str = P::CCM_ALG_NAME; @@ -817,14 +652,14 @@ where /// over counter blocks formatted as A.3 specifies. /// /// Crate-private: CCM's CTR half alone is an unauthenticated cipher, and is only reachable -/// through `CcmPacket`. It shares its key schedule with the CBC-MAC half, which reaches it through +/// through `Ccm`. It shares its key schedule with the CBC-MAC half, which reaches it through /// [`StreamCipher::keystream`]. #[derive(Clone)] -struct CcmKeyStream +struct CcmKeyStream<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k P, // `Ctr_i` with its counter field zeroed (A.3, Table 3): the flags octet and the nonce, which // are the same in every counter block. Public data -- flags and nonce travel in the clear -- // so deliberately not a `Secret`. @@ -834,10 +669,10 @@ where next_ctr: u64, } -impl - CcmKeyStream +impl<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize> + CcmKeyStream<'k, P, BLOCK_LEN, NONCE_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// 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`. @@ -849,7 +684,7 @@ where if Self::Q_LEN >= 8 { u64::MAX } else { (1u64 << (8 * Self::Q_LEN)) - 1 }; /// Formats the counter template and positions the keystream at `S1`. - fn from_perm(perm: P, nonce: &[u8; NONCE_LEN]) -> Self { + fn from_perm(perm: &'k P, nonce: &[u8; NONCE_LEN]) -> Self { // A.3, Tables 3 and 4: `Ctr_i` is `Flags || N || [i]_8q`, and its flags octet has both // reserved bits and bits 3, 4 and 5 zero -- "to ensure that all the counter blocks are // distinct from B0", whose bits 3..5 encode `t` and so cannot all be zero -- leaving bits @@ -862,7 +697,7 @@ where /// The nonce `N`, read back out of the counter template: A.3 Table 3 puts it in octets /// `1 ... 15-q`, which is `1 ... NONCE_LEN` since `n + q = 15`. It is the same `N` that - /// `B0` carries (A.2.1 Table 2), so `CcmPacket::format_header` needs no second copy of it. + /// `B0` carries (A.2.1 Table 2), so `Ccm::format_header` needs no second copy of it. fn nonce(&self) -> [u8; NONCE_LEN] { let mut nonce = [0u8; NONCE_LEN]; nonce.copy_from_slice(&self.ctr_template[1..1 + NONCE_LEN]); @@ -873,8 +708,8 @@ where /// Table 2) and the counter field of `Ctr_i` (A.3, Table 3), which occupy the same octets. /// /// `Q_LEN <= 8`, so the low `Q_LEN` bytes of a big-endian `u64` are exactly `[x]_8q`. Nothing - /// is ever truncated in a way that matters: `CcmPacket::new` refuses a payload above - /// `CcmPacket::MAX_PAYLOAD_LEN`, and the counter cannot pass that either, since there is one + /// is ever truncated in a way that matters: `Ccm`'s constructors refuse a payload above + /// `Ccm::MAX_PAYLOAD_LEN`, and the counter cannot pass that either, since there is one /// counter block per `BLOCK_LEN` payload bytes. #[inline] fn put_q_field(block: &mut [u8; BLOCK_LEN], x: u64) { @@ -893,29 +728,22 @@ where } } -impl Algorithm - for CcmKeyStream +impl<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize> Algorithm + for CcmKeyStream<'k, P, BLOCK_LEN, NONCE_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - KeyStream for CcmKeyStream +impl<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize> KeyStream + for CcmKeyStream<'k, P, BLOCK_LEN, NONCE_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - fn new( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { - Ok(Self::from_perm(P::new(key)?, nonce)) - } - /// Every counter value from `next_ctr` to `2^8q - 1`. Never the binding limit in practice: - /// `CcmPacket::new` caps the payload at `2^8q - 1` bytes, far fewer than that many blocks. + /// `Ccm`'s constructors cap the payload at `2^8q - 1` bytes, far fewer than that many blocks. fn remaining_blocks(&self) -> u64 { Self::MAX_COUNTER - self.next_ctr + 1 } @@ -925,7 +753,7 @@ where fn apply_blocks(&mut self, blocks: &mut [[u8; BLOCK_LEN]]) { let template = &self.ctr_template; apply_counter_blocks( - &self.perm, + self.perm, &mut self.next_ctr, |j| Self::counter_block(template, j), blocks, @@ -933,7 +761,7 @@ where } } -/// What [`CcmEncryptor`] and [`CcmDecryptor`] share: the `CcmPacket` state, which the trait +/// What [`CcmPacketEncryptor`] and [`CcmPacketDecryptor`] share: the `Ccm` state, which the trait /// constructor builds before it has seen any AAD, and the AAD itself, held back until it is /// complete. /// @@ -944,22 +772,22 @@ where /// buffer in either adapter, and `AAD_LEN` is its capacity: header-sized, by the caller's choice. /// /// `DATA_LEN` is an exact length, not a capacity, and is committed to `B0` here: the payload -/// then streams through `CcmPacket`'s own `owed` accounting, which refuses more and whose finals +/// then streams through `Ccm`'s own `owed` accounting, which refuses more and whose finals /// refuse less. #[derive(Clone)] struct CcmAdapter< + 'k, P, Dir, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - ccm: CcmPacket, + ccm: Ccm<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN>, // 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], @@ -969,33 +797,32 @@ struct CcmAdapter< } impl< + 'k, P, Dir, - 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<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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 + /// The compile-time check for the trait adapters, run from both [`CcmPacketEncryptor`]'s and + /// [`CcmPacketDecryptor`]'s constructors and from their one-shots, which never build an adapter, so /// that a parameter set either names a usable pair or does not compile at all. - /// `CcmPacket::check_shape`'s own checks run as well, from the `CcmPacket` constructor underneath, - /// and the floor for a generated nonce is `CcmEncryptor::check_random_nonce`'s, run only from + /// `Ccm::check_shape`'s own checks run as well, from the `Ccm` constructor underneath, + /// and the floor for a generated nonce is `CcmPacketEncryptor::check_random_nonce`'s, run only from /// the forms that draw one: every A.1 nonce length from 7 through 13 is usable here under a /// supplied nonce. #[inline] fn check_shape() { const { // `B0` could not carry it (A.1's `p < 2^8q`), and this is the one place the length is - // known at compile time, so it is a compile error rather than `CcmPacket::new`'s `Err`. + // known at compile time, so it is a compile error rather than the constructor's `Err`. assert!( - DATA_LEN as u64 - <= CcmPacket::::MAX_PAYLOAD_LEN, + DATA_LEN as u64 <= Ccm::::MAX_PAYLOAD_LEN, "CCM: DATA_LEN exceeds the payload limit 2^8q - 1 that NONCE_LEN implies (A.1)" ); } @@ -1003,10 +830,10 @@ where /// Readies the keystream and commits `DATA_LEN` as the payload length; the CBC-MAC absorbs /// nothing until [`Self::begin_data`]. - fn new(perm: P, nonce: &[u8; NONCE_LEN]) -> Self { + fn new(perm: &'k P, nonce: &[u8; NONCE_LEN]) -> Self { Self::check_shape(); Self { - ccm: CcmPacket::from_perm_unformatted(perm, nonce, DATA_LEN), + ccm: Ccm::from_perm_unformatted(perm, nonce, DATA_LEN), aad: [0u8; AAD_LEN], aad_len: 0, formatted: false, @@ -1044,7 +871,7 @@ where /// Ends the AAD phase, the first time it is called: runs Sec 6.1 step 1 -- `B0`, the encoding /// of `a`, the AAD and its zero pad -- over the AAD held so far, which is now known to be all - /// of it. Called before any payload byte goes through `CcmPacket`, and from every final, so a + /// of it. Called before any payload byte goes through `Ccm`, and from every final, so a /// message with no payload at all still gets its header. Later calls do nothing. fn begin_data(&mut self) { if !self.formatted { @@ -1056,10 +883,10 @@ where } } -/// Adapts `CcmPacket` to [`AEADCipherEncryptor`] and, through it, [`SymmetricCipherEncryptor`], for a +/// Adapts `Ccm` to [`AEADCipherEncryptor`] and, through it, [`SymmetricCipherEncryptor`], for a /// payload of exactly `DATA_LEN` bytes. /// -/// [`SymmetricCipherEncryptor::do_encrypt_init`] is handed a key and nothing else, but CCM cannot +/// An AEAD key's `create_encryptor` is handed a nonce and nothing else, but CCM cannot /// form `B0` -- and so cannot authenticate anything -- until it knows the payload length /// (Appendix A.2.1; see the module docs). Rather than hold the message until the final call /// reveals that length, this type fixes the payload length as the const parameter `DATA_LEN`. @@ -1069,10 +896,9 @@ where /// `FINAL_LEN` is `TAG_LEN`, exactly as for GCM. The price is that they accept exactly /// `DATA_LEN` bytes of payload -- the fixed frame of SP 800-38C Sec 3's "packet environment" -- /// and refuse any other amount, more at the update that would exceed it and less at the final. -/// The one-shots are not bound by either parameter: with the whole message in hand there is -/// nothing to declare up front and nothing to hold back, so they are `CcmPacket`'s own one-shots -/// under a generated nonce, for a payload and AAD of any length, and the AAD buffer is never -/// built. `DATA_LEN` and `AAD_LEN` govern the streaming methods only. +/// The one-shots a key bound to [`CCM_Packet`] provides are written over this worker, so they +/// carry the same frame: a payload of exactly `DATA_LEN` bytes and up to `AAD_LEN` of AAD. A +/// message of another length goes through [`CCM`], whose constructors take the lengths. /// /// `AAD_LEN` is a capacity, not an exact length: the trait's AAD is optional and open-ended, and /// the inherited [`SymmetricCipherEncryptor`] methods are this AEAD with none at all. Up to @@ -1082,20 +908,22 @@ where /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadDecryptMode, AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; /// use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor}; -/// use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor}; +/// use bouncycastle_cipher::modes::CCM_Packet; +/// use bouncycastle_rng::HashDRBG_SHA512; /// -/// // Frames of exactly 40 payload bytes, with up to 16 bytes of header. -/// type Enc = CcmEncryptor; -/// type Dec = CcmDecryptor; +/// // Frames of exactly 40 payload bytes, with up to 16 bytes of header, as a key is bound to them. +/// type Frame = CCM_Packet<12, 16, 16, 40>; /// /// let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .expect("a 16-byte symmetric cipher key"); +/// let state = >::key_state(ToyBlockCipher::new(&key).unwrap()); /// let header = b"frame 7"; /// let frame = [0x5Au8; 40]; /// -/// let (mut enc, nonce) = Enc::do_encrypt_init(&key).expect("init"); +/// let (mut enc, nonce) = Frame::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()).expect("nonce draw"); /// enc.do_update_aad(header).expect("within AAD_LEN"); /// let mut ct = [0u8; 40]; /// // Every byte comes straight out; the chunking is the caller's business. @@ -1106,7 +934,7 @@ where /// assert_eq!(written, 40); /// let (_, _, tag) = enc.do_encrypt_final_detached().expect("exactly DATA_LEN was supplied"); /// -/// let mut dec = Dec::do_decrypt_init(&key, &nonce).expect("init"); +/// let mut dec = Frame::decrypt_init(&state, &nonce); /// dec.do_update_aad(header).expect("within AAD_LEN"); /// let mut pt = [0u8; 40]; /// dec.do_decrypt_out(&ct, &mut pt).expect("released, but not yet authenticated"); @@ -1114,7 +942,7 @@ where /// assert_eq!(pt, frame); /// /// // 39 bytes is not a frame: the final refuses rather than authenticate a length `B0` did not commit to. -/// let (mut short, _) = Enc::do_encrypt_init(&key).expect("init"); +/// let mut short = Frame::encrypt_init_nonce(&state, &nonce); /// short.do_encrypt_out(&frame[..39], &mut ct).expect("within DATA_LEN"); /// assert!(short.do_encrypt_final().is_err()); /// ``` @@ -1123,26 +951,30 @@ where /// /// The forms that generate a nonce need `NONCE_LEN` of at least 12, to keep the random-collision /// bound useful; the check runs only when one of them is instantiated. The forms that take the -/// caller's nonce -- [`do_encrypt_init_nonce`](AEADCipherEncryptor::do_encrypt_init_nonce) and -/// the `_nonce` one-shots -- and [`CcmDecryptor`] accept every A.1 nonce length from 7 through -/// 13, as the inherent `CcmPacket` API does, with uniqueness the caller's to guarantee. So a short -/// nonce compiles when it is supplied: +/// caller's nonce -- the `_nonce` constructor and one-shots of a key bound to [`CCM_Packet`] -- and +/// [`CcmPacketDecryptor`] accept every A.1 nonce length from 7 through 13, as the inherent `Ccm` +/// API does, with uniqueness the caller's to guarantee. So a short nonce compiles when it is +/// supplied: /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::AEADCipherEncryptor; -/// use bouncycastle_cipher::modes::CcmEncryptor; +/// use bouncycastle_core::traits::{AEADCipherEncryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_cipher::modes::CCM_Packet; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); /// // n = 8 is permitted by A.1; the caller's counter supplies it. /// let counter = 7u64.to_be_bytes(); +/// let mut enc = CCM_Packet::<8, 16, 64, 256>::encrypt_init_nonce(&state, &counter); +/// enc.do_update_aad(b"hdr").unwrap(); /// let mut sealed = [0u8; 256 + 16]; -/// let n = CcmEncryptor::::encrypt_with_aad_nonce_out( -/// &key, &counter, b"hdr", &[0x5A; 256], &mut sealed, -/// ).unwrap(); -/// assert_eq!(n, 256 + 16); +/// let n = enc.do_encrypt_out(&[0x5A; 256], &mut sealed).unwrap(); +/// let (tag, tag_len) = enc.do_encrypt_final().unwrap(); +/// sealed[n..].copy_from_slice(&tag[..tag_len]); +/// assert_eq!(n + tag_len, 256 + 16); /// ``` /// /// ...and not when it would be drawn: @@ -1150,13 +982,15 @@ where /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; -/// use bouncycastle_cipher::modes::CcmEncryptor; +/// use bouncycastle_cipher::modes::CCM_Packet; +/// use bouncycastle_rng::HashDRBG_SHA512; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); /// // n = 8 is permitted by A.1, but too short for a random draw. -/// let _ = CcmEncryptor::::do_encrypt_init(&key); +/// let _ = CCM_Packet::<8, 16, 64, 256>::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()); /// ``` /// /// Nor does a `DATA_LEN` that `B0` could not carry under this `NONCE_LEN` (A.1's `p < 2^8q`): @@ -1164,18 +998,20 @@ where /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; -/// use bouncycastle_cipher::modes::CcmEncryptor; +/// use bouncycastle_cipher::modes::CCM_Packet; +/// use bouncycastle_rng::HashDRBG_SHA512; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); /// // n = 13 leaves q = 2, so the payload is at most 65535 bytes. -/// let _ = CcmEncryptor::::do_encrypt_init(&key); +/// let _ = CCM_Packet::<13, 16, 64, 65536>::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()); /// ``` /// /// # Memory /// -/// A value is the fixed-size `CcmPacket` state, the `AAD_LEN`-byte AAD buffer and two words of +/// A value is the fixed-size `Ccm` state, the `AAD_LEN`-byte AAD buffer and two words of /// bookkeeping, independent of `DATA_LEN`; the finals return and write only the tag. Nothing /// scales with the message. /// @@ -1184,14 +1020,15 @@ where /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::AEADCipherEncryptor; -/// use bouncycastle_cipher::modes::CcmEncryptor; +/// use bouncycastle_cipher::modes::CCM_Packet; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .unwrap(); +/// let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); /// // n = 6 is not in {7, ..., 13}: it would make q = 9, which A.1 does not allow. -/// let _ = CcmEncryptor::::do_encrypt_init_nonce(&key, &[0u8; 6]); +/// let _ = CCM_Packet::<6, 16, 64, 256>::encrypt_init_nonce(&state, &[0u8; 6]); /// ``` /// /// Nor does an odd tag length: @@ -1199,55 +1036,56 @@ where /// ```compile_fail /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{AeadEncryptMode, ElectronicCodeBook, Mode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::AEADCipherEncryptor; -/// use bouncycastle_cipher::modes::CcmEncryptor; +/// use bouncycastle_cipher::modes::CCM_Packet; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .unwrap(); +/// let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); /// // t = 15 is not in {4, 6, 8, 10, 12, 14, 16}. -/// let _ = CcmEncryptor::::do_encrypt_init_nonce(&key, &[0u8; 12]); +/// let _ = CCM_Packet::<12, 15, 64, 256>::encrypt_init_nonce(&state, &[0u8; 12]); /// ``` #[derive(Clone)] -pub struct CcmEncryptor< +pub struct CcmPacketEncryptor< + 'k, P, - 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<'k, P, Encrypting, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>) where - P: ElectronicCodeBook + ModeNames; + P: BlockPermutation + ModeNames; impl< + 'k, P, - 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 CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } impl< + 'k, P, - 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 +> CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// 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. @@ -1255,20 +1093,19 @@ where /// # Errors /// [`SymmetricCipherError::StateError`] if fewer than `DATA_LEN` payload bytes were supplied: /// `B0` committed to `DATA_LEN`, so a shorter message would get a tag no verifier could - /// reproduce, and `CcmPacket::do_encrypt_final` refuses to produce one. + /// reproduce, and `Ccm::do_encrypt_final` refuses to produce one. fn finish(mut self) -> Result<[u8; TAG_LEN], SymmetricCipherError> { self.0.begin_data(); self.0.ccm.do_encrypt_final() } /// Begins an encryption from a key schedule that has already been expanded, under a nonce - /// drawn from `rng`: the constructor a mode-bound key's `encrypt_init` uses. The result is - /// identical to [`SymmetricCipherEncryptor::do_encrypt_init_rng`] from the same key and RNG. + /// drawn from `rng`: the constructor a mode-bound key's `encrypt_init` uses. /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - pub fn from_perm( - perm: P, + pub(crate) fn from_perm( + perm: &'k P, rng: &mut dyn RNG, ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { Self::check_random_nonce(); @@ -1278,7 +1115,7 @@ where /// As [`Self::from_perm`] under a nonce the caller supplies, at any A.1 length: the /// constructor a mode-bound key's `encrypt_init_nonce` uses. - pub fn from_perm_nonce(perm: P, nonce: &[u8; NONCE_LEN]) -> Self { + pub(crate) fn from_perm_nonce(perm: &'k P, nonce: &[u8; NONCE_LEN]) -> Self { Self(CcmAdapter::new(perm, nonce)) } @@ -1292,43 +1129,25 @@ where const { assert!( NONCE_LEN >= 12, - "CCM: a generated nonce needs NONCE_LEN >= 12; supply the nonce (do_encrypt_init_nonce, the _nonce one-shots, or CcmPacket) for a shorter one" + "CCM: a generated nonce needs NONCE_LEN >= 12; supply the nonce (do_encrypt_init_nonce, the _nonce one-shots, or Ccm) for a shorter one" ); } } } impl< + 'k, P, - 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 CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { - // `P::new`'s own checks are the only key validation needed, exactly as for `CcmPacket` itself - // 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. - Self::from_perm(P::new(key)?, rng) - } - /// The identity: nothing is held back, since `B0` is already committed to `DATA_LEN`. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -1358,7 +1177,7 @@ where // Before the length check, so that a refused oversized call still closes the AAD phase: // the phase order is about call history, and this call happened. self.0.begin_data(); - // `CcmPacket::do_encrypt_inplace` would refuse this too, but only after the plaintext had been copied + // `Ccm::do_encrypt_inplace` would refuse this too, but only after the plaintext had been copied // into the caller's output buffer, and a refused call must not leave plaintext there. if plaintext.len() > self.0.ccm.owed { return Err(SymmetricCipherError::StateError( @@ -1383,50 +1202,22 @@ where fn encrypt_out_len(plaintext_len: usize) -> usize { plaintext_len + TAG_LEN } - - /// The AEAD one-shot with no AAD; see - /// [`encrypt_with_aad_out`](AEADCipherEncryptor::encrypt_with_aad_out). - fn encrypt_out( - key: &KeyMaterial, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - >::encrypt_with_aad_out( - key, - &[], - plaintext, - ciphertext, - ) - } - - /// 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, - rng: &mut dyn RNG, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - >::encrypt_with_aad_rng_out( - key, rng, &[], plaintext, ciphertext, - ) - } } /// The AEAD view, with `FINAL_LEN = TAG_LEN`: the encryptor holds nothing back, so the detached /// final flushes nothing and returns only the tag. impl< + 'k, P, - 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 CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Holds back `aad` until the payload begins; see `CcmAdapter`. /// @@ -1438,53 +1229,6 @@ where self.0.do_update_aad(aad) } - /// [`do_encrypt_init_rng`](SymmetricCipherEncryptor::do_encrypt_init_rng) with the caller's - /// 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, - nonce: &[u8; NONCE_LEN], - ) -> Result { - let perm = P::new(key)?; - Ok(Self(CcmAdapter::new(perm, nonce))) - } - - /// `CcmPacket::encrypt_detached_out` itself, for a `plaintext` and `aad` of any length. - /// - /// # Errors - /// As [`encrypt_detached_rng_out`](AEADCipherEncryptor::encrypt_detached_rng_out), less the - /// RNG's. - fn encrypt_detached_nonce_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { - CcmAdapter::::check_shape(); - CcmPacket::::encrypt_detached_out( - key, nonce, aad, plaintext, ciphertext, - ) - } - - /// `CcmPacket::encrypt_out` itself, for a `plaintext` and `aad` of any length. - /// - /// # Errors - /// As [`encrypt_with_aad_rng_out`](AEADCipherEncryptor::encrypt_with_aad_rng_out), less the - /// RNG's. - fn encrypt_with_aad_nonce_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result { - CcmAdapter::::check_shape(); - CcmPacket::::encrypt_out( - key, nonce, aad, plaintext, ciphertext, - ) - } - /// Sec 6.1 steps 4 and 8: the tag. Nothing is held back, so `ciphertext` is left zeroed. /// /// # Errors @@ -1496,91 +1240,10 @@ where ciphertext.fill(0); Ok((0, self.finish()?)) } - - /// `CcmPacket::encrypt_detached_out` under a nonce drawn from the default DRBG, as - /// [`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, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError> { - let mut rng = HashDRBG_SHA512::new_from_os(); - Self::encrypt_detached_rng_out(key, &mut rng, aad, plaintext, ciphertext) - } - - /// `CcmPacket::encrypt_detached_out` under a nonce drawn from `rng`, for a `plaintext` and `aad` - /// of any length: neither `DATA_LEN` nor `AAD_LEN` applies to a one-shot (see the type docs). - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is shorter than - /// `plaintext`, checked before anything is drawn from `rng`; plus `CcmPacket::new`'s errors and - /// the RNG's. - fn encrypt_detached_rng_out( - key: &KeyMaterial, - rng: &mut dyn RNG, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError> { - ciphertext.fill(0); - if ciphertext.len() < plaintext.len() { - return Err(SymmetricCipherError::OutputBufferTooSmall(plaintext.len())); - } - Self::check_random_nonce(); - CcmAdapter::::check_shape(); - let nonce = random_iv::(rng)?; - let (written, tag) = - CcmPacket::::encrypt_detached_out( - key, &nonce, aad, plaintext, ciphertext, - )?; - Ok((nonce, written, tag)) - } - - /// `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, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - let mut rng = HashDRBG_SHA512::new_from_os(); - Self::encrypt_with_aad_rng_out(key, &mut rng, aad, plaintext, ciphertext) - } - - /// `CcmPacket::encrypt_out` under a nonce drawn from `rng`, for a `plaintext` and `aad` of any - /// length, as [`encrypt_detached_rng_out`](AEADCipherEncryptor::encrypt_detached_rng_out). - /// - /// # Errors - /// 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, - rng: &mut dyn RNG, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - Self::check_random_nonce(); - CcmAdapter::::check_shape(); - let nonce = random_iv::(rng)?; - let written = - CcmPacket::::encrypt_out( - key, &nonce, aad, plaintext, ciphertext, - )?; - Ok((nonce, written)) - } } -/// Adapts `CcmPacket` to [`AEADCipherDecryptor`] and, through it, [`SymmetricCipherDecryptor`], for -/// a payload of exactly `DATA_LEN` bytes; the mirror of [`CcmEncryptor`], and see it for the +/// Adapts `Ccm` to [`AEADCipherDecryptor`] and, through it, [`SymmetricCipherDecryptor`], for +/// a payload of exactly `DATA_LEN` bytes; the mirror of [`CcmPacketEncryptor`], and see it for the /// parameters, the nonce floor and the memory. /// /// Knowing the payload length has a consequence no other decryptor in this crate enjoys: there is @@ -1588,8 +1251,8 @@ where /// ciphertext and are decrypted and released by the call that brings them; anything after them /// can only be an inline tag (Sec 6.2 step 6's `LSB_Tlen(C)`), and only those bytes -- at most /// `TAG_LEN` -- are held back for the final to check. A `C` of any other length than `DATA_LEN` -/// (detached) or `DATA_LEN + TAG_LEN` (inline) is refused as malformed. The one-shots are -/// `CcmPacket`'s own and take a `C` of any length; see [`CcmEncryptor`]. +/// (detached) or `DATA_LEN + TAG_LEN` (inline) is refused as malformed, by the one-shots of a key +/// bound to [`CCM_Packet`] as much as by the streaming methods; see [`CcmPacketEncryptor`]. /// /// # 🚨 Security Considerations 🚨 /// @@ -1598,22 +1261,22 @@ where /// recovered rather than hold the whole frame back, so a forged ciphertext yields attacker-chosen /// bytes that only the final's [`SymmetricCipherError::AEADTagCheckFailed`] disowns. That is /// [`AEADCipherDecryptor`]'s general streaming caveat, and the inherent -/// `CcmPacket::do_decrypt_inplace` has it too. Sec 6.2's "the payload P and the MAC T shall not be +/// `Ccm::do_decrypt_inplace` has it too. Sec 6.2's "the payload P and the MAC T shall not be /// revealed" on INVALID is honoured by the one-shots, which zeroize what they wrote before /// returning the error. #[derive(Clone)] -pub struct CcmDecryptor< +pub struct CcmPacketDecryptor< + 'k, P, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, > where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - inner: CcmAdapter, + inner: CcmAdapter<'k, P, Decrypting, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>, // 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. @@ -1622,48 +1285,48 @@ pub struct CcmDecryptor< } impl< + 'k, P, - 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 CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const ALG_NAME: &'static str = P::CCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } impl< + 'k, P, - 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 +> CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Begins a decryption from a key schedule that has already been expanded: the constructor a /// mode-bound key's `decrypt_init` uses. Infallible, as [`AeadDecryptMode::decrypt_init`] is. /// /// [`AeadDecryptMode::decrypt_init`]: bouncycastle_core::hazmat::AeadDecryptMode::decrypt_init - pub fn from_perm(perm: P, nonce: &[u8; NONCE_LEN]) -> Self { + pub(crate) fn from_perm(perm: &'k P, nonce: &[u8; NONCE_LEN]) -> Self { Self { inner: CcmAdapter::new(perm, nonce), tag: [0u8; TAG_LEN], tag_len: 0 } } /// 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 + /// through `Ccm::do_decrypt_final`. The header goes in first if no payload call put it /// there, which is the `DATA_LEN = 0` message. /// /// # Errors /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify. The callers have - /// already refused a short payload, so `CcmPacket::do_decrypt_final`'s own refusal of one is not + /// already refused a short payload, so `Ccm::do_decrypt_final`'s own refusal of one is not /// reachable from here; it stays as the backstop it is for the inherent API. fn finish(mut self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError> { self.inner.begin_data(); @@ -1672,26 +1335,18 @@ where } impl< + 'k, P, - 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 CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - fn do_decrypt_init( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result { - // `P::new`'s own checks are the only key validation needed; see the encryptor. - Ok(Self::from_perm(P::new(key)?, nonce)) - } - /// Every byte of `input_len` that is still inside the declared payload; the rest can only be /// the tag, and is held back. fn do_decrypt_out_len(&self, input_len: usize) -> usize { @@ -1718,7 +1373,7 @@ where if ciphertext.is_empty() { return Ok(0); } - let release = self.do_decrypt_out_len(ciphertext.len()); + let release = SymmetricCipherDecryptor::do_decrypt_out_len(self, ciphertext.len()); if plaintext.len() < release { return Err(SymmetricCipherError::OutputBufferTooSmall(release)); } @@ -1732,7 +1387,7 @@ where declares plus an inline tag", )); } - // `release` is within what is owed, so `CcmPacket` cannot refuse it. + // `release` is within what is owed, so `Ccm` cannot refuse it. plaintext[..release].copy_from_slice(data); self.inner.ccm.do_decrypt_inplace(&mut plaintext[..release])?; self.tag[self.tag_len..self.tag_len + tail.len()].copy_from_slice(tail); @@ -1759,46 +1414,29 @@ where Ok(([0u8; TAG_LEN], 0)) } - /// `C` less its inline tag, as for any AEAD: the one-shots take a `C` of any length. The - /// streaming side's own bound is [`do_decrypt_out_len`](Self::do_decrypt_out_len). + /// `C` less its inline tag, as for any AEAD; the frame's own bound is + /// [`do_decrypt_out_len`](Self::do_decrypt_out_len). fn decrypt_out_len(ciphertext_len: usize) -> usize { ciphertext_len.saturating_sub(TAG_LEN) } - - /// The AEAD one-shot with no AAD; see - /// [`decrypt_with_aad_out`](AEADCipherDecryptor::decrypt_with_aad_out). - fn decrypt_out( - key: &KeyMaterial, - init_data: &[u8; NONCE_LEN], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - >::decrypt_with_aad_out( - key, - init_data, - &[], - ciphertext, - plaintext, - ) - } } -/// The AEAD view, with `FINAL_LEN = TAG_LEN`. The one-shots are `CcmPacket`'s own, for a `C` and -/// AAD of any length; see [`CcmEncryptor`]. +/// The AEAD view, with `FINAL_LEN = TAG_LEN`; the one-shots over it carry the frame, see +/// [`CcmPacketEncryptor`]. impl< + 'k, P, - 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 CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// As [`CcmEncryptor::do_update_aad`](AEADCipherEncryptor::do_update_aad); the concatenation + /// As [`CcmPacketEncryptor::do_update_aad`](AEADCipherEncryptor::do_update_aad); the concatenation /// must match the encryptor's byte for byte or the tag check fails. fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.inner.do_update_aad(aad) @@ -1824,75 +1462,18 @@ where self.finish(tag)?; Ok(0) } - - /// `CcmPacket::decrypt_detached_out`, for a `ciphertext` and `aad` of any length: neither - /// `DATA_LEN` nor `AAD_LEN` applies to a one-shot (see [`CcmEncryptor`]). Zeroizes - /// `plaintext` on a failed tag, as `CcmPacket`'s does. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is shorter than - /// `ciphertext`; [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify; - /// plus `CcmPacket::new`'s errors. - fn decrypt_detached_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; TAG_LEN], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - if plaintext.len() < ciphertext.len() { - return Err(SymmetricCipherError::OutputBufferTooSmall(ciphertext.len())); - } - CcmAdapter::::check_shape(); - CcmPacket::::decrypt_detached_out( - key, nonce, aad, ciphertext, tag, plaintext, - ) - } - - /// `CcmPacket::decrypt_out`, for a `ciphertext` and `aad` of any length, as - /// [`decrypt_detached_out`](AEADCipherDecryptor::decrypt_detached_out). - /// - /// # Errors - /// As [`decrypt_detached_out`](AEADCipherDecryptor::decrypt_detached_out), with `plaintext` - /// 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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - let needed = Self::decrypt_out_len(ciphertext.len()); - if plaintext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - CcmAdapter::::check_shape(); - CcmPacket::::decrypt_out( - key, nonce, aad, ciphertext, plaintext, - ) - } } /// The suspended state is the CTR half, the CBC-MAC chaining value, how much of its current /// block has gone in, and how much AAD and payload are still owed. The chaining value is /// key-dependent MAC state, which is why the state must be protected; see [`bouncycastle_utils::suspendable_state`]. -impl< - P, - Dir, - const KEY_LEN: usize, - const BLOCK_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, -> SuspendableComponent for CcmPacket +impl<'k, P, Dir, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> + SuspendableComponent<'k> for Ccm<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = Self::CTR_STATE_LEN + BLOCK_LEN + 8 + 8 + 8; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { let (ctr, rest) = out.split_at_mut(Self::CTR_STATE_LEN); @@ -1905,7 +1486,7 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Self::check_shape(); let (ctr, rest) = state.split_at(Self::CTR_STATE_LEN); let ctr = StreamCipher::read_state(ctr, key)?; @@ -1926,15 +1507,15 @@ 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 +/// the key's, re-supplied by reference. Crate-private like the type, reachable only through +/// `Ccm`'s state. +impl<'k, P, const BLOCK_LEN: usize, const NONCE_LEN: usize> SuspendableComponent<'k> + for CcmKeyStream<'k, P, BLOCK_LEN, NONCE_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = BLOCK_LEN + 8; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); @@ -1943,8 +1524,8 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + let perm = key; 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 @@ -1961,290 +1542,15 @@ where } } -#[cfg(test)] -mod tests { - //! Tests for the private formatting helpers, which are what a reviewer with SP 800-38C open - //! most needs to check and which no public API exposes directly. - //! - //! The expected values are the `B` and `Ctr_i` strings printed in the spec's own Appendix C - //! examples, transcribed from the errata-updated PDF. Appendix C gives the formatted block - //! string for each example, so these pin the flags octet, the placement of `N` and `Q`, and - //! the AAD length encoding against the document rather than against this implementation. - - use super::*; - use bouncycastle_core::key_material::KeyType; - - /// 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 - /// tests about the formatting function rather than about AES. - struct Identity; - - impl Algorithm for Identity { - const ALG_NAME: &'static str = "identity"; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; - } - - impl ModeNames for Identity { - const CBC_ALG_NAME: &'static str = "identity_CBC"; - const CCM_ALG_NAME: &'static str = "identity_CCM"; - const CFB_ALG_NAME: &'static str = "identity_CFB"; - const CFB8_ALG_NAME: &'static str = "identity_CFB8"; - const CMAC_ALG_NAME: &'static str = "identity_CMAC"; - const CTR_ALG_NAME: &'static str = "identity_CTR"; - const ECB_ALG_NAME: &'static str = "identity_ECB"; - const GCM_ALG_NAME: &'static str = "identity_GCM"; - const GMAC_ALG_NAME: &'static str = "identity_GMAC"; - } - - impl ElectronicCodeBook<16, 16> for Identity { - fn new(_key: &KeyMaterial<16>) -> Result { - Ok(Identity) - } - fn encrypt_block(&self, _block: &mut [u8; 16]) {} - fn decrypt_block(&self, _block: &mut [u8; 16]) {} - fn encrypt_2blocks(&self, _blocks: &mut [[u8; 16]; 2]) {} - fn decrypt_2blocks(&self, _blocks: &mut [[u8; 16]; 2]) {} - fn encrypt_4blocks(&self, _blocks: &mut [[u8; 16]; 4]) {} - 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, - ) - .expect("Appendix C's 128-bit key") - } - - /// Appendix C.1: `Tlen=32, Nlen=56, Alen=64, Plen=32`, so `t = 4`, `n = 7`, `q = 8`. - /// - /// The spec prints `B` as - /// `4f101112 13141516 00000000 00000004 | 00080001 02030405 06070000 00000000 | ...`, - /// so `B0` is `4f` then the 7-byte nonce then `[4]_64`, and `B1` is `[8]_16` then the 8-byte - /// AAD then six zero bytes of pad. - /// - /// C.1's AAD is 8 bytes, so its Adata bit is set. - #[test] - fn c1_b0_matches_the_spec() { - let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - assert_eq!( - 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" - ); - } - - /// A.2.2: the Adata bit is "'0' if a=0 and '1' if a>0", and it is bit 6 -- so clearing it must - /// take C.1's `0x4f` to `0x0f` and change nothing else in the block. - #[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); - 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"); - } - - /// The constructor really does absorb the `B0` that [`CcmPacket::format_b0`] built. With the identity - /// permutation the CBC-MAC chaining value after one block is that block itself, so a - /// no-AAD, no-payload construction leaves `B0` sitting in `y`. - /// - /// Without this, `format_b0` could be correct and unused. - #[test] - fn the_constructor_absorbs_b0() { - let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let ccm = - CcmPacket::::new(&key(), &nonce, &[], 4).unwrap(); - assert_eq!( - *ccm.y, - CcmPacket::::format_b0(&nonce, false, 4) - ); - assert_eq!(ccm.mac_pos, 0, "a whole block was absorbed, so nothing is part-filled"); - } - - /// Appendix C.4: `Tlen=112, Nlen=104, Plen=256`, so `t = 14`, `n = 13`, `q = 2`; the spec - /// prints `B0` as `71101112 13141516 1718191a 1b1c0020`. - /// - /// This is the other end of the `q` range from C.1, so between them the two tests pin the - /// `[q-1]_3` encoding and the fact that `Q` is `q` octets wide, not a fixed width. - #[test] - 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), - [ - 0x71, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x00, 0x20 - ], - "C.4 B0: flags 0x71 = Adata 1 | [(14-2)/2]_3 = 110 | [2-1]_3 = 001, then Q = [32]_16" - ); - } - - /// Appendix C.1 prints `Ctr0` as `07101112 13141516 00000000 00000000` and `Ctr1` as the same - /// with a trailing `01`; C.4's are `01101112 ... 1b1c0000` and `... 1b1c0001`. - /// - /// Table 4 makes the counter flags `[q-1]_3` alone, with every other bit zero -- which is what - /// keeps them distinct from `B0`, whose `t` field cannot be zero. - #[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); - // `Ctr0` is the template with a zero counter field. - 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], - "C.1 Ctr0" - ); - // The first payload keystream block is `S1`, so the first block the keystream XORs in must - // be `Ctr1`: under the identity permutation, XORed into zeros, that is `Ctr1` itself. - let mut s1 = [[0u8; 16]]; - ks.apply_blocks(&mut s1); - let mut ctr1 = ctr0; - ctr1[15] = 1; - assert_eq!(s1[0], ctr1, "C.1 Ctr1 (the identity permutation leaves S1 = Ctr1)"); - - let nonce_c4 = - [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; - let ks4 = CcmKeyStream::::from_perm(Identity, &nonce_c4); - assert_eq!( - CcmKeyStream::::counter_block(&ks4.ctr_template, 0), - [ - 0x01, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x00, 0x00 - ], - "C.4 Ctr0" - ); - } - - /// A fresh keystream has every counter value but `Ctr0` left: step 7's `S1 || S2 || ...` runs - /// from `j = 1` to the largest `q`-octet counter, `2^8q - 1`, and `S0` is the tag mask. Pinned - /// absolutely because the limit can never bind through the public API -- A.1 caps the payload - /// at `2^8q - 1` bytes, far fewer than that many blocks -- so nothing else would notice an - /// off-by-one here. - #[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]); - 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]); - assert_eq!(ks.remaining_blocks(), u64::MAX); - } - - /// CCM's keystream against the shared [`KeyStream`] conformance suite. A unit test rather than - /// an integration test because `CcmKeyStream` is crate-private. Over the keyed toy rather than - /// 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>(); - } - - /// A.2.2's three AAD length encodings, at and around both boundaries. - /// - /// Two of these values come from the spec itself: C.1's `a = 8` is printed as `0008`, and - /// C.4's `a = 65536` (`Alen = 524288` bits) is printed as - /// `11111111 11111110 00000000 00000001 00000000 00000000`, i.e. `ff fe 00 01 00 00`. - /// - /// The rest pin the boundaries, which is the part no end-to-end test can reach: the first is - /// `2^16 - 2^8` = 65280 rather than the obvious-but-wrong `2^16`, and the second is `2^32`, - /// which through the public API would need a 4 GiB AAD. - #[test] - fn aad_length_encoding_matches_a_2_2() { - type Mode = CcmPacket; - - // Case 1: 0 < a < 2^16 - 2^8, two octets, `[a]_16`. - assert_eq!( - Mode::encode_aad_len(8), - ([0x00, 0x08, 0, 0, 0, 0, 0, 0, 0, 0], 2), - "C.1's a = 8" - ); - assert_eq!(Mode::encode_aad_len(1).1, 2); - // 65279 = 2^16 - 2^8 - 1 is the largest value still in the first case. - assert_eq!( - Mode::encode_aad_len(65279), - ([0xfe, 0xff, 0, 0, 0, 0, 0, 0, 0, 0], 2), - "65279 is still [a]_16" - ); - - // Case 2: 2^16 - 2^8 <= a < 2^32, six octets, `0xff || 0xfe || [a]_32`. 65280 is the first. - assert_eq!( - Mode::encode_aad_len(65280), - ([0xff, 0xfe, 0x00, 0x00, 0xff, 0x00, 0, 0, 0, 0], 6), - "65280 crosses into the six-octet case; a two-octet 0xff00 would be ambiguous" - ); - assert_eq!( - Mode::encode_aad_len(65536), - ([0xff, 0xfe, 0x00, 0x01, 0x00, 0x00, 0, 0, 0, 0], 6), - "C.4's a = 65536" - ); - // 2^32 - 1 is the largest value still in the second case. - assert_eq!( - Mode::encode_aad_len(u32::MAX as u64), - ([0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0], 6), - "2^32 - 1 is still the six-octet case" - ); - - // Case 3: 2^32 <= a < 2^64, ten octets, `0xff || 0xff || [a]_64`. - assert_eq!( - Mode::encode_aad_len(1u64 << 32), - ([0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], 10), - "2^32 is the first ten-octet case" - ); - assert_eq!( - Mode::encode_aad_len(u64::MAX), - ([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 10) - ); - - // A.2.2's whole point: the three cases are distinguishable by their leading octets, so no - // two distinct lengths can encode to the same prefix. The first octet is 0xff only in the - // second and third cases, and the second octet separates those. - for a in [1u64, 8, 65279] { - assert_ne!(Mode::encode_aad_len(a).0[0], 0xff, "case 1 must not lead with 0xff"); - } - } - - /// The constructor really uses `CcmPacket::encode_aad_len`, and puts it *before* the AAD. - /// - /// With the identity permutation the CBC-MAC is `y = B0 ^ B1 ^ ... ^ Br`, so with a one-block - /// all-zero AAD the only nonzero contributions are `B0` and the length encoding. That makes the - /// 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; - 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. - let ccm = Mode::new(&key(), &nonce, &[0u8; 14], 0).unwrap(); - - let b0 = Mode::format_b0(&nonce, true, 0); - let mut b1 = [0u8; 16]; - b1[..2].copy_from_slice(&14u16.to_be_bytes()); - let expected: [u8; 16] = core::array::from_fn(|i| b0[i] ^ b1[i]); - assert_eq!(*ccm.y, expected, "y must be B0 ^ B1, with B1 starting with [14]_16"); - } -} - -/// The CCM mode type: the `M` of a mode-bound key such as `AES128Key>`. -/// -/// CCM must commit to the AAD and payload lengths before its first byte (`B0` carries the -/// payload length, A.2.2 length-prefixes the AAD), so the key's constructors take them: `CCM` -/// implements the packet mode traits, and its workers, [`CcmPacketEncryptor`] / -/// [`CcmPacketDecryptor`], then take exactly those amounts in any chunking and hold nothing -/// back. The generic-trait pair with the frame in its type, [`CcmEncryptor`] / [`CcmDecryptor`], -/// is the other way onto the same mode. -pub struct CCM; +/// The CCM mode type: the `M` of a mode-bound key such as `AES128Key>`. +/// +/// CCM must commit to the AAD and payload lengths before its first byte (`B0` carries the +/// payload length, A.2.2 length-prefixes the AAD), so the key's constructors take them: `CCM` +/// implements the declared-length mode traits, and its workers, [`CcmEncryptor`] / +/// [`CcmDecryptor`], then take exactly those amounts in any chunking and hold nothing +/// back. The generic-trait pair with the frame in its type, [`CcmPacketEncryptor`] / +/// [`CcmPacketDecryptor`], is the other way onto the same mode. +pub struct CCM; impl CCM { /// The largest payload this nonce length can carry, from A.1's "by definition, p<2^8q" with @@ -2256,35 +1562,39 @@ impl CCM { /// The encrypting worker a `CCM` key produces: Sec 6.1 over the lengths declared at /// construction, released as it is processed. The direction is in the type. -pub struct CcmPacketEncryptor( - CcmPacket, +pub struct CcmEncryptor<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize>( + Ccm<'k, P, Encrypting, 16, NONCE_LEN, TAG_LEN>, ) where - P: ElectronicCodeBook + ModeNames; + P: BlockPermutation<16> + ModeNames; /// The decrypting worker a `CCM` key produces: Sec 6.2 over the lengths declared at /// construction. The payload is released as it is recovered, up to the declared length; what /// follows can only be the inline tag, and is held back for the final. -pub struct CcmPacketDecryptor +pub struct CcmDecryptor<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - ccm: CcmPacket, + ccm: Ccm<'k, P, Decrypting, 16, NONCE_LEN, TAG_LEN>, // Public either way (the tag travels in the clear), so not wrapped. tag: [u8; TAG_LEN], tag_len: usize, } -impl - AeadEncryptWorker - for CcmPacketEncryptor +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> Algorithm + for CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { - self.0.do_update_aad(aad) - } + const ALG_NAME: &'static str = P::CCM_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> + SymmetricCipherEncryptor for CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ /// The identity: nothing is held back, since `B0` is already committed to the length. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -2295,14 +1605,14 @@ where plaintext: &[u8], ciphertext: &mut [u8], ) -> Result { - if plaintext.len() > self.0.owed { - return Err(SymmetricCipherError::StateError( - "CCM was given more payload than the declared payload length, which B0 commits to", - )); - } + ciphertext.fill(0); if ciphertext.len() < plaintext.len() { return Err(SymmetricCipherError::OutputBufferTooSmall(plaintext.len())); } + // Before the copy: `Ccm::do_encrypt_inplace` would refuse this too, but only after the + // plaintext had been copied into the caller's buffer, and a refused call must not leave + // plaintext there. + self.0.accepts_payload(plaintext.len())?; let out = &mut ciphertext[..plaintext.len()]; out.copy_from_slice(plaintext); self.0.do_encrypt_inplace(out)?; @@ -2313,6 +1623,20 @@ where Ok((self.0.do_encrypt_final()?, TAG_LEN)) } + fn encrypt_out_len(plaintext_len: usize) -> usize { + plaintext_len + TAG_LEN + } +} + +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> + AEADCipherEncryptor for CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ + fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { + self.0.do_update_aad(aad) + } + fn do_encrypt_final_detached_out( self, ciphertext: &mut [u8; TAG_LEN], @@ -2321,25 +1645,25 @@ where Ok((0, self.0.do_encrypt_final()?)) } - fn encrypt_out_len(plaintext_len: usize) -> usize { - plaintext_len + TAG_LEN - } - fn encrypt_detached_out_len(plaintext_len: usize) -> usize { plaintext_len } } -impl - AeadDecryptWorker - for CcmPacketDecryptor +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> Algorithm + for CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { - self.ccm.do_update_aad(aad) - } + const ALG_NAME: &'static str = P::CCM_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> + SymmetricCipherDecryptor for CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ /// Every byte still inside the declared payload; the rest can only be the tag, and is held. fn do_decrypt_out_len(&self, input_len: usize) -> usize { input_len.min(self.ccm.owed) @@ -2350,6 +1674,7 @@ where ciphertext: &[u8], plaintext: &mut [u8], ) -> Result { + plaintext.fill(0); let take = ciphertext.len().min(self.ccm.owed); let rest = ciphertext.len() - take; if self.tag_len + rest > TAG_LEN { @@ -2360,6 +1685,8 @@ where if plaintext.len() < take { return Err(SymmetricCipherError::OutputBufferTooSmall(take)); } + // Before the copy, as for the encryptor: a refused call must leave nothing behind. + self.ccm.accepts_payload(take)?; let out = &mut plaintext[..take]; out.copy_from_slice(&ciphertext[..take]); self.ccm.do_decrypt_inplace(out)?; @@ -2378,6 +1705,20 @@ where Ok(([0u8; TAG_LEN], 0)) } + fn decrypt_out_len(ciphertext_len: usize) -> usize { + ciphertext_len.saturating_sub(TAG_LEN) + } +} + +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> + AEADCipherDecryptor for CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ + fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { + self.ccm.do_update_aad(aad) + } + /// Detached: the ciphertext must be exactly the declared payload, with nothing after it. fn do_decrypt_final_detached_out( self, @@ -2392,20 +1733,18 @@ where Ok(0) } - fn decrypt_out_len(ciphertext_len: usize) -> usize { - ciphertext_len.saturating_sub(TAG_LEN) - } - fn decrypt_detached_out_len(ciphertext_len: usize) -> usize { ciphertext_len } } // The mode impls are generic over the permutation: `Mode

` needs no `KEY_LEN`, since CCM keeps -// nothing per key but the schedule, and the packet mode traits carry what else they need. The -// in-place one-shots override the provided bodies to run the packet directly over the buffer. +// nothing per key but the schedule, and the declared-length mode traits carry what else they need. The +// in-place one-shots override the provided bodies to run `Ccm` directly over the buffer. -impl Mode

for CCM { +impl Mode

+ for CCM +{ type KeyState = P; const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; @@ -2415,34 +1754,32 @@ impl Mode

for CCM - AeadPacketEncryptMode for CCM + AeadDeclaredEncryptMode for CCM where P: ElectronicCodeBook + ModeNames + Clone, { - type Encryptor = CcmPacketEncryptor; + type Encryptor<'k> + = CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> + where + P: 'k; - fn encrypt_init( - state: &Self::KeyState, + fn encrypt_init<'k>( + state: &'k Self::KeyState, rng: &mut dyn RNG, aad_len: usize, payload_len: usize, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError> { - let nonce = CcmPacket::::random_nonce(rng)?; + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + let nonce = Ccm::::random_nonce(rng)?; Ok((Self::encrypt_init_nonce(state, &nonce, aad_len, payload_len)?, nonce)) } - fn encrypt_init_nonce( - state: &Self::KeyState, + fn encrypt_init_nonce<'k>( + state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result { - Ok(CcmPacketEncryptor(CcmPacket::from_perm_with_lengths( - state.clone(), - nonce, - aad_len, - payload_len, - )?)) + ) -> Result, SymmetricCipherError> { + Ok(CcmEncryptor(Ccm::from_perm_with_lengths(state, nonce, aad_len, payload_len)?)) } fn encrypt_detached_in_place_nonce( @@ -2451,32 +1788,31 @@ where aad: &[u8], buf: &mut [u8], ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { - let mut ccm = CcmPacket::::from_perm( - state.clone(), - nonce, - aad, - buf.len(), - )?; + let mut ccm = + Ccm::::from_perm(state, nonce, aad, buf.len())?; ccm.do_encrypt_inplace(buf)?; ccm.do_encrypt_final() } } impl - AeadPacketDecryptMode for CCM + AeadDeclaredDecryptMode for CCM where P: ElectronicCodeBook + ModeNames + Clone, { - type Decryptor = CcmPacketDecryptor; + type Decryptor<'k> + = CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> + where + P: 'k; - fn decrypt_init( - state: &Self::KeyState, + fn decrypt_init<'k>( + state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result { - Ok(CcmPacketDecryptor { - ccm: CcmPacket::from_perm_with_lengths(state.clone(), nonce, aad_len, payload_len)?, + ) -> Result, SymmetricCipherError> { + Ok(CcmDecryptor { + ccm: Ccm::from_perm_with_lengths(state, nonce, aad_len, payload_len)?, tag: [0u8; TAG_LEN], tag_len: 0, }) @@ -2491,12 +1827,8 @@ where buf: &mut [u8], tag: &[u8; TAG_LEN], ) -> Result<(), SymmetricCipherError> { - let mut ccm = CcmPacket::::from_perm( - state.clone(), - nonce, - aad, - buf.len(), - )?; + let mut ccm = + Ccm::::from_perm(state, nonce, aad, buf.len())?; let result = ccm.do_decrypt_inplace(buf).and_then(|()| ccm.do_decrypt_final(tag)); if result.is_err() { buf.fill(0); @@ -2505,35 +1837,119 @@ where } } +/// The mode type of the fixed-frame pair: the `M` of a mode-bound key such as +/// `AES128Key>`, on the AEAD key path with [`CcmPacketEncryptor`] / +/// [`CcmPacketDecryptor`] as its workers, which take exactly `DATA_LEN` bytes of payload and up to +/// `AAD_LEN` of AAD. The one-shots a key provides are written over those workers, so they take +/// the frame length too; a message of any length goes through [`CCM`], whose constructors take +/// the lengths. The key state is the keyed permutation alone, as for [`CCM`]. +#[allow(non_camel_case_types)] +pub struct CCM_Packet< + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +>; + +impl< + P: Clone + ModeNames, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> Mode

for CCM_Packet +{ + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> AeadEncryptMode + for CCM_Packet +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = CcmPacketEncryptor<'k, P, 16, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + CcmPacketEncryptor::from_perm(state, rng) + } + + fn encrypt_init_nonce<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + ) -> Self::Encryptor<'k> { + CcmPacketEncryptor::from_perm_nonce(state, nonce) + } +} + +impl< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> AeadDecryptMode + for CCM_Packet +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = CcmPacketDecryptor<'k, P, 16, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> + where + P: 'k; + + fn decrypt_init<'k>(state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k> { + CcmPacketDecryptor::from_perm(state, nonce) + } +} + // ---- suspending the pair ----------------------------------------------------------------- -/// The adapter's suspended state is the packet's, then the held-back AAD, its length and the +/// The adapter's suspended state is the `Ccm` state's, then the held-back AAD, its length and the /// phase flag. A state that says the header is not yet formatted must have absorbed nothing, /// and the payload still owed can be no more than the frame. impl< + 'k, P, Dir, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> SuspendableComponent - for CcmAdapter +> SuspendableComponent<'k> + for CcmAdapter<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = - as SuspendableComponent>::STATE_LEN + as SuspendableComponent<'k>>::STATE_LEN + AAD_LEN + 8 + 1; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { let (ccm, rest) = out.split_at_mut( - as SuspendableComponent>::STATE_LEN, + as SuspendableComponent<'k>>::STATE_LEN, ); self.ccm.write_state(ccm); let mut w = CursorMut::new(rest); @@ -2543,12 +1959,12 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Self::check_shape(); let (ccm, rest) = state.split_at( - as SuspendableComponent>::STATE_LEN, + as SuspendableComponent<'k>>::STATE_LEN, ); - let ccm = CcmPacket::read_state(ccm, key)?; + let ccm = Ccm::read_state(ccm, key)?; let mut r = Cursor::new(rest); let aad: [u8; AAD_LEN] = r.array(); let aad_len = bounded_usize(r.u64(), AAD_LEN)?; @@ -2558,9 +1974,10 @@ where _ => return Err(SuspendableError::InvalidData), }; debug_assert!(r.is_done()); - // `B0` commits to `DATA_LEN`, so no more than that can still be owed; and until the - // header is formatted the CBC-MAC has absorbed nothing and no AAD is owed to it. - if ccm.owed > DATA_LEN || (!formatted && (ccm.mac_pos != 0 || ccm.aad_owed != 0)) { + // `B0` commits to `DATA_LEN`, so no more than that can still be owed. No AAD is ever + // owed: until the header is formatted the CBC-MAC has absorbed nothing, and `begin_data` + // declares the AAD and absorbs all of it in one step. + if ccm.owed > DATA_LEN || ccm.aad_owed != 0 || (!formatted && ccm.mac_pos != 0) { return Err(SuspendableError::InvalidData); } Ok(Self { ccm, aad, aad_len, formatted }) @@ -2568,81 +1985,80 @@ where } impl< + 'k, P, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> SuspendableComponent - for CcmEncryptor +> SuspendableComponent<'k> + for CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = as SuspendableComponent>::STATE_LEN; - type Key = KeyMaterial; + > as SuspendableComponent<'k>>::STATE_LEN; + type Key = P; fn write_state(&self, out: &mut [u8]) { self.0.write_state(out); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Ok(Self(CcmAdapter::read_state(state, key)?)) } } /// The decryptor adds the bytes it is holding back past the frame and how many there are. impl< + 'k, P, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, -> SuspendableComponent - for CcmDecryptor +> SuspendableComponent<'k> + for CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = as SuspendableComponent>::STATE_LEN + > as SuspendableComponent<'k>>::STATE_LEN + TAG_LEN + 8; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { - let (inner, rest) = - out.split_at_mut( - as SuspendableComponent>::STATE_LEN, - ); + let (inner, rest) = out.split_at_mut( + as SuspendableComponent<'k>>::STATE_LEN, + ); self.inner.write_state(inner); let mut w = CursorMut::new(rest); w.bytes(&self.tag); @@ -2650,20 +2066,19 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { - let (inner, rest) = - state.split_at( - as SuspendableComponent>::STATE_LEN, - ); + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + let (inner, rest) = state.split_at( + as SuspendableComponent<'k>>::STATE_LEN, + ); let inner = CcmAdapter::read_state(inner, key)?; let mut r = Cursor::new(rest); let tag: [u8; TAG_LEN] = r.array(); @@ -2674,88 +2089,374 @@ where } impl< + 'k, P, - 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 +> CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the mode's + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the version header, the mode's /// state, the `AAD_LEN`-byte AAD buffer, its length and the phase flag. See /// [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + LIB_VERSION_LEN + >::STATE_LEN; } impl< + 'k, P, - 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 +> CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the encryptor's, plus the held-back + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the encryptor's, plus the held-back /// tag and its length. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + LIB_VERSION_LEN + >::STATE_LEN; } -/// `N` must be [`CcmEncryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. +/// `N` must be [`CcmPacketEncryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. impl< + 'k, P, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, const N: usize, -> SuspendableKeyed for CcmEncryptor +> SuspendableKeyed<'k, N> + for CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } -/// `N` must be [`CcmDecryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. +/// `N` must be [`CcmPacketDecryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. impl< + 'k, P, - const KEY_LEN: usize, const BLOCK_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const AAD_LEN: usize, const DATA_LEN: usize, const N: usize, -> SuspendableKeyed for CcmDecryptor +> SuspendableKeyed<'k, N> + for CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } + +#[cfg(test)] +mod tests { + //! Tests for the private formatting helpers, which are what a reviewer with SP 800-38C open + //! most needs to check and which no public API exposes directly. + //! + //! The expected values are the `B` and `Ctr_i` strings printed in the spec's own Appendix C + //! examples, transcribed from the errata-updated PDF. Appendix C gives the formatted block + //! string for each example, so these pin the flags octet, the placement of `N` and `Q`, and + //! the AAD length encoding against the document rather than against this implementation. + + use super::*; + use bouncycastle_core::key_material::{KeyMaterial, KeyType}; + + /// 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 + /// tests about the formatting function rather than about AES. + struct Identity; + + impl Algorithm for Identity { + const ALG_NAME: &'static str = "identity"; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + } + + impl ModeNames for Identity { + const CBC_ALG_NAME: &'static str = "identity_CBC"; + const CCM_ALG_NAME: &'static str = "identity_CCM"; + const CFB_ALG_NAME: &'static str = "identity_CFB"; + const CFB8_ALG_NAME: &'static str = "identity_CFB8"; + const CMAC_ALG_NAME: &'static str = "identity_CMAC"; + const CTR_ALG_NAME: &'static str = "identity_CTR"; + const ECB_ALG_NAME: &'static str = "identity_ECB"; + const GCM_ALG_NAME: &'static str = "identity_GCM"; + const GMAC_ALG_NAME: &'static str = "identity_GMAC"; + } + + impl ElectronicCodeBook<16, 16> for Identity { + fn new(_key: &KeyMaterial<16>) -> Result { + Ok(Identity) + } + } + + impl BlockPermutation<16> for Identity { + fn encrypt_block(&self, _block: &mut [u8; 16]) {} + fn decrypt_block(&self, _block: &mut [u8; 16]) {} + fn encrypt_2blocks(&self, _blocks: &mut [[u8; 16]; 2]) {} + fn decrypt_2blocks(&self, _blocks: &mut [[u8; 16]; 2]) {} + fn encrypt_4blocks(&self, _blocks: &mut [[u8; 16]; 4]) {} + 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, + ) + .expect("Appendix C's 128-bit key") + } + + /// Appendix C.1: `Tlen=32, Nlen=56, Alen=64, Plen=32`, so `t = 4`, `n = 7`, `q = 8`. + /// + /// The spec prints `B` as + /// `4f101112 13141516 00000000 00000004 | 00080001 02030405 06070000 00000000 | ...`, + /// so `B0` is `4f` then the 7-byte nonce then `[4]_64`, and `B1` is `[8]_16` then the 8-byte + /// AAD then six zero bytes of pad. + /// + /// C.1's AAD is 8 bytes, so its Adata bit is set. + #[test] + fn c1_b0_matches_the_spec() { + let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; + assert_eq!( + Ccm::::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" + ); + } + + /// A.2.2: the Adata bit is "'0' if a=0 and '1' if a>0", and it is bit 6 -- so clearing it must + /// take C.1's `0x4f` to `0x0f` and change nothing else in the block. + #[test] + fn adata_flag_is_bit_6_of_the_flags_octet() { + let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; + let with = Ccm::::format_b0(&nonce, true, 4); + let without = Ccm::::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"); + } + + /// The constructor really does absorb the `B0` that [`Ccm::format_b0`] built. With the identity + /// permutation the CBC-MAC chaining value after one block is that block itself, so a + /// no-AAD, no-payload construction leaves `B0` sitting in `y`. + /// + /// Without this, `format_b0` could be correct and unused. + #[test] + fn the_constructor_absorbs_b0() { + let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; + let identity = Identity::new(&key()).unwrap(); + let ccm = + Ccm::::from_perm_with_lengths(&identity, &nonce, 0, 4) + .unwrap(); + assert_eq!(*ccm.y, Ccm::::format_b0(&nonce, false, 4)); + assert_eq!(ccm.mac_pos, 0, "a whole block was absorbed, so nothing is part-filled"); + } + + /// Appendix C.4: `Tlen=112, Nlen=104, Plen=256`, so `t = 14`, `n = 13`, `q = 2`; the spec + /// prints `B0` as `71101112 13141516 1718191a 1b1c0020`. + /// + /// This is the other end of the `q` range from C.1, so between them the two tests pin the + /// `[q-1]_3` encoding and the fact that `Q` is `q` octets wide, not a fixed width. + #[test] + fn c4_b0_matches_the_spec() { + let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; + assert_eq!( + Ccm::::format_b0(&nonce, true, 32), + [ + 0x71, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, + 0x00, 0x20 + ], + "C.4 B0: flags 0x71 = Adata 1 | [(14-2)/2]_3 = 110 | [2-1]_3 = 001, then Q = [32]_16" + ); + } + + /// Appendix C.1 prints `Ctr0` as `07101112 13141516 00000000 00000000` and `Ctr1` as the same + /// with a trailing `01`; C.4's are `01101112 ... 1b1c0000` and `... 1b1c0001`. + /// + /// Table 4 makes the counter flags `[q-1]_3` alone, with every other bit zero -- which is what + /// keeps them distinct from `B0`, whose `t` field cannot be zero. + #[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); + // `Ctr0` is the template with a zero counter field. + 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], + "C.1 Ctr0" + ); + // The first payload keystream block is `S1`, so the first block the keystream XORs in must + // be `Ctr1`: under the identity permutation, XORed into zeros, that is `Ctr1` itself. + let mut s1 = [[0u8; 16]]; + ks.apply_blocks(&mut s1); + let mut ctr1 = ctr0; + ctr1[15] = 1; + assert_eq!(s1[0], ctr1, "C.1 Ctr1 (the identity permutation leaves S1 = Ctr1)"); + + let nonce_c4 = + [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; + let ks4 = CcmKeyStream::::from_perm(&Identity, &nonce_c4); + assert_eq!( + CcmKeyStream::::counter_block(&ks4.ctr_template, 0), + [ + 0x01, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, + 0x00, 0x00 + ], + "C.4 Ctr0" + ); + } + + /// A fresh keystream has every counter value but `Ctr0` left: step 7's `S1 || S2 || ...` runs + /// from `j = 1` to the largest `q`-octet counter, `2^8q - 1`, and `S0` is the tag mask. Pinned + /// absolutely because the limit can never bind through the public API -- A.1 caps the payload + /// at `2^8q - 1` bytes, far fewer than that many blocks -- so nothing else would notice an + /// off-by-one here. + #[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]); + 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]); + assert_eq!(ks.remaining_blocks(), u64::MAX); + } + + /// CCM's keystream against the shared [`KeyStream`] conformance suite. A unit test rather than + /// an integration test because `CcmKeyStream` is crate-private. Over the keyed toy rather than + /// 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 perm = ToyBlockCipher::new(&key()).unwrap(); + let framework = TestFrameworkKeyStream::new(); + framework.test::<7, 16, _>(|nonce| { + CcmKeyStream::::from_perm(&perm, nonce) + }); + framework.test::<13, 16, _>(|nonce| { + CcmKeyStream::::from_perm(&perm, nonce) + }); + } + + /// A.2.2's three AAD length encodings, at and around both boundaries. + /// + /// Two of these values come from the spec itself: C.1's `a = 8` is printed as `0008`, and + /// C.4's `a = 65536` (`Alen = 524288` bits) is printed as + /// `11111111 11111110 00000000 00000001 00000000 00000000`, i.e. `ff fe 00 01 00 00`. + /// + /// The rest pin the boundaries, which is the part no end-to-end test can reach: the first is + /// `2^16 - 2^8` = 65280 rather than the obvious-but-wrong `2^16`, and the second is `2^32`, + /// which through the public API would need a 4 GiB AAD. + #[test] + fn aad_length_encoding_matches_a_2_2() { + type Mode<'k> = Ccm<'k, Identity, Encrypting, 16, 7, 4>; + + // Case 1: 0 < a < 2^16 - 2^8, two octets, `[a]_16`. + assert_eq!( + Mode::encode_aad_len(8), + ([0x00, 0x08, 0, 0, 0, 0, 0, 0, 0, 0], 2), + "C.1's a = 8" + ); + assert_eq!(Mode::encode_aad_len(1).1, 2); + // 65279 = 2^16 - 2^8 - 1 is the largest value still in the first case. + assert_eq!( + Mode::encode_aad_len(65279), + ([0xfe, 0xff, 0, 0, 0, 0, 0, 0, 0, 0], 2), + "65279 is still [a]_16" + ); + + // Case 2: 2^16 - 2^8 <= a < 2^32, six octets, `0xff || 0xfe || [a]_32`. 65280 is the first. + assert_eq!( + Mode::encode_aad_len(65280), + ([0xff, 0xfe, 0x00, 0x00, 0xff, 0x00, 0, 0, 0, 0], 6), + "65280 crosses into the six-octet case; a two-octet 0xff00 would be ambiguous" + ); + assert_eq!( + Mode::encode_aad_len(65536), + ([0xff, 0xfe, 0x00, 0x01, 0x00, 0x00, 0, 0, 0, 0], 6), + "C.4's a = 65536" + ); + // 2^32 - 1 is the largest value still in the second case. + assert_eq!( + Mode::encode_aad_len(u32::MAX as u64), + ([0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0], 6), + "2^32 - 1 is still the six-octet case" + ); + + // Case 3: 2^32 <= a < 2^64, ten octets, `0xff || 0xff || [a]_64`. + assert_eq!( + Mode::encode_aad_len(1u64 << 32), + ([0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], 10), + "2^32 is the first ten-octet case" + ); + assert_eq!( + Mode::encode_aad_len(u64::MAX), + ([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 10) + ); + + // A.2.2's whole point: the three cases are distinguishable by their leading octets, so no + // two distinct lengths can encode to the same prefix. The first octet is 0xff only in the + // second and third cases, and the second octet separates those. + for a in [1u64, 8, 65279] { + assert_ne!(Mode::encode_aad_len(a).0[0], 0xff, "case 1 must not lead with 0xff"); + } + } + + /// The constructor really uses `Ccm::encode_aad_len`, and puts it *before* the AAD. + /// + /// With the identity permutation the CBC-MAC is `y = B0 ^ B1 ^ ... ^ Br`, so with a one-block + /// all-zero AAD the only nonzero contributions are `B0` and the length encoding. That makes the + /// 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<'k> = Ccm<'k, Identity, Encrypting, 16, 7, 4>; + 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. + let identity = Identity::new(&key()).unwrap(); + let mut ccm = Mode::from_perm_with_lengths(&identity, &nonce, 14, 0).unwrap(); + ccm.absorb_aad(&[0u8; 14]); + + let b0 = Mode::format_b0(&nonce, true, 0); + let mut b1 = [0u8; 16]; + b1[..2].copy_from_slice(&14u16.to_be_bytes()); + let expected: [u8; 16] = core::array::from_fn(|i| b0[i] ^ b1[i]); + assert_eq!(*ccm.y, expected, "y must be B0 ^ B1, with B1 starting with [14]_16"); + } +} diff --git a/crypto/cipher/src/modes/cfb.rs b/crypto/cipher/src/modes/cfb.rs index a953ef6a2..36213f44c 100644 --- a/crypto/cipher/src/modes/cfb.rs +++ b/crypto/cipher/src/modes/cfb.rs @@ -26,8 +26,8 @@ //! 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`], -//! but never calls [`ElectronicCodeBook::decrypt_block`]. A permutation that implements only the +//! So [`Cfb<'k, P, Decrypting, ..>`](Cfb) is implemented over a permutation that impls [`ElectronicCodeBook`], +//! but never calls [`BlockPermutation::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 //! the next block. @@ -40,44 +40,50 @@ //! decryption, the required forward cipher operations can be performed in parallel if the input //! blocks are first constructed (in series) from the IV and the ciphertext." //! -//! This implementation follows: encryption handles blocks singly via [`ElectronicCodeBook::encrypt_block`], +//! This implementation follows: encryption handles blocks singly via [`BlockPermutation::encrypt_block`], //! while decryption can batch-process two or four blocks at a time via -//! [`ElectronicCodeBook::encrypt_2blocks`] or [`ElectronicCodeBook::encrypt_4blocks`], which may +//! [`BlockPermutation::encrypt_2blocks`] or [`BlockPermutation::encrypt_4blocks`], which may //! yield a performance gain, depending on the implementation of the underlying permutation. //! //! # 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<'k, P, Encrypting, ..>`](Cfb) implements +//! [`StreamCipherEncryptor`] and nothing else, and [`Cfb<'k, P, Decrypting, ..>`](Cfb) implements //! [`StreamCipherDecryptor`] and nothing else. //! //! They take a `&mut [u8]` of any length; there is no padding layer and the ciphertext is exactly //! as long as the plaintext: //! +//! The pair is reached through a mode-bound key, [`CFB`] or [`CFB8`](crate::modes::CFB8) on a key type such as +//! `bouncycastle_aes::AES128Key`, whose role traits carry the one-shots. The key holds the +//! mode's key state and calls the mode traits shown here, which is the route over the toy +//! permutation: +//! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; -//! use bouncycastle_cipher::modes::{Cfb, Cfb8}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyCfb

= Cfb; -//! type ToyCfb8 = Cfb8; +//! use bouncycastle_cipher::modes::CFB; +//! use bouncycastle_rng::HashDRBG_SHA512; //! //! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let state = >::key_state(ToyBlockCipher::new(&key).unwrap()); +//! let mut rng = HashDRBG_SHA512::new_from_os(); //! //! // Start with the plaintext. //! let plaintext = b"the quick brown fox!!"; //! let mut data = *plaintext; //! -//! let (bytes_written, iv) = ToyCfb::::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (mut enc, iv) = CFB::encrypt_init(&state, &mut rng).expect("iv draw"); +//! let bytes_written = enc.do_encrypt_inplace(&mut data).expect("encryption"); //! assert_eq!(bytes_written, plaintext.len()); //! //! // `data` now contains the ciphertext //! -//! ToyCfb::::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); +//! CFB::decrypt_init(&state, &iv).do_decrypt_inplace(&mut data).expect("decryption"); //! assert_eq!(data, *b"the quick brown fox!!"); //! ``` //! @@ -87,13 +93,14 @@ //! //! [`Cfb`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a byte //! array and resumed later with the re-supplied key. The state is the open segment and how much of -//! it is used; the permutation is rebuilt from the key. The array length is +//! it is used; the permutation is reached through the re-supplied key. The array length is //! `Cfb::SUSPENDED_STATE_LEN`; see [the crate docs](crate#suspending-and-resuming-execution) for an //! example. //! //! # Memory Usage //! -//! The state consists of the underlying permutation struct, one block, `buf`, and a byte count, `used`. +//! The state consists of a reference to the permutation, one block, `buf`, and a byte count, +//! `used`. //! //! # 🚨 Security Considerations 🚨 //! @@ -140,14 +147,16 @@ use crate::modes::ModeNames; 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::hazmat::{ + BlockPermutation, ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode, +}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::HashDRBG_SHA512; +use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ Cursor, CursorMut, LIB_VERSION_LEN, SuspendableComponent, bounded_usize, resume_component, suspend_component, @@ -159,6 +168,58 @@ use core::marker::PhantomData; use crate::modes::cfb8; // End imports needed for docs +/// The CFB mode type: the `M` of a mode-bound key such as `AES128Key`, on the symmetric key +/// path with [`Cfb`] as its worker. Its key state is the keyed permutation alone. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CFB; + +impl Mode

for CFB { + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl SymmetricEncryptMode + for CFB +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Cfb<'k, P, Encrypting, BLOCK_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; BLOCK_LEN]), SymmetricCipherError> { + // `I1 = IV`. + let iv = random_iv::(rng)?; + Ok((Cfb::start(state, iv), iv)) + } +} + +impl SymmetricDecryptMode + for CFB +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Cfb<'k, P, Decrypting, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; BLOCK_LEN], + ) -> Self::Decryptor<'k> { + Cfb::start(state, *init_data) + } +} + /// CFB mode over any [`ElectronicCodeBook`], as a stream cipher, with the direction encoded in the /// type. /// @@ -167,17 +228,17 @@ 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<'k, _, Encrypting, _, _>` 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<'k, P, Dir, const BLOCK_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k P, /// `buf[..used]` is the ciphertext of the current segment so far, i.e. the head of `I_{j+1}`; /// `buf[used..]` is the unused tail of `Oj`. When `used == BLOCK_LEN` the whole buffer is the /// next input block (initially `I1 = IV`) and no keystream is pending. @@ -191,26 +252,28 @@ where // into `O_{j+1}`. So the same 16 bytes are the input block, then the output block, then the // next input block, and no copy is ever made. // - // Since `buf` holds either plaintext or ciphertext, but never any keymaterial, it is not - // necessary to tag it as `Secret<>`. - buf: [u8; BLOCK_LEN], + // `buf[used..]` is keystream, which gives away the next `BLOCK_LEN - used` plaintext bytes + // against their ciphertext, so the buffer is a `Secret`. + buf: Secret<[u8; BLOCK_LEN]>, /// Bytes of the current segment already processed, `0..=BLOCK_LEN`. used: usize, _dir: PhantomData

, } -impl Cfb +impl<'k, P, Dir, const BLOCK_LEN: usize> Cfb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the segment + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the version header, the segment /// buffer and the `used` count as a `u64`. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = LIB_VERSION_LEN + BLOCK_LEN + 8; /// `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 } + fn start(perm: &'k P, iv: [u8; BLOCK_LEN]) -> Self { + let mut buf: Secret<[u8; BLOCK_LEN]> = Secret::new(); + *buf = iv; + Self { perm, buf, used: BLOCK_LEN, _dir: PhantomData } } /// Makes the next keystream byte available: if the current segment is complete, `buf` is the @@ -269,7 +332,7 @@ where *b ^= *o; } // I_{j+1} = Cj. Serial: this is the input to the next cipher call. - self.buf = *block; + *self.buf = *block; } /// Decrypts one whole block at a segment boundary. `Cj` is overwritten by `Pj`, so it is copied @@ -282,10 +345,10 @@ where for (b, o) in block.iter_mut().zip(self.buf.iter()) { *b ^= *o; } - self.buf = cj; + *self.buf = cj; } - /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::encrypt_2blocks`] call. + /// Decrypts two consecutive blocks with one [`BlockPermutation::encrypt_2blocks`] call. /// /// Writing the pair as `Cj, Cj+1` with `Ij` the incoming input block, the `s = b` equations /// give @@ -306,11 +369,11 @@ where fn decrypt_pair(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { debug_assert_eq!(self.used, BLOCK_LEN, "the block path needs a segment boundary"); // The two input blocks, constructed in series: Ij (already held) and Ij+1 (= Cj). - let mut o = [self.buf, blocks[0]]; + let mut o = [*self.buf, blocks[0]]; self.perm.encrypt_2blocks(&mut o); // I_{j+2} = Cj+1, read before the XOR below turns it into Pj+1. - self.buf = blocks[1]; + *self.buf = blocks[1]; for (block, o) in blocks.iter_mut().zip(o.iter()) { for (b, o) in block.iter_mut().zip(o.iter()) { @@ -319,7 +382,7 @@ where } } - /// Decrypts four consecutive blocks with one [`ElectronicCodeBook::encrypt_4blocks`] call. + /// Decrypts four consecutive blocks with one [`BlockPermutation::encrypt_4blocks`] call. /// /// The same construction as [`Self::decrypt_pair`] widened to four: the input blocks are the /// incoming input block followed by the first three ciphertext blocks, all known before any @@ -328,9 +391,9 @@ where #[inline] fn decrypt_four(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 4]) { debug_assert_eq!(self.used, BLOCK_LEN, "the block path needs a segment boundary"); - let mut o = [self.buf, blocks[0], blocks[1], blocks[2]]; + let mut o = [*self.buf, blocks[0], blocks[1], blocks[2]]; self.perm.encrypt_4blocks(&mut o); - self.buf = blocks[3]; + *self.buf = blocks[3]; for (block, o) in blocks.iter_mut().zip(o.iter()) { for (b, o) in block.iter_mut().zip(o.iter()) { *b ^= *o; @@ -354,10 +417,9 @@ where } } -impl Algorithm - for Cfb +impl<'k, P, Dir, const BLOCK_LEN: usize> Algorithm for Cfb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::CFB_ALG_NAME`]. const ALG_NAME: &'static str = P::CFB_ALG_NAME; @@ -365,30 +427,11 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor for Cfb +impl<'k, P, const BLOCK_LEN: usize> SymmetricCipherEncryptor + for Cfb<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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> { - 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; - // `I1 = IV`. - let iv = random_iv::(rng)?; - Ok((Self::start(perm, iv), iv)) - } - /// Every input byte produces exactly one output byte. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -426,10 +469,10 @@ where } } -impl StreamCipherEncryptor - for Cfb +impl<'k, P, const BLOCK_LEN: usize> StreamCipherEncryptor + for Cfb<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Encrypts `data`, of any length, in place. /// @@ -451,22 +494,11 @@ where } } -impl - SymmetricCipherDecryptor for Cfb +impl<'k, P, const BLOCK_LEN: usize> SymmetricCipherDecryptor + for Cfb<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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)?; - // `I1 = IV`, exactly as on the encrypt side. - Ok(Self::start(perm, *init_data)) - } - /// Nothing is held back, so every input byte can be released immediately. fn do_decrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -504,10 +536,10 @@ where } } -impl StreamCipherDecryptor - for Cfb +impl<'k, P, const BLOCK_LEN: usize> StreamCipherDecryptor + for Cfb<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Decrypts `data`, of any length, in place. /// @@ -537,27 +569,28 @@ 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 +/// the keystream not yet used against it -- in both directions; the permutation is the key's, +/// re-supplied by reference. See [`bouncycastle_utils::suspendable_state`]. +impl<'k, P, Dir, const BLOCK_LEN: usize> SuspendableComponent<'k> for Cfb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = BLOCK_LEN + 8; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); - w.bytes(&self.buf); + w.bytes(&*self.buf); w.u64(self.used as u64); debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + let perm = key; let mut r = Cursor::new(state); - let buf = r.array::(); + // Read straight into the `Secret`, so no copy of the keystream tail sits on the stack. + let mut buf: Secret<[u8; BLOCK_LEN]> = Secret::new(); + (*buf).copy_from_slice(r.bytes(BLOCK_LEN)); // `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()); @@ -566,18 +599,21 @@ where } /// `N` must be [`Cfb::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cfb +impl<'k, P, Dir, const BLOCK_LEN: usize, const N: usize> SuspendableKeyed<'k, N> + for Cfb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + /// The state carries no direction: one suspended by the other direction's worker is accepted + /// and resumes over the same chaining value, which gives wrong output rather than an error. + /// Only GCM, whose state means different things per direction, refuses the other one. + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/src/modes/cfb8.rs b/crypto/cipher/src/modes/cfb8.rs index 0d4859f6e..b1797ff8e 100644 --- a/crypto/cipher/src/modes/cfb8.rs +++ b/crypto/cipher/src/modes/cfb8.rs @@ -27,7 +27,7 @@ //! //! [`Cfb8`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a byte //! array and resumed later with the re-supplied key. The state is the shift register; the -//! permutation is rebuilt from the key. The array length is `Cfb8::SUSPENDED_STATE_LEN`; see [the +//! permutation is reached through the re-supplied key. The array length is `Cfb8::SUSPENDED_STATE_LEN`; see [the //! crate docs](crate#suspending-and-resuming-execution) for an example. //! //! # 🚨 Security Considerations 🚨 @@ -56,14 +56,15 @@ use crate::modes::ModeNames; 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::hazmat::{ + BlockPermutation, ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode, +}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::suspendable_state::{ LIB_VERSION_LEN, SuspendableComponent, resume_component, suspend_component, }; @@ -74,13 +75,65 @@ use core::marker::PhantomData; use crate::modes::cfb; // End imports needed for docs +/// The CFB8 mode type: the `M` of a mode-bound key such as `AES128Key`, on the symmetric +/// key path with [`Cfb8`] as its worker. Its key state is the keyed permutation alone. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CFB8; + +impl Mode

for CFB8 { + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl SymmetricEncryptMode + for CFB8 +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Cfb8<'k, P, Encrypting, BLOCK_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; BLOCK_LEN]), SymmetricCipherError> { + // `I1 = IV`. + let iv = random_iv::(rng)?; + Ok((Cfb8 { perm: state, chain: iv, _dir: PhantomData }, iv)) + } +} + +impl SymmetricDecryptMode + for CFB8 +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Cfb8<'k, P, Decrypting, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; BLOCK_LEN], + ) -> Self::Decryptor<'k> { + Cfb8 { perm: state, chain: *init_data, _dir: PhantomData } + } +} + /// CFB8 mode over any [`ElectronicCodeBook`], with the direction encoded in the type. /// /// The segment size is one byte (`s = 8`); see the module docs, and note that this is **not** /// interoperable with [`cfb`], which is `s = b`. /// /// `Dir` is [`Encrypting`] or [`Decrypting`]. [`StreamCipherEncryptor`] is implemented only for the -/// former and [`StreamCipherDecryptor`] only for the latter, so a `Cfb8<_, Encrypting, _, _>` has +/// former and [`StreamCipherDecryptor`] only for the latter, so a `Cfb8<'k, _, Encrypting, _, _>` has /// no decryption methods at all -- using one in the wrong direction is a compile error rather than /// a runtime check. /// @@ -88,30 +141,29 @@ use crate::modes::cfb; /// /// # State /// -/// Two fields, the same size as `Cbc`: the permutation (which owns the key schedule, and is -/// responsible for keeping it in a zeroize-on-drop wrapper) and one block holding the shift -/// register `Ij`. `Ij` is built from the IV and ciphertext bytes, both of which are public, so it +/// Two fields, the same size as `Cbc`: a reference to the permutation, whose key schedule stays +/// in the key, and one block holding the shift register `Ij`. `Ij` is built from the IV and ciphertext bytes, both of which are public, so it /// is deliberately not wrapped in a `Secret`. /// /// Note what is *not* stored: the output block `Oj`. It is recomputed from the register on each /// 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<'k, P, Dir, const BLOCK_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k P, /// `Ij`: the IV, then the shift register. See the module docs. chain: [u8; BLOCK_LEN], _dir: PhantomData

, } -impl Cfb8 +impl<'k, P, Dir, const BLOCK_LEN: usize> Cfb8<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header and the shift + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the version header and the shift /// register. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = LIB_VERSION_LEN + BLOCK_LEN; @@ -169,10 +221,9 @@ where } } -impl Algorithm - for Cfb8 +impl<'k, P, Dir, const BLOCK_LEN: usize> Algorithm for Cfb8<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::CFB8_ALG_NAME`]. const ALG_NAME: &'static str = P::CFB8_ALG_NAME; @@ -180,30 +231,11 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor for Cfb8 +impl<'k, P, const BLOCK_LEN: usize> SymmetricCipherEncryptor + for Cfb8<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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> { - 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; BLOCK_LEN]), SymmetricCipherError> { - let perm = P::new(key)?; - // `I1 = IV`. - let iv = random_iv::(rng)?; - Ok((Self { perm, chain: iv, _dir: PhantomData }, iv)) - } - /// Every input byte produces exactly one output byte. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -241,10 +273,10 @@ where } } -impl StreamCipherEncryptor - for Cfb8 +impl<'k, P, const BLOCK_LEN: usize> StreamCipherEncryptor + for Cfb8<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// 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,22 +293,11 @@ where } } -impl - SymmetricCipherDecryptor for Cfb8 +impl<'k, P, const BLOCK_LEN: usize> SymmetricCipherDecryptor + for Cfb8<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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)?; - // `I1 = IV`, exactly as on the encrypt side. - Ok(Self { perm, chain: *init_data, _dir: PhantomData }) - } - /// Nothing is held back, so every input byte can be released immediately. fn do_decrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -314,10 +335,10 @@ where } } -impl StreamCipherDecryptor - for Cfb8 +impl<'k, P, const BLOCK_LEN: usize> StreamCipherDecryptor + for Cfb8<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// 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 @@ -345,22 +366,21 @@ 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 +/// The suspended state is the shift register `Ij`, in both directions; the permutation is the +/// key's, re-supplied by reference. See [`bouncycastle_utils::suspendable_state`]. +impl<'k, P, Dir, const BLOCK_LEN: usize> SuspendableComponent<'k> for Cfb8<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = BLOCK_LEN; - type Key = KeyMaterial; + type Key = P; 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)?; + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + let perm = key; let mut chain = [0u8; BLOCK_LEN]; chain.copy_from_slice(state); Ok(Self { perm, chain, _dir: PhantomData }) @@ -368,18 +388,21 @@ where } /// `N` must be [`Cfb8::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Cfb8 +impl<'k, P, Dir, const BLOCK_LEN: usize, const N: usize> SuspendableKeyed<'k, N> + for Cfb8<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + /// The state carries no direction: one suspended by the other direction's worker is accepted + /// and resumes over the same chaining value, which gives wrong output rather than an error. + /// Only GCM, whose state means different things per direction, refuses the other one. + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/src/modes/cmac.rs b/crypto/cipher/src/modes/cmac.rs index 67fe5a227..dcdc91278 100644 --- a/crypto/cipher/src/modes/cmac.rs +++ b/crypto/cipher/src/modes/cmac.rs @@ -1,48 +1,53 @@ //! CMAC, the block-cipher-based message authentication code of NIST SP 800-38B. //! -//! [`Cmac`] implements [`MAC`] over any [`ElectronicCodeBook`] permutation with a 64- or 128-bit +//! [`Cmac`] implements [`MAC`] over any [`BlockPermutation`] permutation with a 64- or 128-bit //! block (the two block sizes SP 800-38B Sec 5.3 defines `R_b` for). The output is one full block, //! which [`MAC::mac_out`] / [`MAC::do_final_out`] truncate to the caller's buffer, down to //! [`CMAC_MIN_TAG_LEN`] bytes. //! -//! There are two ways onto it. [`MAC::new`] takes the key material for one message and derives -//! the subkeys each time. A mode-bound key holds a [`CmacKeyState`] -- the keyed permutation and -//! the two subkeys, derived once -- and hands out workers through [`CMAC`], the marker a key type -//! such as `bouncycastle_aes::AES128Key` is bound to. +//! The way onto it is a mode-bound key, built with `from_key_material`, which holds a +//! [`CmacKeyState`] -- the keyed permutation and the two subkeys, derived once -- and hands out +//! workers that borrow it through [`CMAC`], the marker a key type such as +//! `bouncycastle_aes::AES128Key` is bound to. //! //! # Usage Examples //! -//! One-shot, and verifying, over `ToyBlockCipher` (see the [module docs](super) for why): +//! One-shot, and verifying, from a mode-bound key over `ToyBlockCipher` (see the +//! [module docs](super) for why); over the toy permutation the key is the test suite's `ToyKey`, +//! the shape of the AES one: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::Cmac; +//! # use toy_key::ToyKey; +//! use bouncycastle_cipher::modes::CMAC; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! -//! type ToyCmac = Cmac; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); -//! let tag = ToyCmac::new(&key).unwrap().mac(b"a message"); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); +//! let key = ToyKey::::from_key_material(&material).unwrap(); +//! let tag = key.mac(b"a message"); //! assert_eq!(tag.len(), 16); -//! assert!(ToyCmac::new(&key).unwrap().verify(b"a message", &tag)); +//! assert!(key.verify(b"a message", &tag)); //! ``` //! -//! Streaming from a key state, with a tag truncated to 8 bytes: +//! Streaming, with a tag truncated to 8 bytes: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::{Cmac, CmacKeyState}; -//! use bouncycastle_core::hazmat::ElectronicCodeBook; +//! # use toy_key::ToyKey; +//! use bouncycastle_cipher::modes::CMAC; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey}; //! use bouncycastle_core::traits::MAC; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! let state = CmacKeyState::new::<16>(ToyBlockCipher::new(&key).unwrap()); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); +//! let key = ToyKey::::from_key_material(&material).unwrap(); //! -//! let mut cmac = Cmac::::from_key_state(&state); +//! let mut cmac = key.create_generator(); //! cmac.do_update(b"a message that arrives "); //! cmac.do_update(b"in pieces"); //! let mut tag = [0u8; 8]; @@ -58,15 +63,18 @@ //! * **Message span** (Appendix B): limit a key to 2^48 messages with a 128-bit block cipher, and //! 2^21 with a 64-bit one. //! * **Key separation** (Sec 5.2): the key "shall be used exclusively for the CMAC mode of the -//! chosen block cipher", so [`MAC::new`] accepts only a [`KeyType::MACKey`] key (or an all-zero -//! key, as HMAC does), never a [`KeyType::SymmetricCipherKey`]. +//! chosen block cipher", so the key's `from_key_material` accepts only a [`KeyType::MACKey`] +//! key (or an all-zero key, as HMAC does), never a [`KeyType::SymmetricCipherKey`]. //! * **Replay** (Appendix C): a valid tag says nothing about freshness; that is the protocol's job. +//! +//! [`KeyType::MACKey`]: bouncycastle_core::key_material::KeyType::MACKey +//! [`KeyType::SymmetricCipherKey`]: bouncycastle_core::key_material::KeyType::SymmetricCipherKey use super::ModeNames; use alloc::vec::Vec; -use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; -use bouncycastle_core::hazmat::{ElectronicCodeBook, do_hazardous_operations}; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::errors::{MACError, SuspendableError}; +use bouncycastle_core::hazmat::{BlockPermutation, MacMode, Mode}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{Algorithm, MAC, SuspendableKeyed}; use bouncycastle_utils::ct::ct_eq_bytes; @@ -79,10 +87,38 @@ use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; /// a careful analysis of the risks"). pub const CMAC_MIN_TAG_LEN: usize = 8; -/// The CMAC mode, as a key type names it: `AES128Key`. The mode-trait impls over each -/// permutation live with that permutation. +/// The CMAC mode, as a key type names it: `AES128Key`. `BLOCK_LEN` is the permutation's +/// block length, 16 by default and 8 for a 64-bit block cipher; it is a parameter because stable +/// Rust cannot read it off the permutation from inside the mode impl, as for `CTR`. The impls +/// below are generic over any [`BlockPermutation`] of that block length. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct CMAC; +pub struct CMAC; + +impl Mode

for CMAC +where + P: BlockPermutation + ModeNames + Clone, +{ + type KeyState = CmacKeyState; + const KEY_TYPE: KeyType = KeyType::MACKey; + + fn key_state(perm: P) -> Self::KeyState { + CmacKeyState::new(perm) + } +} + +impl MacMode

for CMAC +where + P: BlockPermutation + ModeNames + Clone, +{ + type Worker<'k> + = Cmac<'k, P, BLOCK_LEN> + where + P: 'k; + + fn mac_init<'k>(state: &'k Self::KeyState) -> Self::Worker<'k> { + Cmac::from_key_state(state) + } +} /// What a CMAC key keeps per key: the keyed permutation and the subkeys `K1` and `K2` /// (Sec 6.1), derived once from `L = CIPH_K(0^b)` rather than per message. @@ -95,6 +131,16 @@ pub struct CmacKeyState { k2: Secret<[u8; BLOCK_LEN]>, } +// All three fields are compared, joined with `&` rather than `&&` so that the time taken does not +// say which of them differed; see `GcmKeyState` for the expectation on `P`. +impl PartialEq for CmacKeyState { + fn eq(&self, other: &Self) -> bool { + (self.perm == other.perm) & (self.k1 == other.k1) & (self.k2 == other.k2) + } +} + +impl Eq for CmacKeyState {} + impl CmacKeyState { /// The last byte of `R_b` (Sec 5.3): `R_128 = 0^120 || 10000111` and `R_64 = 0^59 || 11011`, /// every other byte zero. Any other block length fails to compile. @@ -104,10 +150,10 @@ impl CmacKeyState { _ => panic!("CMAC is defined for 64- and 128-bit block ciphers only (SP 800-38B Sec 5.3)"), }; - /// Derives the subkeys from an already-keyed permutation (Sec 6.1). - pub fn new(perm: P) -> Self + /// Derives the subkeys from an already-keyed permutation (Sec 6.1): [`CMAC`]'s `Mode` impl. + pub(crate) fn new(perm: P) -> Self where - P: ElectronicCodeBook, + P: BlockPermutation, { // Step 1: L = CIPH_K(0^b). Sec 5.3: L "shall also be secret", so it is computed in place // in a `Secret`. @@ -133,13 +179,13 @@ impl CmacKeyState { } } -/// CMAC (SP 800-38B) over the permutation `P`, keyed by a `KEY_LEN`-byte key, with a `BLOCK_LEN` -/// of 8 or 16 bytes. See the module docs. -pub struct Cmac +/// CMAC (SP 800-38B) over the permutation `P`, with a `BLOCK_LEN` of 8 or 16 bytes. See the +/// module docs. +pub struct Cmac<'k, P, const BLOCK_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - key: CmacKeyState, + key: &'k CmacKeyState, /// `C_i` of Sec 6.2 step 6 for the blocks absorbed so far; `C_0 = 0^b` (step 5). c: Secret<[u8; BLOCK_LEN]>, /// The last `pending_len` bytes of message seen. Held back even when full, because only @@ -149,62 +195,20 @@ where pending_len: usize, } -impl Cmac +impl<'k, P, const BLOCK_LEN: usize> Cmac<'k, P, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Length of the [`SuspendableKeyed`] state: the three-byte library version, `C_i`, the /// pending block, and its length. pub const SUSPENDED_STATE_LEN: usize = 3 + 2 * BLOCK_LEN + 1; - /// Begins a MAC over a new message from a key state, which is cloned into the worker. - pub fn from_key_state(state: &CmacKeyState) -> Self - where - P: Clone, - { - Self::from_owned_state(state.clone()) - } - - fn from_owned_state(key: CmacKeyState) -> Self { + /// Begins a MAC over a new message over a key state, which the worker borrows: the + /// permutation and the subkeys stay in the key. [`CMAC`]'s `MacMode` impl. + pub(crate) fn from_key_state(key: &'k CmacKeyState) -> Self { Self { key, c: Secret::new(), pending: Secret::new(), pending_len: 0 } } - /// Validates `key` and derives the subkeys. The checks run type, then strength, then length, - /// so a short-strength key is reported as such whatever its length. - fn init( - key: &(impl KeyMaterialTrait + ?Sized), - allow_weak_key: bool, - ) -> Result { - if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { - return Err(KeyMaterialError::InvalidKeyType("CMAC: key type must be a MAC key").into()); - } - if !allow_weak_key && key.security_strength() < P::MAX_SECURITY_STRENGTH { - return Err(KeyMaterialError::SecurityStrength( - "CMAC: provided key has a lower security strength than the block cipher", - ) - .into()); - } - if key.key_len() != KEY_LEN { - return Err(KeyMaterialError::InvalidLength.into()); - } - - // `P::new` accepts only a full-strength `SymmetricCipherKey`. The MAC key's type, strength - // and length were checked above, so this copy is retagged to pass that gate and is used for - // nothing else; `P::new` then has nothing left to reject. - let mut cipher_key = KeyMaterial::::from_bytes_as_type( - key.ref_to_bytes(), - KeyType::SymmetricCipherKey, - )?; - do_hazardous_operations(&mut cipher_key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(P::MAX_SECURITY_STRENGTH) - })?; - let perm = P::new(&cipher_key) - .map_err(|_| MACError::GenericError("CMAC: the block cipher rejected the key"))?; - - Ok(Self::from_owned_state(CmacKeyState::new(perm))) - } - /// Sec 6.2 step 6 for one block: `C_i = CIPH_K(C_{i-1} (+) M_i)`, in place in `c`. /// /// An associated function over the fields rather than a `&mut self` method, so that `pending` @@ -234,31 +238,18 @@ where } } -impl Algorithm for Cmac +impl<'k, P, const BLOCK_LEN: usize> Algorithm for Cmac<'k, P, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const ALG_NAME: &'static str = P::CMAC_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl MAC for Cmac +impl<'k, P, const BLOCK_LEN: usize> MAC for Cmac<'k, P, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// # Errors - /// [`KeyMaterialError::InvalidKeyType`] unless the key is a [`KeyType::MACKey`] (or all-zero), - /// [`KeyMaterialError::SecurityStrength`] if it is weaker than the block cipher, and - /// [`KeyMaterialError::InvalidLength`] unless it is exactly `KEY_LEN` bytes. - fn new(key: &impl KeyMaterialTrait) -> Result { - Self::init(key, false) - } - - /// As [`MAC::new`], without the security strength check. - fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - Self::init(key, true) - } - fn output_len(&self) -> usize { BLOCK_LEN } @@ -332,18 +323,17 @@ where } } -/// The state is `C_i` and the pending block; the subkeys are recomputed from the re-supplied key, -/// which must be the one the suspended `Cmac` was created with -- a different key cannot be -/// detected and silently produces a wrong tag. As for HMAC, the key's security strength is not -/// re-checked on resume. +/// The state is `C_i` and the pending block; the subkeys are the re-supplied key state's, which +/// must be the one the suspended `Cmac` was created with -- a different key cannot be detected +/// and silently produces a wrong tag. /// /// `STATE_LEN` must be [`Cmac::SUSPENDED_STATE_LEN`]; any other value fails to compile. -impl - SuspendableKeyed for Cmac +impl<'k, P, const BLOCK_LEN: usize, const STATE_LEN: usize> SuspendableKeyed<'k, STATE_LEN> + for Cmac<'k, P, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = dyn KeyMaterialTrait; + type Key = CmacKeyState; fn suspend(self) -> [u8; STATE_LEN] { const { @@ -363,9 +353,11 @@ where /// # Errors /// [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library version, - /// and [`SuspendableError::InvalidData`] for a malformed state or a key [`MAC::new_allow_weak_key`] - /// would reject. - fn from_suspended(state: [u8; STATE_LEN], key: &Self::Key) -> Result { + /// and [`SuspendableError::InvalidData`] for a malformed state. + fn from_suspended( + state: [u8; STATE_LEN], + key: &'k Self::Key, + ) -> Result { const { assert!( STATE_LEN == 3 + 2 * BLOCK_LEN + 1, @@ -377,7 +369,7 @@ where if pending_len > BLOCK_LEN { return Err(SuspendableError::InvalidData); } - let mut cmac = Self::init(key, true).map_err(|_| SuspendableError::InvalidData)?; + let mut cmac = Self::from_key_state(key); cmac.c.copy_from_slice(&body[..BLOCK_LEN]); cmac.pending.copy_from_slice(&body[BLOCK_LEN..2 * BLOCK_LEN]); cmac.pending_len = pending_len; diff --git a/crypto/cipher/src/modes/ctr.rs b/crypto/cipher/src/modes/ctr.rs index 4c49449c3..da2c639e9 100644 --- a/crypto/cipher/src/modes/ctr.rs +++ b/crypto/cipher/src/modes/ctr.rs @@ -25,7 +25,7 @@ //! performed in parallel". Counter blocks depend on nothing but the nonce and the index, so unlike //! CBC and CFB there is no feed-forward between blocks at all. //! As such, both directions walk the block-aligned part of the data in fours through -//! [`ElectronicCodeBook::encrypt_4blocks`], then in pairs through [`ElectronicCodeBook::encrypt_2blocks`]. +//! [`BlockPermutation::encrypt_4blocks`], then in pairs through [`BlockPermutation::encrypt_2blocks`]. //! Only a leftover single block, and the keystream block for a short tail at the end, go one block at a time. //! //! Like the rest of CFB and CTR, only the **forward** cipher function is ever used, in both @@ -41,24 +41,26 @@ //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; -//! use bouncycastle_cipher::modes::Ctr; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // A 12-byte nonce, leaving a 4-byte counter. -//! type ToyCtr

= Ctr; +//! use bouncycastle_cipher::modes::CTR; +//! use bouncycastle_rng::HashDRBG_SHA512; //! //! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! // `CTR`'s defaults: a 12-byte nonce, leaving a 4-byte counter on a 16-byte block. +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); //! //! let plaintext = b"the quick brown fox!!"; //! let mut data = *plaintext; //! -//! let (bytes_written, nonce) = ToyCtr::::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (mut encryptor, nonce) = +//! CTR::<12, 16>::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()).expect("nonce draw"); +//! let bytes_written = encryptor.do_encrypt_inplace(&mut data).expect("encryption"); //! assert_eq!(bytes_written, plaintext.len()); //! -//! ToyCtr::::decrypt_inplace(&key, &nonce, &mut data).expect("decryption"); +//! CTR::<12, 16>::decrypt_init(&state, &nonce).do_decrypt_inplace(&mut data).expect("decryption"); //! assert_eq!(data, *plaintext); //! ``` //! @@ -67,20 +69,19 @@ //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{ -//! StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor -//! }; -//! use bouncycastle_cipher::modes::Ctr; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyCtr = Ctr; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_cipher::modes::CTR; +//! use bouncycastle_rng::HashDRBG_SHA512; //! //! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); //! let mut data = [0x5Au8; 40]; //! -//! let (mut encryptor, nonce) = ToyCtr::::do_encrypt_init(&key).expect("init"); +//! let (mut encryptor, nonce) = +//! CTR::<12, 16>::encrypt_init(&state, &mut HashDRBG_SHA512::new_from_os()).expect("nonce draw"); //! //! // Just to prove that this can handle arbitrary sizes, we'll feed in //! // 7 bytes, then 33: neither is a whole block. @@ -91,7 +92,7 @@ //! assert_eq!(bytes_written, 33); //! //! // Decrypting in a different chunking must also agree. -//! let mut decryptor = ToyCtr::::do_decrypt_init(&key, &nonce).expect("init"); +//! let mut decryptor = CTR::<12, 16>::decrypt_init(&state, &nonce); //! decryptor.do_decrypt_inplace(&mut data[..19]).expect("first chunk"); //! decryptor.do_decrypt_inplace(&mut data[19..]).expect("the rest"); //! assert_eq!(data, [0x5Au8; 40]); @@ -102,7 +103,7 @@ //! [`Ctr`] implements [`SuspendableKeyed`](bouncycastle_core::traits::SuspendableKeyed), so a //! message in progress can be suspended to a byte array and resumed later with the re-supplied key. //! The state is the nonce, the next counter value and the partly used keystream block; the -//! permutation is rebuilt from the key. The array length is `Ctr::SUSPENDED_STATE_LEN`; see [the +//! permutation is the key's, re-supplied by reference. The array length is `Ctr::SUSPENDED_STATE_LEN`; see [the //! crate docs](crate#suspending-and-resuming-execution) for an example. //! //! # 🚨 Security Considerations 🚨 @@ -121,23 +122,84 @@ use crate::modes::ModeNames; use crate::modes::hazmat::ctr_key_stream::CtrKeyStream; +use crate::modes::iv::random_iv; use crate::stream::StreamCipher; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use crate::{Decrypting, Encrypting}; +use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::hazmat::{ + BlockPermutation, ElectronicCodeBook, Mode, SymmetricDecryptMode, SymmetricEncryptMode, +}; +use bouncycastle_core::key_material::KeyType; +use bouncycastle_core::traits::RNG; use bouncycastle_utils::secret::Secret; // Imports needed for docs #[allow(unused_imports)] -use bouncycastle_core::errors::SymmetricCipherError; -#[allow(unused_imports)] use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; // end of imports needed for docs +/// The CTR mode type: the `M` of a mode-bound key such as `AES128Key`, on the symmetric key +/// path with [`Ctr`] as its worker. `NONCE_LEN` is the init data length, `BLOCK_LEN - NONCE_LEN` +/// the counter width; the defaults are a 12-byte nonce on a 16-byte block, the `AES_CTR_*` +/// aliases' shape. The block length is a parameter because the nonce does not fix it, and +/// stable Rust cannot tie it to the permutation's from inside the mode impl. Its key state is the +/// keyed permutation alone. +pub struct CTR; + +impl Mode

+ for CTR +{ + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl + SymmetricEncryptMode for CTR +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Ctr<'k, P, Encrypting, BLOCK_LEN, NONCE_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + let nonce = random_iv::(rng)?; + Ok((StreamCipher::from_keystream(CtrKeyStream::from_perm(state, nonce)), nonce)) + } +} + +impl + SymmetricDecryptMode for CTR +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Ctr<'k, P, Decrypting, BLOCK_LEN, NONCE_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; NONCE_LEN], + ) -> Self::Decryptor<'k> { + StreamCipher::from_keystream(CtrKeyStream::from_perm(state, *init_data)) + } +} + /// CTR mode over any [`ElectronicCodeBook`] permutation function, with the direction encoded in the type: the /// crate-private CTR keystream, `CtrKeyStream`, wrapped in a [`StreamCipher`]. /// /// The counter block is the init data (the nonce) followed by a counter filling the rest of the /// block, so `INIT_DATA_LEN` chooses the counter length; see the module docs. `Dir` is -/// [`Encrypting`](crate::Encrypting) or [`Decrypting`](crate::Decrypting). +/// [`Encrypting`] or [`Decrypting`]. /// /// # The counter width is checked at compile time /// @@ -145,48 +207,46 @@ use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// 13, 14 or 15 bytes. Both bounds are inline `const` assertions in the constructors, so a nonce /// length outside that range is a **compile** error at the call site rather than a runtime `Err`. /// -/// The permitted lengths all work: +/// The permitted lengths all work, here through the mode type [`CTR`] a key is bound to: /// /// ``` /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } /// # use toy::ToyBlockCipher; +/// use bouncycastle_core::hazmat::{ElectronicCodeBook, Mode, SymmetricEncryptMode}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; -/// use bouncycastle_cipher::modes::Ctr; -/// use bouncycastle_cipher::Encrypting; +/// use bouncycastle_cipher::modes::CTR; +/// use bouncycastle_rng::HashDRBG_SHA512; /// /// 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 perm = ToyBlockCipher::new(&key).unwrap(); +/// let mut rng = HashDRBG_SHA512::new_from_os(); +/// // 4-byte counter +/// let _ = CTR::<12>::encrypt_init(& as Mode>::key_state(perm.clone()), &mut rng).unwrap(); +/// // 1-byte counter +/// let _ = CTR::<15>::encrypt_init(& as Mode>::key_state(perm), &mut rng).unwrap(); /// ``` -pub type Ctr = - StreamCipher< - CtrKeyStream, - Dir, - KEY_LEN, - INIT_DATA_LEN, - BLOCK_LEN, - >; +pub type Ctr<'k, P, Dir, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> = + StreamCipher, Dir, INIT_DATA_LEN, BLOCK_LEN>; /// XORs `Oj = CIPH_K(Tj)` into `blocks` for the next `blocks.len()` counter values, starting at /// `*next` and advancing it past them, where `Tj = counter_block(j)`. /// /// Shared by [`CtrKeyStream`] and CCM's keystream (SP 800-38C Sec 6.1 steps 5-7), which differ /// only in how a counter block is formatted. Walks the blocks in fours through -/// [`ElectronicCodeBook::encrypt_4blocks`], then pairs, then a single block: the counter blocks +/// [`BlockPermutation::encrypt_4blocks`], then pairs, then a single block: the counter blocks /// depend only on `j`, not on the data or on each other's cipher output, so the forward ciphers in /// a batch are independent. This is the parallelism SP 800-38A Sec 6.5 describes, and it applies /// to both directions. /// /// The keystream scratch is one [`Secret`] per width and per call rather than per batch, so every /// block of `Oj` is zeroized when this returns. -pub(crate) fn apply_counter_blocks( +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: BlockPermutation + ModeNames, { let (fours, rest) = blocks.as_chunks_mut::<4>(); let mut ks4: Secret<[[u8; BLOCK_LEN]; 4]> = Secret::new(); @@ -209,7 +269,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 +277,7 @@ fn apply_batch( keystream: &mut [[u8; BLOCK_LEN]; N], batch: impl Fn(&P, &mut [[u8; BLOCK_LEN]; N]), ) where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { 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 4de0605c1..9a2f6823c 100644 --- a/crypto/cipher/src/modes/gcm.rs +++ b/crypto/cipher/src/modes/gcm.rs @@ -5,9 +5,8 @@ //! //! [`Gcm`] fixes the nonce at 96 bits (12 bytes, [`GCM_NONCE_LEN`]): SP 800-38D Sec 5.2.1.1 //! recommends that implementations "restrict support to the length of 96 bits", and the other IV -//! lengths are not implemented. The nonce is never taken from the caller: instead -//! [`SymmetricCipherEncryptor::do_encrypt_init`] and [`SymmetricCipherEncryptor::do_encrypt_init_rng`] -//! draw it from the default OS RNG or the provided RNG, respectively. +//! lengths are not implemented. The nonce is drawn from the RNG the key is given, or supplied +//! through the `_nonce` forms, which put its uniqueness on the caller. //! //! The tag length is a const generic `TAG_LEN`, checked at compile time to lie in `12..=16` bytes //! (96, 104, 112, 120 or 128 bits -- Sec 5.2.1.2's five recommended values). The 32- and 64-bit tags @@ -15,100 +14,127 @@ //! //! # Usage Examples //! -//! [`Gcm`] is used through [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`], with -//! `FINAL_LEN = TAG_LEN`, and through the [`SymmetricCipherEncryptor`] / -//! [`SymmetricCipherDecryptor`] traits they extend: +//! [`Gcm`] is the worker a mode-bound key hands out -- [`GCM`] on a key type such as +//! `bouncycastle_aes::AES128Key`, whose AEAD role traits carry the one-shots -- and it +//! implements [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] with `FINAL_LEN = TAG_LEN`, and the +//! [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] traits they extend. The key holds a +//! [`GcmKeyState`] and starts each message from it; over the toy permutation the key is the test +//! suite's `ToyKey`, the shape of the AES one. //! -//! Used through the SymmetricCipher traits, there is no option to include additional associated data (aad), -//! and the tag is inlined into the ciphertext as `ciphertext || tag`. +//! Through the SymmetricCipher methods there is no additional associated data (aad), and the tag +//! is inlined into the ciphertext as `ciphertext || tag`: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; +//! # use toy_key::ToyKey; +//! use bouncycastle_core::errors::SymmetricCipherError; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; //! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; -//! use bouncycastle_core::errors::SymmetricCipherError; -//! use bouncycastle_cipher::modes::Gcm; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_cipher::modes::GCM; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! type ToyGcm

= Gcm; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); -//! let aad = b"header, sent in the clear"; +//! let key = ToyKey::::from_key_material(&material).expect("a valid key"); //! let plaintext: [u8; 16] = *b"attack at dawn!!"; //! -//! let (nonce, ciphertext) = ToyGcm::::encrypt(&key, &plaintext).expect("encrypt"); +//! let (mut enc, nonce) = +//! key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).expect("nonce draw"); +//! let mut ciphertext = vec![0u8; 16 + 16]; +//! let n = enc.do_encrypt_out(&plaintext, &mut ciphertext).expect("encrypt"); +//! let (tag, tag_len) = enc.do_encrypt_final().expect("the tag"); +//! ciphertext[n..].copy_from_slice(&tag[..tag_len]); //! -//! let mut recovered = ToyGcm::::decrypt(&key, &nonce, &ciphertext).expect("decrypt"); -//! assert_eq!(recovered, plaintext); +//! let open = |ct: &[u8]| -> Result, SymmetricCipherError> { +//! let mut dec = key.create_decryptor(&nonce); +//! let mut pt = vec![0u8; ct.len()]; +//! let n = dec.do_decrypt_out(ct, &mut pt)?; +//! let (last, last_len) = dec.do_decrypt_final()?; +//! pt.truncate(n); +//! pt.extend_from_slice(&last[..last_len]); +//! Ok(pt) +//! }; +//! assert_eq!(open(&ciphertext).expect("decrypt"), plaintext); //! //! // A tampered ciphertext will be caught by the tag //! let mut tampered_ct = ciphertext.clone(); //! tampered_ct[1] ^= 0xFF; -//! match ToyGcm::::decrypt(&key, &nonce, &tampered_ct).unwrap_err() { -//! SymmetricCipherError::AEADTagCheckFailed => { /* good */ } -//! _ => { panic!() } -//! } +//! assert_eq!(open(&tampered_ct).unwrap_err(), SymmetricCipherError::AEADTagCheckFailed); //! ``` //! -//! The AEADCipher traits provide the AEAD-specific functionality, including accepting the aad, and -//! the `_detached()` methods handle the tag separately, instead of inlined into the ciphertext. +//! The AEADCipher methods provide the AEAD-specific functionality, including accepting the aad, +//! and the `_detached` finals handle the tag separately, instead of inlined into the ciphertext. +//! The aad must be supplied before any plaintext or ciphertext; attempting to call +//! `do_update_aad()` after a `do_encrypt()` will result in a [`SymmetricCipherError::StateError`]. //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; -//! use bouncycastle_cipher::modes::Gcm; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyGcm = Gcm; +//! # use toy_key::ToyKey; +//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::traits::{ +//! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +//! }; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_cipher::modes::GCM; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); -//! let aad = b"header, sent in the clear"; -//! let plaintext = *b"attack at dawn!!"; +//! let key = ToyKey::::from_key_material(&material).expect("a valid key"); +//! let aad = b"some associated data"; +//! let message = b"a message that streams in over more than one call"; //! -//! let mut ciphertext = [0u8; 16]; -//! let (nonce, _bytes_written, tag) = -//! ToyGcm::::encrypt_detached_out(&key, aad, &plaintext, &mut ciphertext).unwrap(); +//! let (mut enc, nonce) = key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).unwrap(); +//! enc.do_update_aad(aad).unwrap(); +//! let mut ct = vec![0u8; message.len()]; +//! enc.do_encrypt_out(message, &mut ct).unwrap(); +//! let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; 16]).unwrap(); //! -//! let mut recovered = [0u8; 16]; -//! ToyGcm::::decrypt_detached_out(&key, &nonce, aad, &ciphertext, &tag, &mut recovered) -//! .unwrap(); -//! assert_eq!(recovered, plaintext); +//! let mut dec = key.create_decryptor(&nonce); +//! dec.do_update_aad(aad).unwrap(); +//! let mut pt = vec![0u8; ct.len()]; +//! let written = dec.do_decrypt_out(&ct, &mut pt).unwrap(); +//! let mut last = [0u8; 16]; +//! let last_len = dec.do_decrypt_final_detached_out(&tag, &mut last).unwrap(); +//! pt.truncate(written); +//! pt.extend_from_slice(&last[..last_len]); +//! assert_eq!(pt, message); //! ``` //! -//! There is also a streaming mode. -//! Note that the aad must be supplied before any plaintext or ciphertext; attempting to call -//! `do_update_aad()` after a `do_encrypt()` will result in a [`SymmetricCipherError::StateError`]. +//! The same with an inline tag: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; +//! # use toy_key::ToyKey; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{ //! AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, //! }; -//! use bouncycastle_cipher::modes::Gcm; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyGcm = Gcm; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_cipher::modes::GCM; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) +//! let material = KeyMaterial::<16>::from_bytes_as_type(&[0x07; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); +//! let key = ToyKey::::from_key_material(&material).expect("a valid key"); //! let aad = b"some associated data"; //! let message = b"a message that streams in over more than one call"; //! -//! let (mut enc, nonce) = ToyGcm::::do_encrypt_init(&key).unwrap(); +//! let (mut enc, nonce) = key.create_encryptor(&mut HashDRBG_SHA512::new_from_os()).unwrap(); //! enc.do_update_aad(aad).unwrap(); //! let mut ct = vec![0u8; message.len()]; //! enc.do_encrypt_out(message, &mut ct).unwrap(); //! let (tag_block, tag_len) = enc.do_encrypt_final().unwrap(); //! ct.extend_from_slice(&tag_block[..tag_len]); //! -//! let mut dec = ToyGcm::::do_decrypt_init(&key, &nonce).unwrap(); +//! let mut dec = key.create_decryptor(&nonce); //! dec.do_update_aad(aad).unwrap(); //! let mut pt = vec![0u8; ct.len()]; //! let written = dec.do_decrypt_out(&ct, &mut pt).unwrap(); @@ -120,9 +146,10 @@ //! # Suspending and resuming execution //! //! [`Gcm`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a byte -//! array and resumed later with the re-supplied key. The state is the CTR half, the running GHASH, -//! the byte counts and whatever a decryptor is holding back as a possible tag; `H` and the tag mask -//! are re-derived from the key. The array length is `Gcm::SUSPENDED_STATE_LEN`; see [the crate +//! array and resumed later over the re-supplied key state. The state is the CTR half, the running +//! GHASH, the byte counts, the phase with the direction it was suspended in, and whatever a +//! decryptor is holding back as a possible tag; `H` is the key state's and the tag mask is +//! re-derived from it. A state resumes only in its own direction: the other type refuses it. The array length is `Gcm::SUSPENDED_STATE_LEN`; see [the crate //! docs](crate#suspending-and-resuming-execution) for an example. //! //! # 🚨 Security Considerations 🚨 @@ -130,8 +157,9 @@ //! ## Nonce uniqueness //! //! As with all symmetric cipher modes, repeated key and nonce for multiple messages is catastrophic -//! for security, which is why the nonce is always drawn from the library's default RNG or a provided -//! RNG and never accepted from the caller. +//! for security, which is why the key's `create_encryptor` draws the nonce from the caller's RNG. +//! The forms that take a supplied nonce, `create_encryptor_nonce` and the `_nonce` one-shots on +//! the key, leave uniqueness to the caller. //! //! ## Invocation limit //! @@ -158,8 +186,8 @@ //! the end of the ciphertext has been reached, and the `do_decrypt_final` / //! `do_decrypt_final_detached` succeeds. //! -//! The one-shots (`decrypt_out`, `decrypt_detached_out`, `decrypt_with_aad_out`) verify the -//! tag first and release nothing on failure, making them more robust. +//! The one-shots (`decrypt_out`, `decrypt_detached_out`, `decrypt_with_aad_out`) zeroize their +//! output buffer when the tag check fails, so nothing is released on failure. //! //! * **GMAC is GCM with no plaintext** (Sec 5.2): [`Gmac`](crate::modes::gmac) is this type fed //! only AAD, with the plaintext-free API. @@ -167,16 +195,17 @@ use crate::modes::Ctr; use crate::modes::ModeNames; use crate::modes::hazmat::ctr_key_stream::CtrKeyStream; -use crate::{Decrypting, Encrypting}; +use crate::{Decrypting, Direction, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::hazmat::{ + AeadDecryptMode, AeadEncryptMode, BlockPermutation, ElectronicCodeBook, Mode, +}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::ct::ct_eq_bytes; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ @@ -189,29 +218,101 @@ use ghash::{GHASH_STATE_LEN, Ghash}; /// The nonce (IV) length this type uses: 96 bits, SP 800-38D Sec 5.2.1.1's recommended length. pub const GCM_NONCE_LEN: usize = 12; +/// Sec 5.2.1.1's `len(A) <= 2^64 - 1` bits, in bytes. +const MAX_AAD_LEN: u64 = (1 << 61) - 1; + +/// Sec 5.2.1.1's `len(P) <= 2^39 - 256` bits, in bytes: the `2^32 - 2` counter blocks left once +/// `J0` and `inc32(J0)` are taken, which is also where the keystream runs out. +const MAX_DATA_LEN: u64 = (1 << 36) - 32; + /// The GCM mode type: the `M` of a mode-bound key such as `AES128Key`. `GCM` is GCM with /// the full 128-bit tag; `GCM<12>` and so on select a shorter tag, within the `12..=16` byte range -/// [`Gcm`] accepts. The [`Mode`] and AEAD mode-trait impls that connect it to a key type are -/// written by each permutation's crate while the cipher traits still carry `KEY_LEN`. -/// -/// [`Mode`]: bouncycastle_core::hazmat::Mode +/// [`Gcm`] accepts. The [`Mode`] and AEAD mode-trait impls below are generic over any +/// [`BlockPermutation`] with a 128-bit block, so a permutation crate's key type needs nothing of +/// its own. pub struct GCM; +impl Mode

for GCM +where + P: BlockPermutation<16> + ModeNames + Clone, +{ + type KeyState = GcmKeyState

; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + GcmKeyState::new(perm) + } +} + +impl + AeadEncryptMode for GCM +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Gcm<'k, P, Encrypting, TAG_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + Gcm::::from_key_state(state, rng) + } + + fn encrypt_init_nonce<'k>( + state: &'k Self::KeyState, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self::Encryptor<'k> { + Gcm::::from_key_state_nonce(state, nonce) + } +} + +impl + AeadDecryptMode for GCM +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Gcm<'k, P, Decrypting, TAG_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self::Decryptor<'k> { + Gcm::::from_key_state(state, nonce) + } +} + /// What a GCM key keeps per key: the keyed permutation and the hash subkey `H = CIPH_K(0^128)` /// (SP 800-38D Algorithm 4 step 1), derived once here rather than once per message. It is the -/// `KeyState` of [`GCM`] over any permutation, and is only built and consumed through the mode -/// impls. +/// `KeyState` of [`GCM`] over any permutation, built and consumed through the mode impls; a +/// key object reaches it only to resume a suspended worker. #[derive(Clone)] pub struct GcmKeyState

{ perm: P, h: Secret<[u8; 16]>, } +// Both fields are compared, joined with `&` rather than `&&` so that the time taken does not say +// which of them differed. `P`'s equality is expected to be constant-time, as a keyed permutation's +// must be; `H`'s is `Secret`'s. +impl PartialEq for GcmKeyState

{ + fn eq(&self, other: &Self) -> bool { + (self.perm == other.perm) & (self.h == other.h) + } +} + +impl Eq for GcmKeyState

{} + impl

GcmKeyState

{ /// Derives `H` from `perm`. - pub fn new(perm: P) -> Self + pub(crate) fn new(perm: P) -> Self where - P: ElectronicCodeBook, + P: BlockPermutation<16>, { // Encrypted in place inside a `Secret` so that `H` is never held in an unzeroized stack // array (Sec 5.3; Appendix A on what `H` gives an attacker). @@ -243,15 +344,16 @@ impl Phase { /// Galois/Counter Mode over any [`ElectronicCodeBook`] permutation, direction typed as /// [`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. +/// [`GCM_NONCE_LEN`] / `TAG_LEN` for what is fixed and what is chosen. A worker borrows the key +/// state it was made from for `'k`: the schedule and `H` stay in the key. #[derive(Clone)] -pub struct Gcm +pub struct Gcm<'k, P, Dir, const TAG_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { /// `GCTR_K(inc32(J0), .)`: Algorithm 4 step 3 / Algorithm 5 step 4, started at counter 2 (see /// [`Gcm::setup`]). - ctr: Ctr, + ctr: Ctr<'k, P, Dir, 16, GCM_NONCE_LEN>, /// `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)`, @@ -273,19 +375,23 @@ where _dir: PhantomData

, } -impl Gcm +impl<'k, P, Dir, const TAG_LEN: usize> Gcm<'k, P, Dir, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the CTR state, + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] impl: the version header, the CTR state, /// the GHASH state, the two byte counts, the phase, the held-back tail and its length. See /// [`bouncycastle_utils::suspendable_state`]. - pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + pub const SUSPENDED_STATE_LEN: usize = LIB_VERSION_LEN + Self::COMPONENT_STATE_LEN; + + /// The component's share, spelled out here rather than read through the trait so that it is + /// nameable for any `Dir`; the trait impl's `STATE_LEN` is this constant. + const COMPONENT_STATE_LEN: usize = + Self::CTR_STATE_LEN + GHASH_STATE_LEN + 8 + 8 + 1 + TAG_LEN + 8; /// The CTR half's share of the suspended state. const CTR_STATE_LEN: usize = - as SuspendableComponent>::STATE_LEN; + as SuspendableComponent<'k>>::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 @@ -301,14 +407,9 @@ where }; } - /// Algorithm 4 steps 1-2 and the precomputation for step 6's tag mask, from a bare permutation. - fn setup(perm: P, nonce: [u8; GCM_NONCE_LEN]) -> Self { - Self::setup_from_state(GcmKeyState::new::(perm), nonce) - } - /// Algorithm 4 step 2 and the precomputation for step 6's tag mask, over a key state that - /// already holds step 1's `H`. - fn setup_from_state(state: GcmKeyState

, nonce: [u8; GCM_NONCE_LEN]) -> Self { + /// already holds step 1's `H`, which the worker borrows for `'k`. + fn setup_from_state(state: &'k GcmKeyState

, nonce: [u8; GCM_NONCE_LEN]) -> Self { Self::check_shape(); let GcmKeyState { perm, h } = state; @@ -328,7 +429,7 @@ where Self { ctr, - ghash: Ghash::new(&h), + ghash: Ghash::new(h), ek_j0, aad_len: 0, data_len: 0, @@ -353,14 +454,36 @@ where ciphertext (SP 800-38D Algorithm 4 absorbs A before C in one GHASH pass)", )); } + self.aad_len = match self.aad_len.checked_add(aad.len() as u64) { + Some(total) if total <= MAX_AAD_LEN => total, + _ => { + return Err(SymmetricCipherError::StateError( + "GCM: additional authenticated data longer than SP 800-38D Sec 5.2.1.1's \ + 2^64 - 1 bits", + )); + } + }; self.ghash.update(aad); - self.aad_len = - self.aad_len.checked_add(aad.len() as u64).ok_or(SymmetricCipherError::StateError( - "GCM: additional authenticated data length exceeds the supported range", - ))?; Ok(()) } + /// `data_len` after `len` more bytes, if that is still within Sec 5.2.1.1's plaintext limit. + fn data_len_after(&self, len: usize) -> Result { + match self.data_len.checked_add(len as u64) { + Some(total) if total <= MAX_DATA_LEN => Ok(total), + _ => Err(SymmetricCipherError::DataLimitExceeded), + } + } + + /// Whether `len` more data bytes can be taken: within Sec 5.2.1.1's plaintext limit and the + /// keystream left under this nonce. Both directions ask before they copy anything into an + /// output buffer, so a refused call leaves the buffer zeroed rather than holding the + /// caller's plaintext, and before they absorb anything, so it leaves the state untouched. + fn accepts_data(&self, len: usize) -> Result<(), SymmetricCipherError> { + self.data_len_after(len)?; + self.ctr.accepts(len) + } + /// The AAD-to-data transition: pads the AAD to a block boundary (the `0^v` of step 5) the /// first time data arrives. A no-op on every later call. fn begin_data_if_needed(&mut self) { @@ -375,11 +498,9 @@ where /// plaintext into ciphertext in place) and the decryptor (which calls this *before* GCTR turns /// the ciphertext back into plaintext): either way GHASH must see ciphertext, never plaintext. fn absorb_data(&mut self, data: &[u8]) -> Result<(), SymmetricCipherError> { + self.data_len = self.data_len_after(data.len())?; self.begin_data_if_needed(); self.ghash.update(data); - self.data_len = self.data_len.checked_add(data.len() as u64).ok_or( - SymmetricCipherError::StateError("GCM: data length exceeds the supported range"), - )?; Ok(()) } @@ -389,10 +510,8 @@ where /// because on the decrypting side it is the expected tag `T'`, which forges the rejected /// ciphertext if it survives a failed comparison. /// - /// The byte-to-bit multiplication (`* 8`) is not checked for overflow: `aad_len` and `data_len` - /// are accumulated with `checked_add` at every absorption (`absorb_aad`, `absorb_data`), so - /// reaching a count whose `* 8` could overflow `u64` would already require far more calls than - /// are physically possible to make. + /// The byte-to-bit multiplication (`* 8`) cannot overflow: `aad_len` and `data_len` are held + /// under [`MAX_AAD_LEN`] and [`MAX_DATA_LEN`] at every absorption and on resume. fn tag_block(&mut self, out: &mut Secret<[u8; 16]>) { self.ghash.pad_to_block(); let aad_bits = self.aad_len * 8; @@ -404,56 +523,51 @@ where } } -impl Algorithm for Gcm +impl<'k, P, Dir, const TAG_LEN: usize> Algorithm for Gcm<'k, P, Dir, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { const ALG_NAME: &'static str = P::GCM_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl Gcm +impl<'k, P, const TAG_LEN: usize> Gcm<'k, P, Encrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - /// Begins an encryption from a key's stored state under a nonce drawn from `rng`: the - /// encryption constructor a permutation crate's `AeadEncryptMode` impl for [`GCM`] calls. The - /// result is identical to [`SymmetricCipherEncryptor::do_encrypt_init_rng`] from the same key - /// and RNG. + /// Begins an encryption over a key's stored state under a nonce drawn from `rng`: the + /// encryption constructor [`GCM`]'s `AeadEncryptMode` impl calls. Nothing is copied out of + /// `state`; the worker reads the schedule and `H` through the borrow and cannot outlive it. /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - pub fn from_key_state( - state: &GcmKeyState

, + pub(crate) fn from_key_state( + state: &'k GcmKeyState

, rng: &mut dyn RNG, - ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> - where - P: Clone, - { + ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { Self::check_shape(); let nonce = crate::modes::iv::random_iv::(rng)?; - Ok((Self::setup_from_state(state.clone(), nonce), nonce)) + Ok((Self::setup_from_state(state, nonce), nonce)) } - /// As [`Self::from_key_state`] under a nonce the caller supplies: the constructor a - /// permutation crate's `AeadEncryptMode::encrypt_init_nonce` impl for [`GCM`] calls. The - /// result is identical to [`AEADCipherEncryptor::do_encrypt_init_nonce`] from the same key. - pub fn from_key_state_nonce(state: &GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self - where - P: Clone, - { - Self::setup_from_state(state.clone(), *nonce) + /// As [`Self::from_key_state`] under a nonce the caller supplies: the constructor [`GCM`]'s + /// `AeadEncryptMode::encrypt_init_nonce` impl calls. + pub(crate) fn from_key_state_nonce( + state: &'k GcmKeyState

, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self { + Self::setup_from_state(state, *nonce) } /// Encrypts `data` in place (GCTR, Algorithm 4 step 3) and absorbs the resulting ciphertext /// into GHASH (step 5). Nothing is held back. /// /// # Errors - /// [`SymmetricCipherError::DataLimitExceeded`] if the underlying `Ctr` counter would be - /// exhausted -- the SP 800-38D Sec 5.2.1.1 bound `len(P) <= 2^39 - 256` bits -- or - /// [`SymmetricCipherError::StateError`] if the AAD/data length bookkeeping would overflow. - /// Nothing is consumed in either case. + /// [`SymmetricCipherError::DataLimitExceeded`] if `data` would take the message past the + /// SP 800-38D Sec 5.2.1.1 bound `len(P) <= 2^39 - 256` bits, which is where the `Ctr` + /// counter would be exhausted. Nothing is consumed in that case. fn encrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { + self.accepts_data(data.len())?; self.ctr.do_encrypt_inplace(data)?; self.absorb_data(data) } @@ -471,29 +585,11 @@ where } } -impl - SymmetricCipherEncryptor - for Gcm +impl<'k, P, const TAG_LEN: usize> SymmetricCipherEncryptor + for Gcm<'k, P, Encrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { - Self::check_shape(); - let perm = P::new(key)?; - let nonce = crate::modes::iv::random_iv::(rng)?; - Ok((Self::setup(perm, nonce), nonce)) - } - /// The identity: GCM's encryptor holds nothing back. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -508,6 +604,7 @@ where if ciphertext.len() < plaintext.len() { return Err(SymmetricCipherError::OutputBufferTooSmall(plaintext.len())); } + self.accepts_data(plaintext.len())?; ciphertext[..plaintext.len()].copy_from_slice(plaintext); self.encrypt_in_place(&mut ciphertext[..plaintext.len()])?; Ok(plaintext.len()) @@ -526,28 +623,15 @@ 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<'k, P, const TAG_LEN: usize> AEADCipherEncryptor + for Gcm<'k, P, Encrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.absorb_aad(aad) } - /// [`do_encrypt_init_rng`](SymmetricCipherEncryptor::do_encrypt_init_rng) with the caller's - /// 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, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Result { - Self::check_shape(); - let perm = P::new(key)?; - Ok(Self::setup(perm, *nonce)) - } - /// Algorithm 4 steps 4-6; nothing is held back, so `ciphertext` is left zeroed. fn do_encrypt_final_detached_out( self, @@ -558,18 +642,15 @@ where } } -impl Gcm +impl<'k, P, const TAG_LEN: usize> Gcm<'k, P, Decrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { /// Begins the decryption of a message encrypted under `nonce` from a key's stored state: the - /// decryption constructor a permutation crate's `AeadDecryptMode` impl for [`GCM`] calls. - /// Infallible: the state is already keyed and the nonce length is in the type. - pub fn from_key_state(state: &GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self - where - P: Clone, - { - Self::setup_from_state(state.clone(), *nonce) + /// decryption constructor [`GCM`]'s `AeadDecryptMode` impl calls. Infallible: the state is + /// already keyed and the nonce length is in the type. + pub(crate) fn from_key_state(state: &'k GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self { + Self::setup_from_state(state, *nonce) } /// Absorbs `data` (ciphertext) into GHASH, then decrypts it in place. Order matters and is the @@ -582,6 +663,7 @@ where /// # Errors /// As `encrypt_in_place`. fn decrypt_in_place(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { + self.accepts_data(data.len())?; self.absorb_data(data)?; self.ctr.do_decrypt_inplace(data)?; Ok(()) @@ -603,50 +685,13 @@ where Err(SymmetricCipherError::AEADTagCheckFailed) } } - - /// Shared by the trait one-shots (`decrypt_out`, `decrypt_detached_out`, - /// `decrypt_with_aad_out`): absorbs `aad` and - /// `data` (still ciphertext) into GHASH and checks the tag *before* touching `data`, so no - /// unauthenticated plaintext is ever written to the caller's buffer. The preamble of Sec 7 - /// 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, - 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 mut gcm = Self::setup(perm, *nonce); - gcm.absorb_aad(aad)?; - gcm.absorb_data(data)?; - let mut computed: Secret<[u8; 16]> = Secret::new(); - gcm.tag_block(&mut computed); - if !ct_eq_bytes(&computed[..TAG_LEN], tag) { - return Err(SymmetricCipherError::AEADTagCheckFailed); - } - gcm.ctr.do_decrypt_inplace(data)?; - Ok(()) - } } -impl - SymmetricCipherDecryptor - for Gcm +impl<'k, P, const TAG_LEN: usize> SymmetricCipherDecryptor + for Gcm<'k, P, Decrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; GCM_NONCE_LEN], - ) -> Result { - Self::check_shape(); - let perm = P::new(key)?; - Ok(Self::setup(perm, *init_data)) - } - /// `tail_len + input_len`, minus up to `TAG_LEN` bytes held back because they might be the tag. fn do_decrypt_out_len(&self, input_len: usize) -> usize { (self.tail_len + input_len).saturating_sub(TAG_LEN) @@ -665,6 +710,7 @@ where if plaintext.len() < release { return Err(SymmetricCipherError::OutputBufferTooSmall(release)); } + self.accepts_data(release)?; // Data has started even if every byte is still held back as a possible tag, so the AAD // phase ends here rather than at the first byte released: otherwise a `do_update_aad` // after a first call shorter than `TAG_LEN` would be accepted, and absorbed as if it came @@ -715,35 +761,15 @@ where fn decrypt_out_len(ciphertext_len: usize) -> usize { ciphertext_len.saturating_sub(TAG_LEN) } - - /// Overrides the trait's default (which would stream plaintext out before the tag is checked): - /// 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, - init_data: &[u8; GCM_NONCE_LEN], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - >::decrypt_with_aad_out( - key, - init_data, - &[], - ciphertext, - plaintext, - ) - } } /// 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 +/// `FINAL_LEN = TAG_LEN`. The one-shots are the key's provided ones, which zeroize the output on +/// a failed tag check. +impl<'k, P, const TAG_LEN: usize> AEADCipherDecryptor + for Gcm<'k, P, Decrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { self.absorb_aad(aad) @@ -759,6 +785,7 @@ where ) -> Result { plaintext.fill(0); let n = self.tail_len; + self.accepts_data(n)?; plaintext[..n].copy_from_slice(&self.tail[..n]); self.decrypt_in_place(&mut plaintext[..n])?; if let Err(e) = self.finish(tag) { @@ -767,70 +794,19 @@ where } Ok(n) } - - /// Verifies `tag` before decrypting, so no unauthenticated plaintext reaches `plaintext`; on - /// failure what was written there is zeroized. - fn decrypt_detached_out( - key: &KeyMaterial, - nonce: &[u8; GCM_NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; TAG_LEN], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - let len = ciphertext.len(); - if plaintext.len() < len { - return Err(SymmetricCipherError::OutputBufferTooSmall(len)); - } - plaintext[..len].copy_from_slice(ciphertext); - Self::verify_then_decrypt(key, nonce, aad, &mut plaintext[..len], tag).inspect_err( - |_| { - // The buffer holds ciphertext rather than unauthenticated plaintext here, since the - // tag is checked before decryption, but the trait's contract is a zeroized buffer on - // failure, and a caller who ignores the `Result` should find nothing in it at all. - plaintext[..len].fill(0); - }, - )?; - Ok(len) - } - - /// 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, - nonce: &[u8; GCM_NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - plaintext.fill(0); - let needed = Self::decrypt_out_len(ciphertext.len()); - if plaintext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let Some((data, tag)) = ciphertext.split_last_chunk::() else { - return Err(SymmetricCipherError::DecryptionFailed); - }; - let len = data.len(); - plaintext[..len].copy_from_slice(data); - Self::verify_then_decrypt(key, nonce, aad, &mut plaintext[..len], tag) - .inspect_err(|_| plaintext[..len].fill(0))?; - Ok(len) - } } /// The suspended state is the CTR half, the running GHASH, the two byte counts, the phase, and -/// 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 +/// the up-to-`TAG_LEN` bytes a decryptor holds back. `H` and `CIPH_K(J0)` are not in it: `H` is +/// the key state's, and `CIPH_K(J0)` is re-derived from it and the nonce on resume. See /// [`bouncycastle_utils::suspendable_state`]. -impl SuspendableComponent - for Gcm +impl<'k, P, Dir, const TAG_LEN: usize> SuspendableComponent<'k> for Gcm<'k, P, Dir, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, + Dir: Direction, { - const STATE_LEN: usize = Self::CTR_STATE_LEN + GHASH_STATE_LEN + 8 + 8 + 1 + TAG_LEN + 8; - type Key = KeyMaterial; + const STATE_LEN: usize = Self::COMPONENT_STATE_LEN; + type Key = GcmKeyState

; fn write_state(&self, out: &mut [u8]) { let (ctr, rest) = out.split_at_mut(Self::CTR_STATE_LEN); @@ -840,32 +816,40 @@ where let mut w = CursorMut::new(rest); w.u64(self.aad_len); w.u64(self.data_len); - w.u8(self.phase as u8); + // The phase in the low nibble, the direction above it, so that a state suspended while + // encrypting cannot be resumed as a decryptor or the reverse. + w.u8(Dir::STATE_BITS | self.phase as u8); w.bytes(&*self.tail); w.u64(self.tail_len as u64); debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Self::check_shape(); let (ctr, rest) = state.split_at(Self::CTR_STATE_LEN); let (ghash, rest) = rest.split_at(GHASH_STATE_LEN); - // `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 mut gcm = Self::setup(perm, nonce); - gcm.ctr = as SuspendableComponent>::read_state( - ctr, key, + // `setup_from_state` takes `H` from the key state and re-derives `CIPH_K(J0)` from the + // nonce, which is the leading part of the CTR state. Its fresh CTR and GHASH are then + // replaced by the suspended ones. + let nonce = CtrKeyStream::<'k, P, 16, GCM_NONCE_LEN>::nonce_from_state(ctr); + let mut gcm = Self::setup_from_state(key, nonce); + gcm.ctr = as SuspendableComponent<'k>>::read_state( + ctr, &key.perm, )?; gcm.ghash.restore_state(ghash)?; let mut r = Cursor::new(rest); gcm.aad_len = r.u64(); gcm.data_len = r.u64(); - gcm.phase = Phase::from_u8(r.u8())?; + if gcm.aad_len > MAX_AAD_LEN || gcm.data_len > MAX_DATA_LEN { + return Err(SuspendableError::InvalidData); + } + let call_state = r.u8(); + if call_state & 0xF0 != Dir::STATE_BITS { + return Err(SuspendableError::InvalidData); + } + gcm.phase = Phase::from_u8(call_state & 0x0F)?; (*gcm.tail).copy_from_slice(r.bytes(TAG_LEN)); gcm.tail_len = bounded_usize(r.u64(), TAG_LEN)?; debug_assert!(r.is_done()); @@ -874,119 +858,23 @@ where } /// `N` must be [`Gcm::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Gcm +impl<'k, P, Dir, const TAG_LEN: usize, const N: usize> SuspendableKeyed<'k, N> + for Gcm<'k, P, Dir, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, + Dir: Direction, { - type Key = KeyMaterial; + type Key = GcmKeyState

; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } -// The key-produced worker traits, forwarded to the cipher traits' own methods while those still -// exist. Named by path rather than imported, since the method names coincide and an import would -// make every call in this file ambiguous. - -impl - bouncycastle_core::symmetric_key::AeadEncryptWorker - for Gcm -where - P: ElectronicCodeBook + ModeNames, -{ - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { - AEADCipherEncryptor::do_update_aad(self, aad) - } - - fn do_encrypt_out_len(&self, input_len: usize) -> usize { - SymmetricCipherEncryptor::do_encrypt_out_len(self, input_len) - } - - fn do_encrypt_out( - &mut self, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result { - SymmetricCipherEncryptor::do_encrypt_out(self, plaintext, ciphertext) - } - - fn do_encrypt_final(self) -> Result<([u8; TAG_LEN], usize), SymmetricCipherError> { - SymmetricCipherEncryptor::do_encrypt_final(self) - } - - fn do_encrypt_final_detached_out( - self, - ciphertext: &mut [u8; TAG_LEN], - ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { - AEADCipherEncryptor::do_encrypt_final_detached_out(self, ciphertext) - } - - fn encrypt_out_len(plaintext_len: usize) -> usize { - >::encrypt_out_len( - plaintext_len, - ) - } - - fn encrypt_detached_out_len(plaintext_len: usize) -> usize { - >::encrypt_detached_out_len( - plaintext_len, - ) - } -} - -impl - bouncycastle_core::symmetric_key::AeadDecryptWorker - for Gcm -where - P: ElectronicCodeBook + ModeNames, -{ - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { - AEADCipherDecryptor::do_update_aad(self, aad) - } - - fn do_decrypt_out_len(&self, input_len: usize) -> usize { - SymmetricCipherDecryptor::do_decrypt_out_len(self, input_len) - } - - fn do_decrypt_out( - &mut self, - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result { - SymmetricCipherDecryptor::do_decrypt_out(self, ciphertext, plaintext) - } - - fn do_decrypt_final(self) -> Result<([u8; TAG_LEN], usize), SymmetricCipherError> { - SymmetricCipherDecryptor::do_decrypt_final(self) - } - - fn do_decrypt_final_detached_out( - self, - tag: &[u8; TAG_LEN], - plaintext: &mut [u8; TAG_LEN], - ) -> Result { - AEADCipherDecryptor::do_decrypt_final_detached_out(self, tag, plaintext) - } - - fn decrypt_out_len(ciphertext_len: usize) -> usize { - >::decrypt_out_len( - ciphertext_len, - ) - } - - fn decrypt_detached_out_len(ciphertext_len: usize) -> usize { - >::decrypt_detached_out_len( - ciphertext_len, - ) - } -} - mod ghash { //! GHASH: the universal hash function GCM builds its authentication on (NIST SP 800-38D Sec 6.3, //! 6.4), and the GF(2^128) multiplication it is defined over. @@ -1248,9 +1136,8 @@ mod ghash { /// Appends `[aad_bits]_64 || [data_bits]_64` (Algorithm 4 step 5's final block) and writes /// `Y_m`, i.e. `S`, to `out` -- a `Secret`, since `S` is the tag with its mask removed. /// - /// Takes `&mut self` rather than `self` -- `Gcm`'s verify-before-decrypt one-shot needs the rest - /// of its own state (the `Ctr` field) after computing the tag, so consuming `Ghash` here would - /// force that caller to reconstruct it. Nothing asserts a "was padded" flag: the caller is + /// Takes `&mut self` rather than `self`, so that `Gcm` keeps the rest of its state around + /// the call. Nothing asserts a "was padded" flag: the caller is /// expected to have called [`pad_to_block`](Self::pad_to_block) for both the AAD and the data /// phase already (the `0^v` and `0^u` of step 5), so by the time `finish` runs there is nothing /// pending except this one final length block, and no caller should call `update` or diff --git a/crypto/cipher/src/modes/gmac.rs b/crypto/cipher/src/modes/gmac.rs index 118cf8c09..1a5c816d8 100644 --- a/crypto/cipher/src/modes/gmac.rs +++ b/crypto/cipher/src/modes/gmac.rs @@ -6,66 +6,47 @@ //! //! [`Gmac`] is a [`Gcm`] that is only ever given AAD, so it inherits `Gcm`'s 96-bit nonce and its //! `TAG_LEN` range of 12 to 16 bytes. Following that sentence of Sec 5.2, the direction markers -//! carry over too: `Gmac<.., Encrypting, ..>` generates a tag and `Gmac<.., Decrypting, ..>` +//! carry over too: `Gmac<'k, .., Encrypting, ..>` generates a tag and `Gmac<'k, .., Decrypting, ..>` //! verifies one. //! -//! Every tag is bound to a nonce, which is why GMAC implements neither [`MAC`] (whose `new` has -//! nowhere to put one) nor the MAC mode trait. The generating side draws the nonce from the RNG +//! Every tag is bound to a nonce, which is why GMAC implements neither [`MAC`] (whose workers take +//! no nonce) nor the MAC mode trait. The generating side draws the nonce from the RNG //! it is given and returns it, or takes one from a caller who manages its own; the verifying side //! takes the nonce the tag was made under. A mode-bound key, `AES128Key`, hands out both -//! sides from one stored `H`. +//! sides from one stored `H`, which the workers borrow. //! //! The key is [`KeyType::MACKey`] material, as CMAC's is, and so cannot also be a GCM key: a //! nonce shared between a GMAC tag and a GCM message under one key is a repeated nonce. //! //! # Usage Examples //! -//! From a key state, which is what a mode-bound key holds: +//! From a mode-bound key, which holds the key state the workers borrow; over the toy permutation +//! the key is the test suite's `ToyKey`, the shape of the AES one: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } +//! # #[allow(dead_code)] mod toy_key { include!("../../tests/common/toy_key.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::{GcmKeyState, Gmac}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_core::hazmat::ElectronicCodeBook; +//! # use toy_key::ToyKey; +//! use bouncycastle_cipher::modes::GMAC; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_core::symmetric_key::FromKeyMaterial; //! use bouncycastle_rng::HashDRBG_SHA512; //! -//! type ToyGmac

= Gmac; -//! //! let mut rng = HashDRBG_SHA512::new_from_os(); -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -//! let state = GcmKeyState::new::<16>(ToyBlockCipher::new(&key).unwrap()); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); +//! let key = ToyKey::::from_key_material(&material).unwrap(); //! -//! let (mut generator, nonce) = ToyGmac::::from_key_state(&state, &mut rng).unwrap(); +//! let (mut generator, nonce) = key.create_generator(&mut rng).unwrap(); //! generator.do_update(b"a message "); //! generator.do_update(b"in two parts"); //! let tag = generator.do_final(); //! -//! let mut verifier = ToyGmac::::from_key_state(&state, &nonce); +//! let mut verifier = key.create_verifier(&nonce); //! verifier.do_update(b"a message in two parts"); //! assert!(verifier.do_verify_final(&tag)); //! ``` //! -//! From the material, for one message: -//! -//! ``` -//! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -//! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::Gmac; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_rng::HashDRBG_SHA512; -//! -//! type ToyGmac = Gmac; -//! -//! let mut rng = HashDRBG_SHA512::new_from_os(); -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); -//! let (generator, nonce) = ToyGmac::::new(&key, &mut rng).unwrap(); -//! let tag = generator.mac(b"a message"); -//! assert!(ToyGmac::::new(&key, &nonce).unwrap().verify(b"a message", &tag)); -//! ``` -//! //! # 🚨 Security Considerations 🚨 //! //! Everything in the [`gcm`](crate::modes::gcm) module's security considerations applies: above @@ -79,119 +60,112 @@ use crate::modes::ModeNames; use crate::modes::gcm::{GCM_NONCE_LEN, Gcm, GcmKeyState}; use crate::{Decrypting, Encrypting}; -use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; -use bouncycastle_core::hazmat::{ElectronicCodeBook, do_hazardous_operations}; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::hazmat::{BlockPermutation, Mode, NonceMacMode}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{ - AEADCipherEncryptor, Algorithm, RNG, SymmetricCipherDecryptor, SymmetricCipherEncryptor, -}; +use bouncycastle_core::traits::{Algorithm, RNG}; /// The GMAC mode type: the `M` of a mode-bound key such as `AES128Key`. `GMAC` is GMAC with /// the full 128-bit tag; `GMAC<12>` and so on select a shorter tag, within the `12..=16` byte /// range [`Gcm`] accepts. Its key state is [`GcmKeyState`], so a GMAC key caches `H` as a GCM key -/// does. The [`Mode`] impl and the generator/verifier pair that connect it to a key type are -/// written by each permutation's crate while the cipher traits still carry `KEY_LEN`. -/// -/// [`Mode`]: bouncycastle_core::hazmat::Mode +/// does. The [`Mode`] and [`NonceMacMode`] impls below are generic over any [`BlockPermutation`] +/// with a 128-bit block; the generator/verifier pair is inherent on each key type and forwards +/// to the latter, since a GMAC tag is bound to a nonce and so fits neither MAC role trait. pub struct GMAC; +impl Mode

for GMAC +where + P: BlockPermutation<16> + ModeNames + Clone, +{ + type KeyState = GcmKeyState

; + const KEY_TYPE: KeyType = KeyType::MACKey; + + fn key_state(perm: P) -> Self::KeyState { + GcmKeyState::new(perm) + } +} + +impl NonceMacMode for GMAC +where + P: BlockPermutation<16> + ModeNames + Clone, +{ + type Generator<'k> + = Gmac<'k, P, Encrypting, TAG_LEN> + where + P: 'k; + type Verifier<'k> + = Gmac<'k, P, Decrypting, TAG_LEN> + where + P: 'k; + + fn generate_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Generator<'k>, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + Gmac::::from_key_state(state, rng) + } + + fn generate_init_nonce<'k>( + state: &'k Self::KeyState, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self::Generator<'k> { + Gmac::::from_key_state_nonce(state, nonce) + } + + fn verify_init<'k>( + state: &'k Self::KeyState, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self::Verifier<'k> { + Gmac::::from_key_state(state, nonce) + } +} + /// GMAC over the permutation `P`, generating (`Dir = Encrypting`) or verifying (`Dir = /// Decrypting`) a `TAG_LEN`-byte tag. See the module docs. -pub struct Gmac +pub struct Gmac<'k, P, Dir, const TAG_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { - gcm: Gcm, + gcm: Gcm<'k, P, Dir, TAG_LEN>, } /// `Gcm`'s AAD absorption fails only if AAD follows data, which GMAC never supplies, or if the /// running AAD length overflows a `u64`, which needs 2^64 bytes of input. const AAD_INFALLIBLE: &str = "GMAC: GCM rejects AAD only after data or past 2^64 bytes"; -/// The material route's key check: GMAC takes a `MACKey` of exactly `KEY_LEN` bytes, and the -/// permutation accepts only a `SymmetricCipherKey`, so the key is handed on as a retagged copy at -/// the same strength -- the permutation's strength check still applies -- used for nothing else. -fn mac_key_as_cipher_key( - key: &KeyMaterial, -) -> Result, SymmetricCipherError> { - if key.key_type() != KeyType::MACKey { - return Err(KeyMaterialError::InvalidKeyType("GMAC: key type must be a MAC key").into()); - } - let mut cipher_key = KeyMaterial::::from_bytes_as_type( - key.ref_to_bytes(), - KeyType::SymmetricCipherKey, - )?; - do_hazardous_operations(&mut cipher_key, |k| { - k.set_key_type(KeyType::SymmetricCipherKey)?; - k.set_security_strength(key.security_strength()) - })?; - Ok(cipher_key) -} - -impl Algorithm - for Gmac +impl<'k, P, Dir, const TAG_LEN: usize> Algorithm for Gmac<'k, P, Dir, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { const ALG_NAME: &'static str = P::GMAC_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl Gmac +impl<'k, P, const TAG_LEN: usize> Gmac<'k, P, Encrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { /// Begins a tag from a key's stored state under a nonce drawn from `rng`: what a mode-bound /// key's `create_generator` calls. /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - pub fn from_key_state( - state: &GcmKeyState

, + pub(crate) fn from_key_state( + state: &'k GcmKeyState

, rng: &mut dyn RNG, - ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> - where - P: Clone, - { - let (gcm, nonce) = Gcm::::from_key_state(state, rng)?; + ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + let (gcm, nonce) = Gcm::::from_key_state(state, rng)?; Ok((Self { gcm }, nonce)) } /// As [`from_key_state`](Self::from_key_state) under a nonce the caller supplies, who takes /// on the obligation that it never repeat under this key. - pub fn from_key_state_nonce(state: &GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self - where - P: Clone, - { - Self { gcm: Gcm::from_key_state_nonce(state, nonce) } - } - - /// Begins a tag from the material, under a nonce drawn from `rng`. - /// - /// # Errors - /// [`KeyMaterialError::InvalidKeyType`] if the key is not a - /// [`KeyType::MACKey`], the permutation's own refusals (a strength below the key length, - /// an all-zero key), or [`SymmetricCipherError::RNGError`] if the nonce draw fails. - pub fn new( - key: &KeyMaterial, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { - let cipher_key = mac_key_as_cipher_key(key)?; - let (gcm, nonce) = Gcm::do_encrypt_init_rng(&cipher_key, rng)?; - Ok((Self { gcm }, nonce)) - } - - /// As [`new`](Self::new) under a nonce the caller supplies. - /// - /// # Errors - /// As [`new`](Self::new), less the RNG. - pub fn new_nonce( - key: &KeyMaterial, + pub(crate) fn from_key_state_nonce( + state: &'k GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN], - ) -> Result { - let cipher_key = mac_key_as_cipher_key(key)?; - Ok(Self { gcm: Gcm::do_encrypt_init_nonce(&cipher_key, nonce)? }) + ) -> Self { + Self { gcm: Gcm::from_key_state_nonce(state, nonce) } } /// Absorbs more of the message. @@ -211,30 +185,15 @@ where } } -impl Gmac +impl<'k, P, const TAG_LEN: usize> Gmac<'k, P, Decrypting, TAG_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation<16> + ModeNames, { /// Begins verifying a tag made under `nonce` from a key's stored state: what a mode-bound /// key's `create_verifier` calls. Infallible: the state is keyed and the nonce length is in /// the type. - pub fn from_key_state(state: &GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self - where - P: Clone, - { - Self { gcm: Gcm::::from_key_state(state, nonce) } - } - - /// Begins verifying a tag made under `nonce`, from the material. - /// - /// # Errors - /// As the generating side's `new`, less the RNG. - pub fn new( - key: &KeyMaterial, - nonce: &[u8; GCM_NONCE_LEN], - ) -> Result { - let cipher_key = mac_key_as_cipher_key(key)?; - Ok(Self { gcm: Gcm::do_decrypt_init(&cipher_key, nonce)? }) + pub(crate) fn from_key_state(state: &'k GcmKeyState

, nonce: &[u8; GCM_NONCE_LEN]) -> Self { + Self { gcm: Gcm::::from_key_state(state, nonce) } } /// Absorbs more of the message. diff --git a/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs b/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs index 91f17f23b..2919bd1e6 100644 --- a/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs +++ b/crypto/cipher/src/modes/hazmat/ctr_key_stream.rs @@ -3,9 +3,8 @@ 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::errors::SuspendableError; +use bouncycastle_core::hazmat::{BlockPermutation, KeyStream}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::Algorithm; use bouncycastle_utils::suspendable_state::{Cursor, CursorMut, SuspendableComponent}; @@ -17,7 +16,7 @@ use crate::modes::Ctr; use crate::stream::StreamCipher; // end of imports needed for docs -/// The CTR keystream `Oj = CIPH_K(Tj)` (SP 800-38A Sec 6.5) over any [`ElectronicCodeBook`], with +/// The CTR keystream `Oj = CIPH_K(Tj)` (SP 800-38A Sec 6.5) over any [`BlockPermutation`], with /// `Tj = N | [j]m` (SP 800-38A Appendix B.2); see the module docs. Use it through [`Ctr`]. /// /// # 🚨 Security Considerations 🚨 @@ -27,14 +26,15 @@ use crate::stream::StreamCipher; /// /// # State /// -/// 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. +/// A reference to the permutation, which the key owns, 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 +pub struct CtrKeyStream<'k, P, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k P, /// `N`: the message nonce, the leading bytes of every counter block. nonce: [u8; INIT_DATA_LEN], /// The counter of the *next* block to use, as an integer: `Tj = N | [next_counter]m`. @@ -46,10 +46,10 @@ where next_counter: u64, } -impl - CtrKeyStream +impl<'k, P, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> + CtrKeyStream<'k, P, BLOCK_LEN, INIT_DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// Bytes of counter at the end of each block: whatever the nonce leaves. const CTR_LEN: usize = BLOCK_LEN - INIT_DATA_LEN; @@ -79,19 +79,20 @@ where }; } - /// `T1 = N | [0]m`: the nonce, then a zero counter. + /// `T1 = N | [0]m`: the nonce, then a zero counter, over a key's stored permutation, which + /// the keystream borrows. What the [`CTR`](crate::modes::CTR) mode impls build. #[inline] - fn start(perm: P, nonce: [u8; INIT_DATA_LEN]) -> Self { + pub fn from_perm(perm: &'k P, nonce: [u8; INIT_DATA_LEN]) -> Self { Self::start_at(perm, nonce, 0) } - /// As [`start`](Self::start), but the counter of the *next* block is `counter` instead of 0. + /// As [`from_perm`](Self::from_perm), but the counter of the *next* block is `counter` instead of 0. /// /// GCM's GCTR (SP 800-38D Sec 6.5) runs the data through this mode starting at `inc32(J0)`, /// whose counter field is `2` -- see `gcm.rs`. Crate-private because the public API's contract /// is that a message starts at counter 0; only `gcm.rs` needs otherwise. #[inline] - pub(crate) fn start_at(perm: P, nonce: [u8; INIT_DATA_LEN], counter: u64) -> Self { + pub(crate) fn start_at(perm: &'k P, nonce: [u8; INIT_DATA_LEN], counter: u64) -> Self { Self::check_shape(); debug_assert!( counter < Self::BLOCK_LIMIT, @@ -100,7 +101,7 @@ where Self { perm, nonce, next_counter: counter } } - /// The nonce `N` of a suspended keystream, read out of the state [`SuspendableComponent`] + /// The nonce `N` of a suspended keystream, read out of the state [`SuspendableComponent<'k>`] /// writes without rebuilding the keystream: it is the leading `INIT_DATA_LEN` bytes. For a /// composite that needs the nonce before it can afford the key schedule (GCM derives `H` and /// the tag mask from it). @@ -124,10 +125,10 @@ where } } -impl Algorithm - for CtrKeyStream +impl<'k, P, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> Algorithm + for CtrKeyStream<'k, P, BLOCK_LEN, INIT_DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::CTR_ALG_NAME`]. const ALG_NAME: &'static str = P::CTR_ALG_NAME; @@ -135,22 +136,11 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl - KeyStream - for CtrKeyStream +impl<'k, P, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> KeyStream + for CtrKeyStream<'k, P, BLOCK_LEN, INIT_DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// Expands the key; the keystream starts at `T1 = N | [0]m`. - fn new( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result { - Self::check_shape(); - let perm = P::new(key)?; - Ok(Self::start(perm, *init_data)) - } - /// A whole block for every counter value left. fn remaining_blocks(&self) -> u64 { Self::BLOCK_LIMIT - self.next_counter @@ -161,7 +151,7 @@ where fn apply_blocks(&mut self, blocks: &mut [[u8; BLOCK_LEN]]) { let nonce = &self.nonce; apply_counter_blocks( - &self.perm, + self.perm, &mut self.next_counter, |j| Self::counter_block(nonce, j), blocks, @@ -169,15 +159,15 @@ 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 +/// The suspended state is the nonce and the next counter value; the permutation is the key's, +/// re-supplied by reference. See [`bouncycastle_utils::suspendable_state`]. +impl<'k, P, const BLOCK_LEN: usize, const INIT_DATA_LEN: usize> SuspendableComponent<'k> + for CtrKeyStream<'k, P, BLOCK_LEN, INIT_DATA_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = INIT_DATA_LEN + 8; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { let mut w = CursorMut::new(out); @@ -186,9 +176,9 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Self::check_shape(); - let perm = P::new(key).map_err(|_| SuspendableError::InvalidData)?; + let perm = key; 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 @@ -228,8 +218,8 @@ mod tests { include!("../../../tests/common/toy_block_cipher.rs"); } - type ToyKeyStream = CtrKeyStream; - type ToyCtr = Ctr; + type ToyKeyStream<'k> = CtrKeyStream<'k, ToyBlockCipher, 16, 12>; + type ToyCtr<'k> = Ctr<'k, ToyBlockCipher, Encrypting, 16, 12>; fn key() -> KeyMaterial<16> { KeyMaterial::<16>::from_bytes_as_type(&[0x5Au8; 16], KeyType::SymmetricCipherKey) @@ -243,18 +233,12 @@ mod tests { fn start_at_matches_start_after_discarding_blocks() { let nonce = [0x11u8; 12]; - let mut from_start = ToyCtr::from_keystream(ToyKeyStream::start( - ToyBlockCipher::new(&key()).unwrap(), - nonce, - )); + let perm = ToyBlockCipher::new(&key()).unwrap(); + let mut from_start = ToyCtr::from_keystream(ToyKeyStream::from_perm(&perm, nonce)); let mut discarded = [0u8; 32]; from_start.do_encrypt_inplace(&mut discarded).unwrap(); - let mut from_start_at = ToyCtr::from_keystream(ToyKeyStream::start_at( - ToyBlockCipher::new(&key()).unwrap(), - nonce, - 2, - )); + let mut from_start_at = ToyCtr::from_keystream(ToyKeyStream::start_at(&perm, nonce, 2)); let mut a = [0x42u8; 48]; let mut b = a; @@ -268,7 +252,8 @@ mod tests { /// 128-bit blocks) that GCM relies on `Ctr`'s existing "counter exhausted" error to enforce. #[test] fn start_at_capacity_is_block_limit_minus_the_starting_counter() { - let ks = ToyKeyStream::start_at(ToyBlockCipher::new(&key()).unwrap(), [0u8; 12], 2); + let perm = ToyBlockCipher::new(&key()).unwrap(); + let ks = ToyKeyStream::start_at(&perm, [0u8; 12], 2); assert_eq!(ks.remaining_blocks(), ToyKeyStream::BLOCK_LIMIT - 2); } @@ -278,20 +263,24 @@ mod tests { #[test] fn ctr_keystream_conforms_to_the_key_stream_framework() { let framework = TestFrameworkKeyStream::new(); - framework.test::>(); - framework.test::>(); - framework - .test::>(); + let toy = Toy::new(&key()).unwrap(); + framework.test::<12, TOY_LEN, _>(|nonce| { + CtrKeyStream::::from_perm(&toy, *nonce) + }); + framework.test::<15, TOY_LEN, _>(|nonce| { + CtrKeyStream::::from_perm(&toy, *nonce) + }); + let forward_only = ForwardOnlyToy::new(&key()).unwrap(); + framework.test::<12, TOY_LEN, _>(|nonce| { + CtrKeyStream::::from_perm(&forward_only, *nonce) + }); } /// 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::::MAX_SECURITY_STRENGTH, - Toy::MAX_SECURITY_STRENGTH - ); + assert_eq!(CtrKeyStream::::ALG_NAME, Toy::CTR_ALG_NAME); + assert_eq!(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..5e6bc1430 100644 --- a/crypto/cipher/src/modes/hazmat/ecb.rs +++ b/crypto/cipher/src/modes/hazmat/ecb.rs @@ -10,29 +10,30 @@ //! //! # Usage Examples //! -//! ECB has the same shape with no IV: `encrypt` returns an empty array and `decrypt` takes one. -//! The codebook property that makes it unsuitable for data is visible in the ciphertext: +//! ECB has the same shape with no IV and no RNG: a key bound to [`ECB`] (the `hazmat` +//! role pair) hands out its workers infallibly, and [`ECB`](ECB) puts the padded form on the +//! symmetric pair with an empty init array. The codebook property that makes it unsuitable for +//! data is visible in the ciphertext: //! //! ``` //! # mod toy { include!("../../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; +//! use bouncycastle_core::hazmat::{EcbDecryptMode, EcbEncryptMode, ElectronicCodeBook, Mode}; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -//! use bouncycastle_cipher::modes::hazmat::ecb::Ecb; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! type ToyEcb

= Ecb; +//! use bouncycastle_cipher::modes::hazmat::ecb::ECB; +//! use bouncycastle_cipher::modes::Blocks; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); -//! let mut data = [0x5Au8; 32]; // two equal blocks +//! let state = as Mode>::key_state(ToyBlockCipher::new(&key).unwrap()); +//! let mut data = [[0x5Au8; 16]; 2]; // two equal blocks //! -//! let (bytes_written, no_iv): (usize, [u8; 0]) = ToyEcb::::encrypt_inplace(&key, &mut data).expect("encryption"); -//! assert_eq!(no_iv.len(), 0, "ECB mode returns the IV as an empty array"); -//! assert_eq!(data[..16], data[16..], "equal plaintext blocks give equal ciphertext blocks"); +//! ECB::::encrypt_init(&state).do_encrypt_blocks_inplace(&mut data).expect("encryption"); +//! assert_eq!(data[0], data[1], "equal plaintext blocks give equal ciphertext blocks"); //! -//! ToyEcb::::decrypt_inplace(&key, &[], &mut data).expect("decryption"); -//! assert_eq!(data, [0x5Au8; 32]); +//! ECB::::decrypt_init(&state).do_decrypt_blocks_inplace(&mut data).expect("decryption"); +//! assert_eq!(data, [[0x5Au8; 16]; 2]); //! ``` //! //! # A mode with no state @@ -46,15 +47,15 @@ //! cipher functions can be computed in parallel". //! //! To take advantage of this parallelism, this mode batches both directions through the -//! permutation's four-block and pair methods ([`ElectronicCodeBook::encrypt_4blocks`] / -//! [`ElectronicCodeBook::encrypt_2blocks`] and their inverses), which may represent a speed-up over +//! permutation's four-block and pair methods ([`BlockPermutation::encrypt_4blocks`] / +//! [`BlockPermutation::encrypt_2blocks`] and their inverses), which may represent a speed-up over //! iterating one block at a time, depending on the implementation of the underlying cipher. //! //! # Suspending and resuming execution //! //! [`Ecb`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a byte //! array and resumed later with the re-supplied key. The state is empty, since nothing carries over -//! between blocks, and resuming is re-expanding the key; it exists so the padded adapters over ECB +//! between blocks, and resuming is taking the key state again; it exists so the padded adapters over ECB //! can be suspended. The array length is `Ecb::SUSPENDED_STATE_LEN`; see [the crate //! docs](crate#suspending-and-resuming-execution) for an example. //! @@ -80,61 +81,150 @@ //! //! **ECB Mode should not be used in production!** -use crate::modes::ModeNames; +use crate::modes::{Blocks, ModeNames}; +use crate::padding::{PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use crate::{Decrypting, Encrypting}; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::hazmat::{ + BlockPermutation, EcbDecryptMode, EcbEncryptMode, ElectronicCodeBook, Mode, + SymmetricDecryptMode, SymmetricEncryptMode, +}; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SuspendableKeyed, + Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, BlockCipherPadding, RNG, + SuspendableKeyed, }; use bouncycastle_utils::suspendable_state::{ LIB_VERSION_LEN, SuspendableComponent, resume_component, suspend_component, }; use core::marker::PhantomData; +/// The ECB mode type: the `M` of a mode-bound key such as `AES128Key>`. `X` is +/// [`Blocks`], which puts the key on the ECB key path of [`bouncycastle_core::hazmat`] with +/// [`Ecb`] itself as its worker, or a padding scheme, which puts it on the symmetric path with +/// the padded adapter over `Ecb`; that path's constructor takes an RNG, which ECB, having no init +/// data, draws nothing from. Its key state is the keyed permutation alone. +/// +/// **Not a confidentiality mode for data**, whichever form; see [`Ecb`]. +pub struct ECB(core::marker::PhantomData); + +impl Mode

for ECB { + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl EcbEncryptMode + for ECB +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = Ecb<'k, P, Encrypting, BLOCK_LEN> + where + P: 'k; + + fn encrypt_init<'k>(state: &'k Self::KeyState) -> Self::Encryptor<'k> { + Ecb::from_perm(state) + } +} + +impl EcbDecryptMode + for ECB +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = Ecb<'k, P, Decrypting, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>(state: &'k Self::KeyState) -> Self::Decryptor<'k> { + Ecb::from_perm(state) + } +} + +impl + SymmetricEncryptMode for ECB +where + P: ElectronicCodeBook + ModeNames + Clone, + Pad: BlockCipherPadding, +{ + type Encryptor<'k> + = PaddedBlockCipherEncryptor, Pad, 0, BLOCK_LEN> + where + P: 'k; + + /// ECB has no init data, so nothing is drawn from `rng` and the init data is the empty + /// array. + fn encrypt_init<'k>( + state: &'k Self::KeyState, + _rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; 0]), SymmetricCipherError> { + Ok((PaddedBlockCipherEncryptor::from_inner(Ecb::from_perm(state)), [])) + } +} + +impl + SymmetricDecryptMode for ECB +where + P: ElectronicCodeBook + ModeNames + Clone, + Pad: BlockCipherPadding, +{ + type Decryptor<'k> + = PaddedBlockCipherDecryptor, Pad, 0, BLOCK_LEN> + where + P: 'k; + + fn decrypt_init<'k>(state: &'k Self::KeyState, _init_data: &[u8; 0]) -> Self::Decryptor<'k> { + PaddedBlockCipherDecryptor::from_inner(Ecb::from_perm(state)) + } +} + /// ECB mode over any permutation that impls [`ElectronicCodeBook`], with the direction encoded in the type. /// /// **Not a confidentiality mode for data**: see the module docs and [`modes`](crate::modes)'s "Security /// 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<'k, _, Encrypting, _, _>` has no /// decryption methods at all -- using one in the wrong direction is a compile error rather than a /// runtime check. /// /// # State -/// Only the permutation, which owns the key schedule and is responsible for keeping it in a +/// Only a reference to the permutation, which owns the key schedule and keeps it in a /// 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::

()`. +/// there is no block of chaining value: an `Ecb` is the reference and nothing else. #[derive(Clone)] -pub struct Ecb +pub struct Ecb<'k, P, Dir, const BLOCK_LEN: usize> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - perm: P, + perm: &'k P, _dir: PhantomData

, } -impl Ecb +impl<'k, P, Dir, const BLOCK_LEN: usize> Ecb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header alone, since ECB + /// The `N` of this type's [`SuspendableKeyed<'k, N>`] 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 }) + /// Over an already-keyed permutation: what the [`ECB`] mode impls build. + pub(crate) fn from_perm(perm: &'k P) -> Self { + Self { perm, _dir: PhantomData } } } -impl Algorithm - for Ecb +impl<'k, P, Dir, const BLOCK_LEN: usize> Algorithm for Ecb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { /// The permutation's name for this mode, [`ModeNames::ECB_ALG_NAME`]. const ALG_NAME: &'static str = P::ECB_ALG_NAME; @@ -143,37 +233,12 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; } -impl BlockCipherEncryptor - for Ecb +impl<'k, P, const BLOCK_LEN: usize> BlockCipherEncryptor<0, BLOCK_LEN> + for Ecb<'k, P, Encrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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> { - Ok((Self::new(key)?, [])) - } - - /// Always panics: ECB generates no init data, so there is nothing for an RNG to do. - /// - /// # Panics - /// Unconditionally, as [`BlockCipherEncryptor::do_encrypt_init_rng`] requires of a mode whose - /// `INIT_DATA_LEN` is 0. Reaching for the RNG-taking constructor means the caller expects a - /// randomized mode, and ECB is not one -- SP 800-38A Table D.2 lists its IV column as "Not - /// 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, - _rng: &mut dyn RNG, - ) -> Result<(Self, [u8; 0]), SymmetricCipherError> { - unimplemented!( - "ECB has no initialization data, so it draws nothing from an RNG: use do_encrypt_init, \ - or a mode with an IV if a randomized ciphertext was wanted" - ) - } - + /// The implementor hook: /// `Cj = CIPH_K(Pj)` for every block, in place. /// /// Sec 6.1 allows the forward cipher functions to "be computed in parallel", so the blocks go @@ -200,20 +265,12 @@ where } } -impl BlockCipherDecryptor - for Ecb +impl<'k, P, const BLOCK_LEN: usize> BlockCipherDecryptor<0, BLOCK_LEN> + for Ecb<'k, P, Decrypting, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - /// 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 { - Self::new(key) - } - + /// The implementor hook: /// `Pj = CIPH^-1_K(Cj)` for every block, in place -- fours, then pairs, then the remaining /// block, as on the encrypt side. Never fails. fn do_decrypt_blocks_inplace( @@ -237,39 +294,41 @@ 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 +/// is taking the key state again. 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<'k, P, Dir, const BLOCK_LEN: usize> SuspendableComponent<'k> for Ecb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { const STATE_LEN: usize = 0; - type Key = KeyMaterial; + type Key = P; fn write_state(&self, out: &mut [u8]) { debug_assert!(out.is_empty()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { debug_assert!(state.is_empty()); - Self::new(key).map_err(|_| SuspendableError::InvalidData) + Ok(Self::from_perm(key)) } } /// `N` must be [`Ecb::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl SuspendableKeyed - for Ecb +impl<'k, P, Dir, const BLOCK_LEN: usize, const N: usize> SuspendableKeyed<'k, N> + for Ecb<'k, P, Dir, BLOCK_LEN> where - P: ElectronicCodeBook + ModeNames, + P: BlockPermutation + ModeNames, { - type Key = KeyMaterial; + type Key = P; fn suspend(self) -> [u8; N] { suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + /// The state carries no direction: one suspended by the other direction's worker is accepted + /// and resumes over the same chaining value, which gives wrong output rather than an error. + /// Only GCM, whose state means different things per direction, refuses the other one. + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/src/modes/mod.rs b/crypto/cipher/src/modes/mod.rs index 89201a889..b6f4335cb 100644 --- a/crypto/cipher/src/modes/mod.rs +++ b/crypto/cipher/src/modes/mod.rs @@ -43,6 +43,15 @@ //! why it lives under [`hazmat`] -- see [`hazmat::ecb::Ecb`] and //! [Choosing between the modes](#choosing-between-the-modes). //! +//! Each mode is also a **mode type** -- [`CBC`], [`CCM`], [`CCM_Packet`], [`CFB`], [`CFB8`], +//! [`CTR`], [`GCM`], [`hazmat::ecb::ECB`], and the MACs [`CMAC`] and [`GMAC`] -- the `M` a mode-bound +//! key such as `bouncycastle_aes::AES128Key` is parameterised by. The mode type implements the +//! hazmat mode traits of [`bouncycastle_core::hazmat`], which is how a key hands out the mode's +//! encryptors and decryptors through the role traits of `bouncycastle_core::symmetric_key`; a +//! key is bound to one mode, and which role traits it carries follow from that mode. The modes +//! with a padded and a whole-block form, CBC and ECB, take the padding scheme or [`Blocks`] as a +//! parameter, and the two forms are two key types. +//! //! [Choosing between the modes](#choosing-between-the-modes) covers when each is the right answer //! -- which, for a new design, one of them usually is. //! @@ -73,30 +82,31 @@ //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::{Cbc, CcmDecryptor, CcmEncryptor, Cfb, Cfb8, Ctr, Gcm}; +//! use bouncycastle_cipher::modes::{Cbc, CcmPacketDecryptor, CcmPacketEncryptor, 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; +//! // Every worker borrows its key state for `'k`. CBC, CFB, and CFB8 then take a permutation, a +//! // direction and a block length; the key length is the key's business, not the worker's. +//! type ToyCbc<'k, Dir> = Cbc<'k, ToyBlockCipher, Dir, 16>; +//! type ToyCfb<'k, Dir> = Cfb<'k, ToyBlockCipher, Dir, 16>; +//! type ToyCfb8<'k, Dir> = Cfb8<'k, ToyBlockCipher, Dir, 16>; //! //! // 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<'k, Dir> = Ctr<'k, ToyBlockCipher, Dir, 16, 12>; //! -//! // CCM takes the permutation, key length and block length like the rest, plus the nonce -//! // length and the tag length -- both CCM-specific choices rather than cipher params. +//! // CCM takes the permutation and 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. //! // Each direction is its own type here, and the frame follows: `AAD_LEN` of AAD capacity and //! // frames of exactly `DATA_LEN` bytes, since CCM commits to the payload length up front. -//! type ToyCcmEnc = CcmEncryptor; -//! type ToyCcmDec = CcmDecryptor; +//! type ToyCcmEnc<'k> = CcmPacketEncryptor<'k, ToyBlockCipher, 16, 12, 16, 64, 1024>; +//! type ToyCcmDec<'k> = CcmPacketDecryptor<'k, ToyBlockCipher, 16, 12, 16, 64, 1024>; //! //! // 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<'k, Dir> = Gcm<'k, ToyBlockCipher, Dir, 16>; //! ``` //! //! ## Encrypting and decrypting @@ -158,22 +168,32 @@ pub mod gmac; pub mod hazmat; mod iv; -pub use cbc::Cbc; -pub use ccm::{CCM, CcmDecryptor, CcmEncryptor, CcmPacketDecryptor, CcmPacketEncryptor}; -pub use cfb::Cfb; -pub use cfb8::Cfb8; +pub use cbc::{CBC, Cbc}; +pub use ccm::{ + CCM, CCM_Packet, CcmDecryptor, CcmEncryptor, CcmPacketDecryptor, CcmPacketEncryptor, +}; +pub use cfb::{CFB, Cfb}; +pub use cfb8::{CFB8, Cfb8}; pub use cmac::{CMAC, CMAC_MIN_TAG_LEN, Cmac, CmacKeyState}; -pub use ctr::Ctr; +pub use ctr::{CTR, Ctr}; pub use gcm::{GCM, GCM_NONCE_LEN, Gcm, GcmKeyState}; pub use gmac::{GMAC, Gmac}; +/// Selects the whole-block form of a mode that also has a padded form: `CBC` is a key +/// for the block-aligned [`Cbc`] itself, on the block key path, where `CBC` is one for +/// the padded adapter over it, on the symmetric path. Likewise `ECB` under +/// [`hazmat`]. Not a padding scheme: it adds nothing, and the lengths it accepts are checked at +/// compile time rather than refused at the final as `NoPadding` does. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Blocks; + /// A table of algorithm names used to populate [`Algorithm`] for each Mode-Permutation-Size combination. /// /// For permutations that do not implement all modes, simply leave the NAME for that mode as the empty-string. pub trait ModeNames { /// Reported by [`Cbc`]. const CBC_ALG_NAME: &'static str; - /// Reported by [`CcmEncryptor`] and [`CcmDecryptor`]. + /// Reported by [`CcmPacketEncryptor`] and [`CcmPacketDecryptor`]. const CCM_ALG_NAME: &'static str; /// Reported by [`Cfb`]. const CFB_ALG_NAME: &'static str; diff --git a/crypto/cipher/src/padding/padded_block_cipher.rs b/crypto/cipher/src/padding/padded_block_cipher.rs index 019c6edba..3c751b00f 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, + Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, BlockCipherPadding, SuspendableKeyed, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ @@ -28,20 +27,15 @@ const GROUP: usize = 8; /// Encrypts arbitrary-length data with a block cipher `E`, padding the final block with `P`. /// /// Stream with [`SymmetricCipherEncryptor::do_encrypt_out`] then -/// [`SymmetricCipherEncryptor::do_encrypt_final`], or use the one-shot -/// [`SymmetricCipherEncryptor::encrypt_out`]. Output is +/// [`SymmetricCipherEncryptor::do_encrypt_final`], or use the key's one-shot `encrypt_out`. +/// Output is /// `plaintext_len / BLOCK_LEN + 1` blocks for a scheme that always pads (PKCS7), and exactly the /// input length for one that never does (`NoPadding`, which rejects an unaligned input at /// `do_encrypt_final`). The buffered partial plaintext block is held in a [`Secret`]. #[derive(Clone)] -pub struct PaddedBlockCipherEncryptor< - E, - P, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, -> where - E: BlockCipherEncryptor, +pub struct PaddedBlockCipherEncryptor +where + E: BlockCipherEncryptor, P: BlockCipherPadding, { encryptor: E, @@ -51,10 +45,10 @@ pub struct PaddedBlockCipherEncryptor< buf_len: usize, } -impl Algorithm - for PaddedBlockCipherEncryptor +impl Algorithm + for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + Algorithm, + E: BlockCipherEncryptor + Algorithm, P: BlockCipherPadding, { /// The inner cipher's name; padding does not change what the algorithm is. @@ -63,28 +57,26 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = E::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor - for PaddedBlockCipherEncryptor +impl + PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + Algorithm, + E: BlockCipherEncryptor, P: BlockCipherPadding, { - fn do_encrypt_init( - key: &KeyMaterial, - ) -> 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)) - } - - fn do_encrypt_init_rng( - key: &KeyMaterial, - 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)) + /// The adapter over an already-constructed inner cipher, with nothing buffered: what a mode + /// type's `encrypt_init` builds. + pub(crate) fn from_inner(encryptor: E) -> Self { + Self { encryptor, _padding: PhantomData, buf: Secret::new(), buf_len: 0 } } +} +impl + SymmetricCipherEncryptor + for PaddedBlockCipherEncryptor +where + E: BlockCipherEncryptor + Algorithm, + P: BlockCipherPadding, +{ /// Whole blocks among the buffered bytes plus `input_len`. fn do_encrypt_out_len(&self, input_len: usize) -> usize { (self.buf_len + input_len) / BLOCK_LEN * BLOCK_LEN @@ -184,16 +176,11 @@ where /// /// Only the last block carries padding, so [`do_update_out`](Self::do_decrypt_out) always withholds /// the most recent complete block and [`do_decrypt_final`](Self::do_decrypt_final) unpads it. -/// One-shot: [`decrypt_out`](Self::decrypt_out). +/// One-shot: the key's `decrypt_out`. #[derive(Clone)] -pub struct PaddedBlockCipherDecryptor< - D, - P, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, -> where - D: BlockCipherDecryptor, +pub struct PaddedBlockCipherDecryptor +where + D: BlockCipherDecryptor, P: BlockCipherPadding, { decryptor: D, @@ -205,10 +192,10 @@ pub struct PaddedBlockCipherDecryptor< held: Option<[u8; BLOCK_LEN]>, } -impl Algorithm - for PaddedBlockCipherDecryptor +impl Algorithm + for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + Algorithm, + D: BlockCipherDecryptor + Algorithm, P: BlockCipherPadding, { /// The inner cipher's name; padding does not change what the algorithm is. @@ -217,26 +204,26 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = D::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherDecryptor - for PaddedBlockCipherDecryptor +impl + PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + Algorithm, + D: BlockCipherDecryptor, P: BlockCipherPadding, { - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result { - Ok(Self { - decryptor: D::do_decrypt_init(key, init_data)?, - _padding: PhantomData, - buf: [0u8; BLOCK_LEN], - buf_len: 0, - held: None, - }) + /// The adapter over an already-constructed inner cipher, with nothing buffered or held + /// back: what a mode type's `decrypt_init` builds. + pub(crate) fn from_inner(decryptor: D) -> Self { + Self { decryptor, _padding: PhantomData, buf: [0u8; BLOCK_LEN], buf_len: 0, held: None } } +} +impl + SymmetricCipherDecryptor + for PaddedBlockCipherDecryptor +where + D: BlockCipherDecryptor + Algorithm, + P: BlockCipherPadding, +{ /// All complete blocks but the most recent one are released. fn do_decrypt_out_len(&self, input_len: usize) -> usize { let complete = self.held.is_some() as usize + (self.buf_len + input_len) / BLOCK_LEN; @@ -339,25 +326,25 @@ where } } -impl - PaddedBlockCipherEncryptor +impl<'k, E, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> + PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { /// 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 /// [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + LIB_VERSION_LEN + >::STATE_LEN; } /// 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<'k, E, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> SuspendableComponent<'k> + for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { const STATE_LEN: usize = E::STATE_LEN + BLOCK_LEN + 8; @@ -372,7 +359,7 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { let (inner, rest) = state.split_at(E::STATE_LEN); let encryptor = E::read_state(inner, key)?; let mut r = Cursor::new(rest); @@ -387,10 +374,10 @@ where /// `N` must be [`PaddedBlockCipherEncryptor::SUSPENDED_STATE_LEN`]; anything else is a compile /// error. -impl - SuspendableKeyed for PaddedBlockCipherEncryptor +impl<'k, E, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, const N: usize> + SuspendableKeyed<'k, N> for PaddedBlockCipherEncryptor where - E: BlockCipherEncryptor + SuspendableComponent, + E: BlockCipherEncryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { type Key = E::Key; @@ -399,30 +386,30 @@ where suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } -impl - PaddedBlockCipherDecryptor +impl<'k, D, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> + PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { /// 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 /// whether a block is held back, and that block. See [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + LIB_VERSION_LEN + >::STATE_LEN; } /// 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<'k, D, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> SuspendableComponent<'k> + for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { const STATE_LEN: usize = D::STATE_LEN + BLOCK_LEN + 8 + 1 + BLOCK_LEN; @@ -447,7 +434,7 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { let (inner, rest) = state.split_at(D::STATE_LEN); let decryptor = D::read_state(inner, key)?; let mut r = Cursor::new(rest); @@ -469,10 +456,10 @@ where /// `N` must be [`PaddedBlockCipherDecryptor::SUSPENDED_STATE_LEN`]; anything else is a compile /// error. -impl - SuspendableKeyed for PaddedBlockCipherDecryptor +impl<'k, D, P, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, const N: usize> + SuspendableKeyed<'k, N> for PaddedBlockCipherDecryptor where - D: BlockCipherDecryptor + SuspendableComponent, + D: BlockCipherDecryptor + SuspendableComponent<'k>, P: BlockCipherPadding, { type Key = D::Key; @@ -481,7 +468,7 @@ where suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/src/stream.rs b/crypto/cipher/src/stream.rs index 2a1446935..4447ea086 100644 --- a/crypto/cipher/src/stream.rs +++ b/crypto/cipher/src/stream.rs @@ -12,7 +12,7 @@ //! # Suspending and resuming execution //! //! [`StreamCipher`] implements [`SuspendableKeyed`], so a message in progress can be suspended to a -//! byte array and resumed later with the re-supplied key. The state is the keystream's own state +//! byte array and resumed later over the re-supplied key state. The state is the keystream's own state //! and the partly used keystream block, for any keystream that implements [`SuspendableComponent`]. //! The array length is `StreamCipher::SUSPENDED_STATE_LEN`; see [the crate //! docs](crate#suspending-and-resuming-execution) for an example. @@ -21,13 +21,11 @@ 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, + Algorithm, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; -use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{ Cursor, CursorMut, LIB_VERSION_LEN, SuspendableComponent, bounded_usize, resume_component, @@ -56,14 +54,9 @@ use core::marker::PhantomData; /// against the whole call, so a message is never half-processed before the cipher notices. Past /// that point the keystream would repeat, which is the two-time-pad failure within one message. #[derive(Clone)] -pub struct StreamCipher< - KS, - Dir, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, -> where - KS: KeyStream, +pub struct StreamCipher +where + KS: KeyStream, { keystream: KS, /// The keystream block currently being consumed. Meaningful only while `used < BLOCK_LEN`. @@ -74,18 +67,18 @@ pub struct StreamCipher< _marker: PhantomData, } -impl - StreamCipher +impl + StreamCipher where - KS: KeyStream, + KS: KeyStream, { /// 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)`). /// /// # 🚨 Security Considerations 🚨 /// This bypasses init-data generation: the keystream's nonce is whatever it was constructed - /// with, and the caller is responsible for it never repeating under the key. The - /// [`SymmetricCipherEncryptor`] constructors are the safe path. + /// with, and the caller is responsible for it never repeating under the key. A key's + /// `create_encryptor` is the safe path. pub fn from_keystream(keystream: KS) -> Self { Self::check_shape(); Self { keystream, pending: Secret::new(), used: BLOCK_LEN, _marker: PhantomData } @@ -114,6 +107,24 @@ where }; } + /// Whether `len` more bytes fit in the keystream this init data has left: the check every + /// call makes before it consumes anything, on its own for a construction that must know + /// before it copies the caller's input into an output buffer (GCM). + /// + /// # Errors + /// [`SymmetricCipherError::DataLimitExceeded`] if the call would need more keystream than + /// remains for this init data, and continuing would repeat keystream. + pub(crate) fn accepts(&self, len: usize) -> Result<(), SymmetricCipherError> { + let pending_len = BLOCK_LEN - self.used; + // Saturating: a keystream with no practical limit reports `u64::MAX` blocks. + let capacity = (pending_len as u64) + .saturating_add(self.keystream.remaining_blocks().saturating_mul(BLOCK_LEN as u64)); + if len as u64 > capacity { + return Err(SymmetricCipherError::DataLimitExceeded); + } + Ok(()) + } + /// The whole data path, shared by both directions: a keystream cipher's encryption and /// decryption are the same XOR, so there is one implementation and the direction is only a /// type. @@ -127,16 +138,8 @@ where /// [`SymmetricCipherError::DataLimitExceeded`] if the keystream cannot cover the call; nothing /// is consumed in that case. fn apply(&mut self, data: &mut [u8]) -> Result { + self.accepts(data.len())?; let pending_len = BLOCK_LEN - self.used; - // Saturating: a keystream with no practical limit reports `u64::MAX` blocks. - let capacity = (pending_len as u64) - .saturating_add(self.keystream.remaining_blocks().saturating_mul(BLOCK_LEN as u64)); - if data.len() as u64 > capacity { - // Keystream exhausted: this call would need more keystream than remains for this init - // data, and continuing would repeat keystream. - return Err(SymmetricCipherError::DataLimitExceeded); - } - let head_len = core::cmp::min(pending_len, data.len()); let (head, rest) = data.split_at_mut(head_len); for (b, k) in head.iter_mut().zip(self.pending[self.used..].iter()) { @@ -162,10 +165,10 @@ where } } -impl Algorithm - for StreamCipher +impl Algorithm + for StreamCipher where - KS: KeyStream + Algorithm, + KS: KeyStream + Algorithm, { /// The keystream's name. const ALG_NAME: &'static str = KS::ALG_NAME; @@ -173,33 +176,12 @@ where const MAX_SECURITY_STRENGTH: SecurityStrength = KS::MAX_SECURITY_STRENGTH; } -impl - SymmetricCipherEncryptor - for StreamCipher +impl + SymmetricCipherEncryptor + for StreamCipher where - KS: KeyStream + Algorithm, + KS: KeyStream + Algorithm, { - /// 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> { - let mut rng = HashDRBG_SHA512::new_from_os(); - Self::do_encrypt_init_rng(key, &mut rng) - } - - /// 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { - Self::check_shape(); - let mut init_data = [0u8; INIT_DATA_LEN]; - rng.next_bytes_out(&mut init_data)?; - let keystream = KS::new(key, &init_data)?; - Ok((Self::from_keystream(keystream), init_data)) - } - /// Every input byte produces exactly one output byte. fn do_encrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -237,11 +219,10 @@ where } } -impl - StreamCipherEncryptor - for StreamCipher +impl StreamCipherEncryptor + for StreamCipher where - KS: KeyStream + Algorithm, + KS: KeyStream + Algorithm, { /// XORs the next `data.len()` keystream bytes into `data`. /// @@ -253,22 +234,12 @@ where } } -impl - SymmetricCipherDecryptor - for StreamCipher +impl + SymmetricCipherDecryptor + for StreamCipher where - KS: KeyStream + Algorithm, + KS: KeyStream + Algorithm, { - /// Begins a decryption flow from the init data returned by - /// [`SymmetricCipherEncryptor::do_encrypt_init`]. - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result { - Self::check_shape(); - Ok(Self::from_keystream(KS::new(key, init_data)?)) - } - /// Nothing is held back, so every input byte can be released immediately. fn do_decrypt_out_len(&self, input_len: usize) -> usize { input_len @@ -306,11 +277,10 @@ where } } -impl - StreamCipherDecryptor - for StreamCipher +impl StreamCipherDecryptor + for StreamCipher where - KS: KeyStream + Algorithm, + KS: KeyStream + Algorithm, { /// The same XOR as encryption. /// @@ -321,25 +291,25 @@ where } } -impl - StreamCipher +impl<'k, KS, Dir, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> + StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent<'k>, { /// 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 /// [`bouncycastle_utils::suspendable_state`]. pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_LEN; + LIB_VERSION_LEN + >::STATE_LEN; } /// 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<'k, KS, Dir, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize> SuspendableComponent<'k> + for StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent<'k>, { const STATE_LEN: usize = KS::STATE_LEN + BLOCK_LEN + 8; type Key = KS::Key; @@ -353,7 +323,7 @@ where debug_assert!(w.is_done()); } - fn read_state(state: &[u8], key: &Self::Key) -> Result { + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { Self::check_shape(); let (ks, rest) = state.split_at(KS::STATE_LEN); let keystream = KS::read_state(ks, key)?; @@ -369,16 +339,10 @@ where } /// `N` must be [`StreamCipher::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl< - KS, - Dir, - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, - const N: usize, -> SuspendableKeyed for StreamCipher +impl<'k, KS, Dir, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, const N: usize> + SuspendableKeyed<'k, N> for StreamCipher where - KS: KeyStream + SuspendableComponent, + KS: KeyStream + SuspendableComponent<'k>, { type Key = KS::Key; @@ -386,7 +350,10 @@ where suspend_component(&self) } - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { + /// The state carries no direction: one suspended by the other direction's worker is accepted + /// and resumes over the same chaining value, which gives wrong output rather than an error. + /// Only GCM, whose state means different things per direction, refuses the other one. + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { resume_component(&state, key) } } diff --git a/crypto/cipher/tests/ccm_suspend_tests.rs b/crypto/cipher/tests/ccm_suspend_tests.rs index 49298b392..29367ed00 100644 --- a/crypto/cipher/tests/ccm_suspend_tests.rs +++ b/crypto/cipher/tests/ccm_suspend_tests.rs @@ -1,22 +1,23 @@ -//! What a resumed CCM state refuses: the bounds the pair and its packet state check when they +//! What a resumed CCM state refuses: the bounds the pair and its `Ccm` state check when they //! are rebuilt from a suspended array, each tampered with on its own. //! //! The round trips in `suspend_tests.rs` show that a faithful state resumes. These show that an //! unfaithful one does not, which is the other half of the contract and the half that pins each //! check individually: with the flags octet, the counter field, the counter index, the CBC-MAC -//! position, the owed payload, the AAD length and the phase flag all validated, a test that -//! corrupts several at once would still pass if any one check were dropped. +//! position, the owed payload, the owed AAD, the AAD length and the phase flag all validated, a +//! test that corrupts several at once would still pass if any one check were dropped. //! //! The offsets are those of the layout the `SuspendableComponent` impls write, in order: the -//! library version, the packet state (the keystream's counter template and counter index, the +//! library version, the `Ccm` state (the keystream's counter template and counter index, the //! stream cipher's pending block and its `used` count, then the chaining block, `mac_pos`, //! `aad_owed` and `owed`), the held-back AAD, its length and the phase flag; the decryptor adds //! the held-back tag and its length. Every integer is a little-endian `u64`. Spec references are //! to NIST SP 800-38C (May 2004, errata update 07-20-2007). -use bouncycastle_cipher::modes::{CCM, CcmDecryptor, CcmEncryptor}; +use bouncycastle_cipher::modes::{CCM, CCM_Packet, CcmPacketDecryptor, CcmPacketEncryptor}; use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, SymmetricCipherEncryptor, @@ -25,7 +26,11 @@ use bouncycastle_utils::suspendable_state::LIB_VERSION_LEN; #[path = "common/toy_block_cipher.rs"] mod toy_block_cipher; +#[path = "common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; use toy_block_cipher::ToyBlockCipher; +use toy_key::ToyKey; /// 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`. @@ -33,8 +38,10 @@ const NONCE_LEN: usize = 12; const BLOCK_LEN: usize = 16; const AAD_LEN: usize = 16; const DATA_LEN: usize = 32; -type ToyEnc = CcmEncryptor; -type ToyDec = CcmDecryptor; +type ToyEnc<'k> = + CcmPacketEncryptor<'k, ToyBlockCipher, BLOCK_LEN, NONCE_LEN, 16, AAD_LEN, DATA_LEN>; +type ToyDec<'k> = + CcmPacketDecryptor<'k, ToyBlockCipher, BLOCK_LEN, NONCE_LEN, 16, AAD_LEN, DATA_LEN>; const N: usize = ToyEnc::SUSPENDED_STATE_LEN; const ND: usize = ToyDec::SUSPENDED_STATE_LEN; @@ -57,10 +64,16 @@ fn key() -> KeyMaterial<16> { KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap() } +/// The key the pair is made from; its workers borrow it, and resume takes its state. +fn frame_key() -> ToyKey> { + ToyKey::from_key_material(&key()).unwrap() +} + /// An encryptor holding its AAD back, before any payload: the header is not yet formatted, so /// the CBC-MAC has absorbed nothing. fn unformatted() -> [u8; N] { - let mut enc = ToyEnc::do_encrypt_init_nonce(&key(), &[0x24u8; NONCE_LEN]).unwrap(); + let key = frame_key(); + let mut enc = key.create_encryptor_nonce(&[0x24u8; NONCE_LEN]); enc.do_update_aad(b"aad").unwrap(); enc.suspend() } @@ -68,7 +81,8 @@ fn unformatted() -> [u8; N] { /// An encryptor one block into its payload: the header is formatted, `next_ctr = 2`, the CBC-MAC /// block boundary has just been crossed, and 16 of the 32 bytes are still owed. fn mid_payload() -> [u8; N] { - let mut enc = ToyEnc::do_encrypt_init_nonce(&key(), &[0x24u8; NONCE_LEN]).unwrap(); + let key = frame_key(); + let mut enc = key.create_encryptor_nonce(&[0x24u8; NONCE_LEN]); enc.do_update_aad(b"aad").unwrap(); let mut out = [0u8; 16]; enc.do_encrypt_out(&[0x5Au8; 16], &mut out).unwrap(); @@ -81,7 +95,8 @@ fn with_u64(mut state: [u8; L], at: usize, value: u64) -> [u8; L } fn resumes(state: [u8; N]) -> Result<(), SuspendableError> { - ToyEnc::from_suspended(state, &key()).map(|_| ()) + let key = frame_key(); + ToyEnc::from_suspended(state, key.state()).map(|_| ()) } /// `mac_pos` is how much of the current CBC-MAC block has been XORed in, and a full block is @@ -171,6 +186,17 @@ fn an_unformatted_state_has_absorbed_nothing() { assert_eq!(mid_payload()[FORMATTED], 1); } +/// `aad_owed` is the AAD still to come after the header declared it, and `begin_data` declares +/// and absorbs the AAD in one step, so a packet worker never holds a non-zero count: not before +/// the header (the test above) and not after it either. +#[test] +fn no_aad_is_owed_once_the_header_is_formatted() { + let state = mid_payload(); + assert_eq!(state[FORMATTED], 1); + assert_eq!(resumes(with_u64(state, AAD_OWED, 1)), Err(SuspendableError::InvalidData)); + assert_eq!(resumes(with_u64(state, AAD_OWED, u64::MAX)), Err(SuspendableError::InvalidData)); +} + /// The held-back AAD can be no longer than the buffer that holds it. #[test] fn the_aad_length_is_held_to_its_capacity() { @@ -186,15 +212,18 @@ fn the_aad_length_is_held_to_its_capacity() { /// on the tag bytes it holds back. #[test] fn the_decrypting_direction_checks_the_same_bounds() { - let mut dec = ToyDec::do_decrypt_init(&key(), &[0x24u8; NONCE_LEN]).unwrap(); + let key = frame_key(); + let mut dec = key.create_decryptor(&[0x24u8; NONCE_LEN]); dec.do_update_aad(b"aad").unwrap(); let mut out = [0u8; 16]; dec.do_decrypt_out(&[0x5Au8; 16], &mut out).unwrap(); let state = dec.suspend(); - let resumes = |state: [u8; ND]| ToyDec::from_suspended(state, &key()).map(|_| ()); + let key = frame_key(); + let resumes = |state: [u8; ND]| ToyDec::from_suspended(state, key.state()).map(|_| ()); assert!(resumes(state).is_ok()); assert!(resumes(with_u64(state, OWED, DATA_LEN as u64 + 1)).is_err()); + assert!(resumes(with_u64(state, AAD_OWED, 1)).is_err()); assert!(resumes(with_u64(state, NEXT_CTR, 0)).is_err()); assert!(resumes(with_u64(state, TAG_LEN_AT, 16)).is_ok()); assert!(resumes(with_u64(state, TAG_LEN_AT, 17)).is_err()); diff --git a/crypto/cipher/tests/common/toy_block_cipher.rs b/crypto/cipher/tests/common/toy_block_cipher.rs index 36667a1f3..857f3a65b 100644 --- a/crypto/cipher/tests/common/toy_block_cipher.rs +++ b/crypto/cipher/tests/common/toy_block_cipher.rs @@ -16,7 +16,7 @@ use bouncycastle_cipher::modes::ModeNames; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::Algorithm; @@ -70,7 +70,9 @@ impl ElectronicCodeBook for ToyBlockCipher { bytes.copy_from_slice(key.ref_to_bytes()); Ok(Self { key: bytes }) } +} +impl BlockPermutation for ToyBlockCipher { fn encrypt_block(&self, block: &mut [u8; TOY_BLOCK_LEN]) { for (b, k) in block.iter_mut().zip(self.key.iter()) { *b = b.rotate_left(1) ^ *k; diff --git a/crypto/cipher/tests/common/toy_key.rs b/crypto/cipher/tests/common/toy_key.rs new file mode 100644 index 000000000..314aec73a --- /dev/null +++ b/crypto/cipher/tests/common/toy_key.rs @@ -0,0 +1,413 @@ +// A key object over any 16-byte toy permutation: the shape `AESKey` has in the `aes` crate, +// written here so the modes can be driven through the key path without a real cipher. The role +// impls forward to whichever mode traits `M` implements over `P`, exactly as the AES key's do. +// +// Pulled in with `#[path]` from the tests and `include!`d by the doc examples, so every item is +// `pub`, and the site that pulls it in allows the unused ones. + +use bouncycastle_cipher::modes::{GCM_NONCE_LEN, GMAC, Gmac, ModeNames}; +use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; +use bouncycastle_core::hazmat::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, AeadDecryptMode, AeadEncryptMode, + BlockDecryptMode, BlockEncryptMode, BlockPermutation, EcbDecryptMode, EcbDecryptionKey, + EcbEncryptMode, EcbEncryptionKey, ElectronicCodeBook, MacMode, Mode, ModeKey, NonceMacMode, + SymmetricDecryptMode, SymmetricEncryptMode, do_hazardous_operations, +}; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::symmetric_key::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, + BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, MacGenerationKey, MacVerificationKey, + SymmetricDecryptionKey, SymmetricEncryptionKey, +}; +use bouncycastle_core::traits::RNG; + +/// Key and block length of every toy permutation these keys are built over. +pub const TOY_KEY_LEN: usize = 16; + +/// A key over the permutation `P` for the mode `M`: the mode's per-key state and nothing else. +pub struct ToyKey> { + state: M::KeyState, +} + +impl> Clone for ToyKey { + fn clone(&self) -> Self { + Self { state: self.state.clone() } + } +} + +impl> ToyKey { + /// A key over an already-keyed permutation, for tests that build the permutation themselves. + pub fn from_perm(perm: P) -> Self { + Self { state: M::key_state(perm) } + } + + /// The mode's per-key state, for tests of the workers' `from_key_state` routes. + pub fn state(&self) -> &M::KeyState { + &self.state + } +} + +impl> ModeKey for ToyKey { + fn state(&self) -> &M::KeyState { + &self.state + } +} + +/// The permutation takes only a `SymmetricCipherKey`, so a MAC mode's `MACKey` is retagged for it +/// at the same strength, as the AES key does. +fn as_cipher_key( + key: &KeyMaterial, +) -> Result, SymmetricCipherError> { + let mut cipher_key = KeyMaterial::::from_bytes_as_type( + key.ref_to_bytes(), + KeyType::SymmetricCipherKey, + )?; + do_hazardous_operations(&mut cipher_key, |k| { + k.set_key_type(KeyType::SymmetricCipherKey)?; + k.set_security_strength(key.security_strength()) + })?; + Ok(cipher_key) +} + +impl FromKeyMaterial for ToyKey +where + P: ElectronicCodeBook, + M: Mode

, +{ + const KEY_TYPE: KeyType = M::KEY_TYPE; + + fn from_key_material(key: &KeyMaterial) -> Result { + if key.key_type() != M::KEY_TYPE { + return Err(KeyMaterialError::InvalidKeyType( + "The key is not of the KeyType this mode requires.", + ) + .into()); + } + let perm = match M::KEY_TYPE { + KeyType::SymmetricCipherKey => P::new(key)?, + _ => P::new(&as_cipher_key(key)?)?, + }; + Ok(Self { state: M::key_state(perm) }) + } +} + +// ---- the role impls, forwarded to the mode ----------------------------------------------------- + +impl + SymmetricEncryptionKey for ToyKey +where + M: SymmetricEncryptMode, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng) + } +} + +impl + SymmetricDecryptionKey for ToyKey +where + M: SymmetricDecryptMode, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state, init_data) + } +} + +impl + BlockEncryptionKey for ToyKey +where + M: BlockEncryptMode, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng) + } +} + +impl + BlockDecryptionKey for ToyKey +where + M: BlockDecryptMode, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state, init_data) + } +} + +impl EcbEncryptionKey + for ToyKey +where + M: EcbEncryptMode, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>(&'k self) -> Self::Encryptor<'k> { + M::encrypt_init(&self.state) + } +} + +impl EcbDecryptionKey + for ToyKey +where + M: EcbDecryptMode, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state) + } +} + +impl< + P, + M, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> AeadEncryptionKey for ToyKey +where + M: AeadEncryptMode, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng) + } + + fn create_encryptor_nonce<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor<'k> { + M::encrypt_init_nonce(&self.state, nonce) + } + + fn encrypt_detached_in_place( + &self, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + M::encrypt_detached_in_place(&self.state, rng, aad, buf) + } + + fn encrypt_detached_in_place_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + M::encrypt_detached_in_place_nonce(&self.state, nonce, aad, buf) + } +} + +impl< + P, + M, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> AeadDecryptionKey for ToyKey +where + M: AeadDecryptMode, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k> { + M::decrypt_init(&self.state, nonce) + } + + fn decrypt_detached_in_place( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + M::decrypt_detached_in_place(&self.state, nonce, aad, buf, tag) + } +} + +impl< + P, + M, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> AeadDeclaredEncryptionKey for ToyKey +where + M: AeadDeclaredEncryptMode, +{ + type Encryptor<'k> + = M::Encryptor<'k> + where + Self: 'k; + + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + aad_len: usize, + payload_len: usize, + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError> { + M::encrypt_init(&self.state, rng, aad_len, payload_len) + } + + fn create_encryptor_nonce<'k>( + &'k self, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError> { + M::encrypt_init_nonce(&self.state, nonce, aad_len, payload_len) + } + + fn encrypt_detached_in_place( + &self, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + M::encrypt_detached_in_place(&self.state, rng, aad, buf) + } + + fn encrypt_detached_in_place_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + M::encrypt_detached_in_place_nonce(&self.state, nonce, aad, buf) + } +} + +impl< + P, + M, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> AeadDeclaredDecryptionKey for ToyKey +where + M: AeadDeclaredDecryptMode, +{ + type Decryptor<'k> + = M::Decryptor<'k> + where + Self: 'k; + + fn create_decryptor<'k>( + &'k self, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError> { + M::decrypt_init(&self.state, nonce, aad_len, payload_len) + } + + fn decrypt_detached_in_place( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + M::decrypt_detached_in_place(&self.state, nonce, aad, buf, tag) + } +} + +impl> MacGenerationKey for ToyKey { + type Generator<'k> + = M::Worker<'k> + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + M::mac_init(&self.state) + } +} + +impl> MacVerificationKey for ToyKey { + type Verifier<'k> + = M::Worker<'k> + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + M::mac_init(&self.state) + } +} + +// ---- GMAC's pair, inherent on the key as it is on the AES key ---------------------------------- + +impl ToyKey> +where + P: BlockPermutation<16> + ModeNames + Clone, +{ + pub fn create_generator<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Gmac<'k, P, Encrypting, TAG_LEN>, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + GMAC::::generate_init(&self.state, rng) + } + + pub fn create_generator_nonce<'k>( + &'k self, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Gmac<'k, P, Encrypting, TAG_LEN> { + GMAC::::generate_init_nonce(&self.state, nonce) + } + + pub fn create_verifier<'k>( + &'k self, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Gmac<'k, P, Decrypting, TAG_LEN> { + GMAC::::verify_init(&self.state, nonce) + } +} + +/// An RNG for the tests that need one and do not care which: the OS-seeded DRBG the trait +/// constructors used to reach for themselves. +pub fn os_rng() -> bouncycastle_rng::HashDRBG_SHA512 { + bouncycastle_rng::HashDRBG_SHA512::new_from_os() +} diff --git a/crypto/cipher/tests/modes/alg_name_tests.rs b/crypto/cipher/tests/modes/alg_name_tests.rs index 6f8b69aa8..03f5b32a6 100644 --- a/crypto/cipher/tests/modes/alg_name_tests.rs +++ b/crypto/cipher/tests/modes/alg_name_tests.rs @@ -6,14 +6,17 @@ mod common; -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; use bouncycastle_cipher::modes::{ - Cbc, CcmDecryptor, CcmEncryptor, Cfb, Cfb8, Cmac, Ctr, Gcm, Gmac, ModeNames, + Blocks, CBC, CCM, CCM_Packet, CFB, CFB8, CTR, Cbc, CcmPacketDecryptor, CcmPacketEncryptor, Cfb, + Cfb8, Cmac, Ctr, Gcm, Gmac, ModeNames, }; use bouncycastle_cipher::padding::{ NoPadding, PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor, }; use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_core::hazmat::Mode; +use bouncycastle_core::key_material::KeyType; use bouncycastle_core::traits::Algorithm; use common::Toy; @@ -45,38 +48,57 @@ 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::CMAC_ALG_NAME); - check::>(Toy::GMAC_ALG_NAME); - check::>(Toy::GMAC_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::CMAC_ALG_NAME); + check::>(Toy::GMAC_ALG_NAME); + check::>(Toy::GMAC_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>>( + check::>(Toy::CTR_ALG_NAME); + check::>(Toy::CTR_ALG_NAME); + check::, PKCS7, 16, 16>>( Toy::CBC_ALG_NAME, ); - check::, PKCS7, 16, 16, 16>>( + check::, PKCS7, 16, 16>>( Toy::CBC_ALG_NAME, ); - check::, NoPadding, 16, 0, 16>>( + check::, NoPadding, 0, 16>>( Toy::ECB_ALG_NAME, ); - check::, NoPadding, 16, 0, 16>>( + check::, NoPadding, 0, 16>>( Toy::ECB_ALG_NAME, ); } + +/// A mode type asks for a cipher key, whatever the padding or frame parameters. +#[test] +fn each_mode_type_asks_for_a_cipher_key() { + fn check>(name: &str) { + assert_eq!(M::KEY_TYPE, KeyType::SymmetricCipherKey, "{name}"); + } + check::>(Toy::CBC_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::CFB8_ALG_NAME); + check::(Toy::CTR_ALG_NAME); + check::>(Toy::CTR_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); +} diff --git a/crypto/cipher/tests/modes/cbc_tests.rs b/crypto/cipher/tests/modes/cbc_tests.rs index 5e80c834b..9aa06e23c 100644 --- a/crypto/cipher/tests/modes/cbc_tests.rs +++ b/crypto/cipher/tests/modes/cbc_tests.rs @@ -6,22 +6,38 @@ mod common; -use bouncycastle_cipher::modes::Cbc; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; + +use bouncycastle_cipher::modes::{Blocks, CBC, ModeNames}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; + +type ToyCbcKey = ToyKey>; + +fn cbc_key() -> ToyCbcKey { + ToyCbcKey::from_key_material(&toy_key()).expect("the toy key") +} -type ToyCbc

= Cbc; -type SwappedCbc = Cbc; -type SwappedFourCbc = Cbc; +fn swapped_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn swapped_four_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// 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 +47,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; @@ -50,7 +66,7 @@ fn the_toy_permutation_conforms_to_the_trait() { #[test] fn cbc_conforms_to_the_block_cipher_framework() { TestFrameworkBlockCipher::new() - .test::, ToyCbc>(); + .test_key::(&cbc_key(), &mut os_rng()); } // ---- chaining and call sequencing -------------------------------------------------------- @@ -64,23 +80,22 @@ fn cbc_conforms_to_the_block_cipher_framework() { /// remainder after the pair loop, and `N = 1` skips the pair loop altogether. #[test] fn call_grouping_does_not_change_the_result() { - let key = toy_key(); + let key = cbc_key(); let plaintext: [[u8; TOY_LEN]; 8] = core::array::from_fn(|i| core::array::from_fn(|j| (i * TOY_LEN + j) as u8)); // Both encryption runs must use the same IV to be comparable, so pin it with the fixed RNG - // rather than letting `do_encrypt_init` generate a fresh one. + // rather than letting `create_encryptor` draw a fresh one. let iv: [u8; TOY_LEN] = core::array::from_fn(|i| 0xF0 ^ (i as u8)); let pinned_rng = || bouncycastle_rng::FixedSeedRNG::new(iv); // Reference: all eight blocks in one call (two fours). - let (mut enc, got_iv) = - ToyCbc::::do_encrypt_init_rng(&key, &mut pinned_rng()).unwrap(); + let (mut enc, got_iv) = key.create_encryptor(&mut pinned_rng()).unwrap(); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the IV"); let reference = enc_blocks(&mut enc, &plaintext); // The same eight blocks, grouped every way that exercises a different code path. - let (mut enc, _) = ToyCbc::::do_encrypt_init_rng(&key, &mut pinned_rng()).unwrap(); + let (mut enc, _) = key.create_encryptor(&mut pinned_rng()).unwrap(); let mut got = [[0u8; TOY_LEN]; 8]; let a = enc_blocks(&mut enc, &[plaintext[0]])[0]; // N = 1 let b = enc_blocks(&mut enc, &[plaintext[1], plaintext[2]]); // N = 2 @@ -96,12 +111,12 @@ fn call_grouping_does_not_change_the_result() { // Now the decrypt side: one call vs several groupings, all from the same ciphertext. let ct = reference; - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let all_at_once = dec_blocks(&mut dec, &ct); assert_eq!(all_at_once, plaintext); for grouping in [1usize, 2, 4] { - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let mut out = [[0u8; TOY_LEN]; 8]; let mut at = 0; while at < 8 { @@ -124,7 +139,7 @@ fn call_grouping_does_not_change_the_result() { } // N = 3 and N = 5 both leave a one-block remainder after the pair loop. - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let three = dec_blocks(&mut dec, &[ct[0], ct[1], ct[2]]); let five = dec_blocks(&mut dec, &[ct[3], ct[4], ct[5], ct[6], ct[7]]); assert_eq!(three, [plaintext[0], plaintext[1], plaintext[2]]); @@ -139,21 +154,23 @@ fn call_grouping_does_not_change_the_result() { /// path would be dead code and every claim about it would be untested. #[test] fn the_pair_path_is_really_used() { - let key = toy_key(); + let key = cbc_key(); let plaintext = [[0xA5u8; TOY_LEN], [0x5Au8; TOY_LEN]]; // The correct toy round-trips. - let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); assert_eq!(dec_blocks(&mut dec, &ct), plaintext); // The swapped-pair toy encrypts identically (encryption is serial and never pairs)... - let (mut enc, iv) = SwappedCbc::::do_encrypt_init(&key).unwrap(); + let key = swapped_key(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); // ...but decrypting the pair together must now be wrong, because the pair path is used. - let mut dec = SwappedCbc::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut dec = key.create_decryptor(&iv); assert_ne!( dec_blocks(&mut dec, &ct), plaintext, @@ -161,7 +178,8 @@ fn the_pair_path_is_really_used() { ); // Decrypting one block at a time avoids the pair path, so it is correct even for this toy. - let mut dec = SwappedCbc::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut dec = key.create_decryptor(&iv); let p0 = dec_blocks(&mut dec, &[ct[0]])[0]; let p1 = dec_blocks(&mut dec, &[ct[1]])[0]; assert_eq!([p0, p1], plaintext, "the single-block path must not pair"); @@ -176,30 +194,34 @@ fn the_pair_path_is_really_used() { /// two pairs or one at a time. Five blocks are wrong too: four, then one. #[test] fn the_four_block_path_is_really_used() { - let key = toy_key(); + let key = cbc_key(); let plaintext: [[u8; TOY_LEN]; 5] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); // The correct toy round-trips five blocks. - let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); assert_eq!(dec_blocks(&mut dec, &ct), plaintext); // The rotated-four toy encrypts identically (encryption is serial and never batches)... - let (mut enc, iv) = SwappedFourCbc::::do_encrypt_init(&key).unwrap(); + let key = swapped_four_key(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); // ...but decrypting five together must be wrong, because the first four take the four path. - let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut dec = key.create_decryptor(&iv); assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "four blocks must go through decrypt_4blocks"); // Exactly four together is wrong for the same reason. let four: [[u8; TOY_LEN]; 4] = ct[..4].try_into().unwrap(); - let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut dec = key.create_decryptor(&iv); assert_ne!(&dec_blocks(&mut dec, &four)[..], &plaintext[..4]); // Two pairs go through the pair path and are correct; so is the fifth block on its own. - let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut dec = key.create_decryptor(&iv); let first: [[u8; TOY_LEN]; 2] = ct[..2].try_into().unwrap(); let second: [[u8; TOY_LEN]; 2] = ct[2..4].try_into().unwrap(); assert_eq!( @@ -221,10 +243,10 @@ fn the_four_block_path_is_really_used() { /// permutation, and getting it wrong would mean the IV is not being XOR-ed where the spec says. #[test] fn an_iv_bit_error_flips_exactly_that_bit_of_the_first_block() { - let key = toy_key(); + let key = cbc_key(); let plaintext = [[0x00u8; TOY_LEN], [0x11u8; TOY_LEN], [0x22u8; TOY_LEN]]; - let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); for byte in 0..TOY_LEN { @@ -232,7 +254,7 @@ fn an_iv_bit_error_flips_exactly_that_bit_of_the_first_block() { let mut corrupt_iv = iv; corrupt_iv[byte] ^= 1 << bit; - let mut dec = ToyCbc::::do_decrypt_init(&key, &corrupt_iv).unwrap(); + let mut dec = key.create_decryptor(&corrupt_iv); let got = dec_blocks(&mut dec, &ct); let mut expected = plaintext; @@ -249,16 +271,16 @@ fn an_iv_bit_error_flips_exactly_that_bit_of_the_first_block() { /// the same bit positions of `Cj+1`'s decryption, leaving later blocks alone. #[test] fn a_ciphertext_bit_error_affects_only_two_blocks() { - let key = toy_key(); + let key = cbc_key(); let plaintext = [[0x00u8; TOY_LEN], [0x11u8; TOY_LEN], [0x22u8; TOY_LEN], [0x33u8; TOY_LEN]]; - let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); let mut corrupt = ct; corrupt[1][3] ^= 0b0010_0000; - let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let got = dec_blocks(&mut dec, &corrupt); assert_eq!(got[0], plaintext[0], "P1 depends only on C1 and the IV"); @@ -276,10 +298,10 @@ fn a_ciphertext_bit_error_affects_only_two_blocks() { /// repeated here because for CBC it is the single most important operational requirement. #[test] fn each_encryption_gets_a_fresh_iv() { - let key = toy_key(); + let key = cbc_key(); let mut seen = std::collections::BTreeSet::new(); for _ in 0..64 { - let (_, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); + let (_, iv) = key.create_encryptor(&mut os_rng()).unwrap(); assert!(seen.insert(iv), "IV repeated across encryptions: {iv:02x?}"); } } @@ -288,13 +310,13 @@ fn each_encryption_gets_a_fresh_iv() { /// This is the property ECB lacks and the reason CBC needs an IV at all. #[test] fn identical_plaintext_gives_different_ciphertext() { - let key = toy_key(); + let key = cbc_key(); let plaintext = [0x77u8; 2 * TOY_LEN]; let mut first = plaintext; - ToyCbc::::encrypt_inplace(&key, &mut first).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut first).unwrap(); let mut second = plaintext; - ToyCbc::::encrypt_inplace(&key, &mut second).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut second).unwrap(); assert_ne!(first, second); // ...and, within one message, two identical plaintext blocks must not give identical @@ -308,12 +330,11 @@ fn identical_plaintext_gives_different_ciphertext() { // ---- key handling ------------------------------------------------------------------------ +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. #[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()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CBC_ALG_NAME, SecurityStrength::_128bit); } /// The one-shots (`encrypt` / `decrypt` on a `[u8; LEN]`, in place) must produce exactly what the @@ -321,7 +342,7 @@ fn a_key_of_the_wrong_type_is_rejected() { /// tail) and an even one (pairs only), in both directions. #[test] fn one_shots_agree_with_the_streaming_api() { - let key = toy_key(); + let key = cbc_key(); let iv: [u8; TOY_LEN] = core::array::from_fn(|i| 0x0F ^ (i as u8)); let pinned_rng = || bouncycastle_rng::FixedSeedRNG::new(iv); @@ -330,16 +351,14 @@ fn one_shots_agree_with_the_streaming_api() { let blocks3: [[u8; TOY_LEN]; 3] = core::array::from_fn(|b| flat3[b * TOY_LEN..][..TOY_LEN].try_into().unwrap()); let (iv_a, ct_blocks) = { - let (mut enc, iv) = - ToyCbc::::do_encrypt_init_rng(&key, &mut pinned_rng()).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut pinned_rng()).unwrap(); (iv, enc_blocks(&mut enc, &blocks3)) }; let mut buf = flat3; - let (_, iv_b) = - ToyCbc::::encrypt_rng_inplace(&key, &mut pinned_rng(), &mut buf).unwrap(); + let (_, iv_b) = key.encrypt_inplace(&mut pinned_rng(), &mut buf).unwrap(); assert_eq!(iv_a, iv_b); assert_eq!(buf, *ct_blocks.as_flattened(), "3 blocks: one-shot must equal streaming"); - ToyCbc::::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + key.decrypt_inplace(&iv, &mut buf).unwrap(); assert_eq!(buf, flat3); // 4 blocks = 64 bytes: pairs only, no tail. @@ -347,20 +366,19 @@ fn one_shots_agree_with_the_streaming_api() { let blocks4: [[u8; TOY_LEN]; 4] = core::array::from_fn(|b| flat4[b * TOY_LEN..][..TOY_LEN].try_into().unwrap()); let ct_blocks = { - let (mut enc, _) = - ToyCbc::::do_encrypt_init_rng(&key, &mut pinned_rng()).unwrap(); + let (mut enc, _) = key.create_encryptor(&mut pinned_rng()).unwrap(); enc_blocks(&mut enc, &blocks4) }; let mut buf = flat4; - ToyCbc::::encrypt_rng_inplace(&key, &mut pinned_rng(), &mut buf).unwrap(); + key.encrypt_inplace(&mut pinned_rng(), &mut buf).unwrap(); assert_eq!(buf, *ct_blocks.as_flattened(), "4 blocks: one-shot must equal streaming"); - ToyCbc::::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + key.decrypt_inplace(&iv, &mut buf).unwrap(); assert_eq!(buf, flat4); // The OS-RNG variant round-trips too. let mut buf = flat3; - let (_, iv_fresh) = ToyCbc::::encrypt_inplace(&key, &mut buf).unwrap(); + let (_, iv_fresh) = key.encrypt_inplace(&mut os_rng(), &mut buf).unwrap(); assert_ne!(buf, flat3); - ToyCbc::::decrypt_inplace(&key, &iv_fresh, &mut buf).unwrap(); + key.decrypt_inplace(&iv_fresh, &mut buf).unwrap(); assert_eq!(buf, flat3); } diff --git a/crypto/cipher/tests/modes/ccm_tests.rs b/crypto/cipher/tests/modes/ccm_tests.rs index 522107126..35c275cb7 100644 --- a/crypto/cipher/tests/modes/ccm_tests.rs +++ b/crypto/cipher/tests/modes/ccm_tests.rs @@ -6,7 +6,7 @@ //! stream holds to its frame, and which entry points release unauthenticated plaintext -- //! independently of the known-answer vectors in the `aes` crate's `sp800_38c_tests.rs`, //! `ccm_bc-test-data.rs` and `ccm_wycheproof.rs`. The Appendix C file also carries the -//! fixed-frame `CcmEncryptor` / `CcmDecryptor` pair's contract, the shared framework run and the +//! fixed-frame `CcmPacketEncryptor` / `CcmPacketDecryptor` pair's contract, the shared framework run and the //! memory table, so none of those is repeated here. //! //! The one-shots are the mode's in-place pair on [`CCM`], which take any length; the streams are @@ -15,15 +15,25 @@ //! Spec references are to NIST SP 800-38C (May 2004, errata update 07-20-2007). mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{CCM, CcmDecryptor, CcmEncryptor, ModeNames}; +use bouncycastle_cipher::modes::{CCM, CCM_Packet, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::{AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook}; +use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, + FromKeyMaterial, +}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::ToyKey; /// The default shape under test: a 12-byte nonce, so `q = 3`, and a full 16-byte tag. const NONCE_LEN: usize = 12; @@ -31,10 +41,11 @@ const TAG_LEN: usize = 16; /// The streams' AAD capacity, above any AAD used here. const AAD_LEN: usize = 64; -type ToyEnc = - CcmEncryptor; -type ToyDec = - CcmDecryptor; +/// The toy key under the in-place pair, which takes any length. +type DeclaredKey

= ToyKey>; +/// The toy key under the fixed-frame streams, whose frame is `DATA_LEN`. +type FrameKey = + ToyKey>; fn pinned_nonce() -> [u8; NONCE_LEN] { core::array::from_fn(|i| 0xA0 ^ (i as u8)) @@ -44,8 +55,13 @@ fn message(len: usize) -> Vec { (0..len).map(|i| (i as u8).wrapping_mul(3).wrapping_add(1)).collect() } -fn perm>() -> P { - P::new(&toy_key()).expect("the toy key") +fn declared_key + ModeNames + Clone>() -> DeclaredKey

{ + DeclaredKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn frame_key + ModeNames + Clone, const DATA_LEN: usize>() +-> FrameKey { + FrameKey::from_key_material(&toy_key()).expect("the toy key") } /// One-shot Sec 6.1 over the toy permutation `P`, in place: the ciphertext and the tag. @@ -55,13 +71,7 @@ fn seal + ModeNames + Clone>( plaintext: &[u8], ) -> (Vec, [u8; TAG_LEN]) { let mut buf = plaintext.to_vec(); - let tag = CCM::::encrypt_detached_in_place_nonce( - &perm::

(), - nonce, - aad, - &mut buf, - ) - .unwrap(); + let tag = declared_key::

().encrypt_detached_in_place_nonce(nonce, aad, &mut buf).unwrap(); (buf, tag) } @@ -73,19 +83,23 @@ fn open + ModeNames + Clone>( tag: &[u8; TAG_LEN], ) -> Result, SymmetricCipherError> { let mut buf = ciphertext.to_vec(); - CCM::::decrypt_detached_in_place(&perm::

(), nonce, aad, &mut buf, tag)?; + declared_key::

().decrypt_detached_in_place(nonce, aad, &mut buf, tag)?; Ok(buf) } /// Sec 6.1 through the fixed-frame stream over `P`, `chunk` bytes per update. The frame is /// `DATA_LEN`, so `plaintext` must be exactly that long. -fn stream_encrypt + ModeNames, const DATA_LEN: usize>( +fn stream_encrypt< + P: ElectronicCodeBook + ModeNames + Clone, + const DATA_LEN: usize, +>( nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], chunk: usize, ) -> (Vec, [u8; TAG_LEN]) { - let mut enc = ToyEnc::::do_encrypt_init_nonce(&toy_key(), nonce).unwrap(); + let key = frame_key::(); + let mut enc = key.create_encryptor_nonce(nonce); enc.do_update_aad(aad).unwrap(); let mut out = vec![0u8; plaintext.len()]; let mut written = 0; @@ -98,14 +112,18 @@ fn stream_encrypt + ModeNames, const DAT } /// Sec 6.2 through the fixed-frame stream over `P`, `chunk` bytes per update, detached tag. -fn stream_decrypt + ModeNames, const DATA_LEN: usize>( +fn stream_decrypt< + P: ElectronicCodeBook + ModeNames + Clone, + const DATA_LEN: usize, +>( nonce: &[u8; NONCE_LEN], aad: &[u8], ciphertext: &[u8], tag: &[u8; TAG_LEN], chunk: usize, ) -> Result, SymmetricCipherError> { - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), nonce)?; + let key = frame_key::(); + let mut dec = key.create_decryptor(nonce); dec.do_update_aad(aad)?; let mut out = vec![0u8; ciphertext.len()]; let mut written = 0; @@ -181,7 +199,8 @@ fn the_pair_path_is_really_used_in_both_directions_and_the_mac_never_batches() { // Decryption: the swapped keystream recovers the wrong plaintext (Sec 6.2 step 5), which is // what the MAC then absorbs (step 9), so the tag check fails. The stream releases the // plaintext before the final, so the wrong recovery can be seen. - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut back = vec![0u8; LEN]; dec.do_decrypt_out(&ct, &mut back).unwrap(); @@ -212,7 +231,9 @@ fn the_four_block_path_is_really_used_in_both_directions() { assert_eq!(pairs_ct, ct, "pairs must not use the four path"); assert_eq!(pairs_tag, tag); - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut back = vec![0u8; LEN]; dec.do_decrypt_out(&ct, &mut back).unwrap(); @@ -244,7 +265,8 @@ fn every_split_agrees_with_the_one_shot_in_both_directions() { let (ct, tag) = seal::(&nonce, aad, &plaintext); for split in 0..=LEN { - let mut enc = ToyEnc::::do_encrypt_init_nonce(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(aad).unwrap(); let mut streamed = vec![0u8; LEN]; enc.do_encrypt_out(&plaintext[..split], &mut streamed).unwrap(); @@ -253,7 +275,9 @@ fn every_split_agrees_with_the_one_shot_in_both_directions() { assert_eq!(streamed_tag, tag, "tag, split at {split}"); assert_eq!(streamed, ct, "ciphertext, split at {split}"); - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut back = vec![0u8; LEN]; dec.do_decrypt_out(&ct[..split], &mut back).unwrap(); @@ -275,7 +299,8 @@ fn both_streams_hold_to_the_frame() { let (ct, tag) = seal::(&nonce, &[], &plaintext); // Encrypting: too much is refused, and finalizing short is refused. - let mut enc = ToyEnc::::do_encrypt_init_nonce(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut enc = key.create_encryptor_nonce(&nonce); let mut out = [0u8; 9]; assert!( matches!( @@ -292,7 +317,8 @@ fn both_streams_hold_to_the_frame() { // Decrypting: the stream's bound is the frame plus an inline tag, and a final that has not // seen the frame reports a malformed ciphertext. - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); let mut out = [0u8; LEN]; assert!( matches!( @@ -306,7 +332,9 @@ fn both_streams_hold_to_the_frame() { dec.do_decrypt_final_detached(&tag).unwrap(); assert_eq!(out, plaintext[..]); - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + + let mut dec = key.create_decryptor(&nonce); dec.do_decrypt_out(&ct[..4], &mut out).unwrap(); assert!( matches!(dec.do_decrypt_final_detached(&tag), Err(SymmetricCipherError::DecryptionFailed)), @@ -335,12 +363,11 @@ fn tag_length_changes_the_tag_but_not_the_ciphertext_and_tags_do_not_nest() { macro_rules! check_tag_len { ($t:literal) => {{ - type Mode = CCM; + let key = ToyKey::>::from_key_material(&toy_key()).unwrap(); let mut buf = plaintext.clone(); - let tag = Mode::encrypt_detached_in_place_nonce(&perm::(), &nonce, aad, &mut buf) - .unwrap(); + let tag = key.encrypt_detached_in_place_nonce(&nonce, aad, &mut buf).unwrap(); assert_eq!(buf, ct16, "ciphertext must not depend on TAG_LEN ({})", $t); - Mode::decrypt_detached_in_place(&perm::(), &nonce, aad, &mut buf, &tag).unwrap(); + key.decrypt_detached_in_place(&nonce, aad, &mut buf, &tag).unwrap(); assert_eq!(buf, plaintext, "TAG_LEN={} round trip", $t); tag.to_vec() }}; @@ -388,17 +415,16 @@ fn every_permitted_nonce_length_works() { expected_max_payload, "n = {N}: the payload limit 2^8q - 1 that q = 15 - n implies" ); - let perm = P::new(key).unwrap(); + let key = ToyKey::>::from_perm(P::new(key).unwrap()); let nonce: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(11).wrapping_add(3)); let plaintext = message(100); let mut buf = plaintext.clone(); - let tag = - Mode::::encrypt_detached_in_place_nonce(&perm, &nonce, b"aad", &mut buf).unwrap(); + let tag = key.encrypt_detached_in_place_nonce(&nonce, b"aad", &mut buf).unwrap(); assert_ne!(buf, plaintext, "nonce length {N}: must actually encrypt"); let ct = buf.clone(); - Mode::::decrypt_detached_in_place(&perm, &nonce, b"aad", &mut buf, &tag).unwrap(); + key.decrypt_detached_in_place(&nonce, b"aad", &mut buf, &tag).unwrap(); assert_eq!(buf, plaintext, "nonce length {N}: round trip"); // The last nonce octet sits right before `Q` in B0 (Table 2) and before the counter in @@ -408,7 +434,7 @@ fn every_permitted_nonce_length_works() { let mut buf = ct; assert!( matches!( - Mode::::decrypt_detached_in_place(&perm, &wrong, b"aad", &mut buf, &tag), + key.decrypt_detached_in_place(&wrong, b"aad", &mut buf, &tag), Err(SymmetricCipherError::AEADTagCheckFailed) ), "nonce length {N}: the nonce is authenticated" @@ -431,7 +457,7 @@ fn every_permitted_nonce_length_works() { /// Sec 6.2: "When the error message INVALID is returned, the payload P and the MAC T shall not /// be revealed." The in-place one-shot honours that -- the caller's buffer comes back zeroized -- /// because it has the whole ciphertext before it starts. The stream cannot: Sec 6.2 recovers `P` -/// (step 5) before it can verify it (step 10), and the fixed-frame `CcmDecryptor` releases `P` +/// (step 5) before it can verify it (step 10), and the fixed-frame `CcmPacketDecryptor` releases `P` /// as it is recovered rather than hold the frame back, so by the time the final rejects the tag /// the plaintext is already in the caller's buffer, as its docs warn. Pinning the difference /// makes it a documented property rather than an accident. @@ -446,13 +472,7 @@ fn the_one_shot_releases_nothing_on_forgery_but_the_stream_does() { // The one-shot: verify-then-return, so a forged tag leaves nothing but zeros. let mut buf = ct.clone(); assert!(matches!( - CCM::::decrypt_detached_in_place( - &perm::(), - &nonce, - b"aad", - &mut buf, - &tag - ), + declared_key::().decrypt_detached_in_place(&nonce, b"aad", &mut buf, &tag), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(buf, [0u8; LEN], "the one-shot must zeroize its buffer on a forged tag"); @@ -460,7 +480,8 @@ fn the_one_shot_releases_nothing_on_forgery_but_the_stream_does() { // The stream: the payload is released by 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. - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut streamed = [0u8; LEN]; assert_eq!(dec.do_decrypt_out(&ct, &mut streamed).unwrap(), LEN, "released mid-stream"); @@ -475,7 +496,8 @@ fn the_one_shot_releases_nothing_on_forgery_but_the_stream_does() { let mut inline = ct.clone(); inline.extend_from_slice(&tag); - let mut dec = ToyDec::::do_decrypt_init(&toy_key(), &nonce).unwrap(); + let key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut streamed = [0u8; LEN]; assert_eq!(dec.do_decrypt_out(&inline, &mut streamed).unwrap(), LEN); @@ -496,3 +518,61 @@ fn a_large_payload_round_trips_in_place() { assert_ne!(ct, plaintext, "must actually encrypt"); assert_eq!(open::(&nonce, aad, &ct, &tag).unwrap(), plaintext, "in-place round trip"); } + +/// The construction suite for both keys: the type, binding and strength policy of +/// `from_key_material`. +#[test] +fn the_keys_enforce_the_material_policy() { + let framework = TestFrameworkFromKeyMaterial::new(); + framework.test::>(Toy::CCM_ALG_NAME, SecurityStrength::_128bit); + framework.test::>(Toy::CCM_ALG_NAME, SecurityStrength::_128bit); +} + +/// A refused `do_encrypt_out` / `do_decrypt_out` on the any-length workers leaves nothing of the +/// input in the caller's buffer, and zeroes the buffer first as every `_out` method must: payload +/// offered while declared AAD is still owed, and payload past the declared length, on both sides. +/// The refusal must come before the copy; the inner `Ccm` refuses the same calls, but only after +/// the bytes have been copied. +#[test] +fn a_refused_update_leaves_no_payload_in_the_output_buffer() { + let key = declared_key::(); + let nonce = pinned_nonce(); + let payload = [0x5Au8; 16]; + + // AAD still owed: the four bytes declared have not been supplied. An empty update is still + // a no-op, not a refusal. + let mut enc = key.create_encryptor_nonce(&nonce, 4, 16).unwrap(); + let mut out = [0xEEu8; 32]; + assert_eq!(enc.do_encrypt_out(&[], &mut out).unwrap(), 0, "an empty update is accepted"); + let mut out = [0xEEu8; 32]; + assert!(matches!( + enc.do_encrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "a refused encrypt must leave only zeros behind"); + + // Past the declared payload length. + let mut enc = key.create_encryptor_nonce(&nonce, 0, 8).unwrap(); + let mut out = [0xEEu8; 32]; + assert!(matches!( + enc.do_encrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "an over-length encrypt must leave only zeros behind"); + + // The decryptor, with AAD still owed: the sixteen bytes are within the declared payload, so + // they are not held back as a tag, and must not be copied either. + let mut dec = key.create_decryptor(&nonce, 4, 16).unwrap(); + let mut out = [0xEEu8; 32]; + assert!(matches!( + dec.do_decrypt_out(&payload, &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + assert_eq!(out, [0u8; 32], "a refused decrypt must leave only zeros behind"); + + // An accepted update still zeroes the buffer past what it wrote. + let mut enc = key.create_encryptor_nonce(&nonce, 0, 16).unwrap(); + let mut out = [0xEEu8; 32]; + assert_eq!(enc.do_encrypt_out(&payload, &mut out).unwrap(), 16); + assert_eq!(out[16..], [0u8; 16], "the bytes past the ciphertext must be zeroed"); +} diff --git a/crypto/cipher/tests/modes/cfb8_tests.rs b/crypto/cipher/tests/modes/cfb8_tests.rs index ca8b75847..c2fcd7131 100644 --- a/crypto/cipher/tests/modes/cfb8_tests.rs +++ b/crypto/cipher/tests/modes/cfb8_tests.rs @@ -12,33 +12,58 @@ //! is not re-run. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{Cfb, Cfb8, ModeNames}; +use bouncycastle_cipher::modes::{CFB, CFB8, Cfb8, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_rng::FixedSeedRNG; use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; -type ToyCfb8

= Cfb8; -type SwappedCfb8 = Cfb8; -type ForwardOnlyCfb8 = Cfb8; -type SwappedFourCfb8 = Cfb8; +type ToyCfb8<'k, Dir> = Cfb8<'k, Toy, Dir, TOY_LEN>; + +type ToyCfb8Key = ToyKey; + +fn cfb8_key() -> ToyCfb8Key { + ToyCfb8Key::from_key_material(&toy_key()).expect("the toy key") +} + +/// The key the worker helpers below hand out borrowed workers over; a worker cannot outlive +/// its key, so the helpers' key lives for the whole test binary. +static KEY: std::sync::LazyLock = std::sync::LazyLock::new(cfb8_key); + +fn swapped_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn swapped_four_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn forward_only_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// `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 +71,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 { @@ -58,7 +83,7 @@ fn dec_chunked( } /// A pinned IV, so two runs are comparable. Encryption never accepts one, so it is fed through the -/// fixed-output RNG that `do_encrypt_init_rng` takes. +/// fixed-output RNG that `create_encryptor` takes. fn pinned_iv() -> [u8; TOY_LEN] { core::array::from_fn(|i| 0xF0 ^ (i as u8)) } @@ -67,15 +92,14 @@ fn pinned_rng(iv: [u8; TOY_LEN]) -> FixedSeedRNG<[u8; TOY_LEN]> { FixedSeedRNG::new(iv) } -fn pinned_encryptor(iv: [u8; TOY_LEN]) -> ToyCfb8 { - let (enc, got) = ToyCfb8::::do_encrypt_init_rng(&toy_key(), &mut pinned_rng(iv)) - .expect("encrypt init"); +fn pinned_encryptor(iv: [u8; TOY_LEN]) -> ToyCfb8<'static, Encrypting> { + let (enc, got) = KEY.create_encryptor(&mut pinned_rng(iv)).expect("encrypt init"); assert_eq!(got, iv, "the pinned RNG should reproduce the IV"); enc } -fn pinned_decryptor(iv: [u8; TOY_LEN]) -> ToyCfb8 { - ToyCfb8::::do_decrypt_init(&toy_key(), &iv).expect("decrypt init") +fn pinned_decryptor(iv: [u8; TOY_LEN]) -> ToyCfb8<'static, Decrypting> { + KEY.create_decryptor(&iv) } /// A test message of `len` bytes with no repeating structure at the block size. @@ -91,8 +115,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>(); + TestFrameworkStreamCipher::new().test_key::(&cfb8_key(), &mut os_rng()); } // ---- the spec equations ------------------------------------------------------------------- @@ -108,7 +131,7 @@ fn cfb8_conforms_to_the_stream_cipher_framework() { /// the implementation uses. /// /// This is the independent reference the mode is checked against below. It uses only -/// [`ElectronicCodeBook::encrypt_block`], because that is all the spec calls for. +/// [`BlockPermutation::encrypt_block`], because that is all the spec calls for. fn reference_cfb8(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> Vec { let mut chain = iv; // I1 = IV let mut out = Vec::with_capacity(input.len()); @@ -137,9 +160,8 @@ fn reference_cfb8(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> /// mistake preserves: `C1 = P1 XOR MSB_8(CIPH_K(IV))`, and the second input block. #[test] fn the_mode_matches_the_spec_equations() { - let key = toy_key(); let iv = pinned_iv(); - let perm = >::new(&key).unwrap(); + let perm = >::new(&toy_key()).unwrap(); for len in [1, 2, TOY_LEN - 1, TOY_LEN, TOY_LEN + 1, 3 * TOY_LEN + 5] { let plaintext = message(len); @@ -201,15 +223,14 @@ fn the_mode_matches_the_spec_equations() { /// is what catches a CFB8 that has quietly become CFB128 or vice versa. #[test] fn cfb8_is_not_cfb128() { - let key = toy_key(); + let key = cfb8_key(); let iv = pinned_iv(); let plaintext = message(2 * TOY_LEN); let cfb8 = enc(&mut pinned_encryptor(iv), &plaintext); - let (mut cfb, got) = - Cfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)) - .unwrap(); + let cfb128_key = ToyKey::::from_key_material(&toy_key()).unwrap(); + let (mut cfb, got) = cfb128_key.create_encryptor(&mut pinned_rng(iv)).unwrap(); assert_eq!(got, iv); let mut cfb128 = plaintext.clone(); cfb.do_encrypt_inplace(&mut cfb128).unwrap(); @@ -219,11 +240,11 @@ fn cfb8_is_not_cfb128() { // ...and neither can decrypt the other's ciphertext. let mut wrong = cfb128.clone(); - ToyCfb8::::decrypt_inplace(&key, &iv, &mut wrong).unwrap(); + key.decrypt_inplace(&iv, &mut wrong).unwrap(); assert_ne!(wrong, plaintext, "CFB8 must not decrypt a CFB128 ciphertext"); let mut wrong = cfb8.clone(); - Cfb::::decrypt_inplace(&key, &iv, &mut wrong).unwrap(); + cfb128_key.decrypt_inplace(&iv, &mut wrong).unwrap(); assert_ne!(wrong, plaintext, "CFB128 must not decrypt a CFB8 ciphertext"); } @@ -259,20 +280,22 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// the plain [`Toy`], otherwise the test could pass by not really encrypting anything. #[test] fn neither_direction_uses_the_inverse_cipher() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(19); - let (mut e, _) = - ForwardOnlyCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = forward_only_key(); + + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); let ct = enc(&mut e, &plaintext); // One call: four fours, then a pair, then a single byte. - let mut d = ForwardOnlyCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = forward_only_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec(&mut d, &ct), plaintext, "all paths, forward cipher only"); // Byte by byte: the single-byte path only. - let mut d = ForwardOnlyCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = forward_only_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "single-byte path, forward cipher only"); // The forward-only toy must agree with the real one, or the above proves nothing. @@ -359,28 +382,24 @@ fn call_chunking_does_not_change_the_result() { fn chunking_matches_a_single_call_at_every_batch_remainder() { fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { 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 = ToyKey::::from_perm(P::new(&key).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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .expect("encrypt init"); + let (enc, got) = + key.create_encryptor(&mut FixedSeedRNG::new(iv)).expect("encrypt init"); assert_eq!(got, iv, "{name}: the pinned RNG should reproduce the IV"); enc }; - let decryptor = || { - Cfb8::::do_decrypt_init(&key, &iv).expect("decrypt init") - }; + let decryptor = || key.create_decryptor(&iv); // The reference: the whole message in one call. let mut reference = plaintext.clone(); @@ -428,7 +447,6 @@ fn chunking_matches_a_single_call_at_every_batch_remainder() { /// bytes would also be wrong and would not distinguish the two paths. #[test] fn the_pair_path_is_really_used() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(2); @@ -437,16 +455,18 @@ fn the_pair_path_is_really_used() { assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); // The swapped-pair toy encrypts identically -- CFB8 encryption is serial and never batches. - let (mut e, _) = - SwappedCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = swapped_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the pair path"); // ...but decrypting two bytes together must now be wrong, because the pair path is used. - let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&iv); assert_ne!(dec(&mut d, &ct), plaintext, "a pair must go through encrypt_2blocks"); // One byte at a time avoids the pair path, so it is correct even for this toy. - let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "the single-byte path must not pair"); } @@ -458,7 +478,6 @@ fn the_pair_path_is_really_used() { /// batched, then one), while two bytes (a pair) or one at a time decrypt correctly. #[test] fn the_four_byte_path_is_really_used() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(5); @@ -466,29 +485,32 @@ fn the_four_byte_path_is_really_used() { assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); // The rotated-four toy encrypts identically: CFB8 encryption is serial and never batches. - let (mut e, _) = - SwappedFourCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = swapped_four_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the four path"); // ...but five bytes together must now be wrong, because the first four go through // encrypt_4blocks. - let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_ne!(dec(&mut d, &ct), plaintext, "five bytes must go through encrypt_4blocks"); // Two bytes use the pair path only, so they are correct even for this toy... let two = &ct[..2]; - let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec(&mut d, two), plaintext[..2], "pairs must not use the four path"); // ...and so is one byte at a time. - let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "the single-byte path must not batch"); } /// The one-shots must produce exactly what the streaming API produces. #[test] fn one_shots_agree_with_the_streaming_api() { - let key = toy_key(); + let key = cfb8_key(); let iv = pinned_iv(); for len in [1, 9, 2 * TOY_LEN + 3] { @@ -496,23 +518,21 @@ fn one_shots_agree_with_the_streaming_api() { let streamed = enc(&mut pinned_encryptor(iv), &plaintext); let mut buf = plaintext.clone(); - let (_, iv_b) = - ToyCfb8::::encrypt_rng_inplace(&key, &mut pinned_rng(iv), &mut buf) - .unwrap(); + let (_, iv_b) = key.encrypt_inplace(&mut pinned_rng(iv), &mut buf).unwrap(); assert_eq!(iv_b, iv); assert_eq!(buf, streamed, "len {len}: one-shot must equal streaming"); - ToyCfb8::::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + key.decrypt_inplace(&iv, &mut buf).unwrap(); assert_eq!(buf, plaintext); // The OS-RNG variant round-trips too. Whether the ciphertext *differs* from the plaintext // is only worth asserting once the message is long enough that coinciding with the // keystream by chance is negligible -- see `every_length_round_trips_without_padding`. let mut buf = plaintext.clone(); - let (_, iv_fresh) = ToyCfb8::::encrypt_inplace(&key, &mut buf).unwrap(); + let (_, iv_fresh) = key.encrypt_inplace(&mut os_rng(), &mut buf).unwrap(); if len >= 8 { assert_ne!(buf, plaintext); } - ToyCfb8::::decrypt_inplace(&key, &iv_fresh, &mut buf).unwrap(); + key.decrypt_inplace(&iv_fresh, &mut buf).unwrap(); assert_eq!(buf, plaintext); } } @@ -614,10 +634,10 @@ fn a_ciphertext_bit_error_flips_exactly_that_bit_of_its_own_byte() { /// Two encryption flows under the same key must not reuse an IV. #[test] fn each_encryption_gets_a_fresh_iv() { - let key = toy_key(); + let key = cfb8_key(); let mut seen = std::collections::BTreeSet::new(); for _ in 0..64 { - let (_, iv) = ToyCfb8::::do_encrypt_init(&key).unwrap(); + let (_, iv) = key.create_encryptor(&mut os_rng()).unwrap(); assert!(seen.insert(iv), "IV repeated across encryptions: {iv:02x?}"); } } @@ -625,13 +645,13 @@ fn each_encryption_gets_a_fresh_iv() { /// Identical plaintext under the same key must give different ciphertext, because the IV differs. #[test] fn identical_plaintext_gives_different_ciphertext() { - let key = toy_key(); + let key = cfb8_key(); let plaintext = [0x77u8; 2 * TOY_LEN]; let mut first = plaintext; - ToyCfb8::::encrypt_inplace(&key, &mut first).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut first).unwrap(); let mut second = plaintext; - ToyCfb8::::encrypt_inplace(&key, &mut second).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut second).unwrap(); assert_ne!(first, second); // ...and, within one message, a run of identical plaintext bytes must not give a run of @@ -647,12 +667,11 @@ fn identical_plaintext_gives_different_ciphertext() { // ---- key handling ------------------------------------------------------------------------ +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. #[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()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CFB8_ALG_NAME, SecurityStrength::_128bit); } // ---- every length, no padding ------------------------------------------------------------ @@ -661,11 +680,11 @@ fn a_key_of_the_wrong_type_is_rejected() { /// exactly as long as the plaintext, and no padding layer is involved. #[test] fn every_length_round_trips_without_padding() { - let key = toy_key(); + let key = cfb8_key(); for len in 0..=(2 * TOY_LEN + 1) { let plaintext = message(len); let mut data = plaintext.clone(); - let (n, iv) = ToyCfb8::::encrypt_inplace(&key, &mut data).expect("encryption"); + let (n, iv) = key.encrypt_inplace(&mut os_rng(), &mut data).expect("encryption"); assert_eq!(n, len, "len {len}: encrypt must report the number of bytes written"); assert_eq!(data.len(), len, "len {len}: the ciphertext is as long as the plaintext"); // Only meaningful once the message is long enough that agreeing with the keystream by @@ -676,7 +695,7 @@ fn every_length_round_trips_without_padding() { if len >= 8 { assert_ne!(data, plaintext, "len {len}: the data must actually be encrypted"); } - ToyCfb8::::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); + key.decrypt_inplace(&iv, &mut data).expect("decryption"); assert_eq!(data, plaintext, "len {len}: round trip"); } } diff --git a/crypto/cipher/tests/modes/cfb_tests.rs b/crypto/cipher/tests/modes/cfb_tests.rs index 8a0bbdee6..d1a2c1019 100644 --- a/crypto/cipher/tests/modes/cfb_tests.rs +++ b/crypto/cipher/tests/modes/cfb_tests.rs @@ -13,33 +13,60 @@ //! is not re-run. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{Cbc, Cfb, ModeNames}; +use bouncycastle_cipher::modes::{Blocks, CBC, CFB, Cbc, Cfb, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + BlockEncryptionKey, FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ - BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_rng::FixedSeedRNG; use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; + +type ToyCfb<'k, Dir> = Cfb<'k, Toy, Dir, TOY_LEN>; + +type ToyCfbKey = ToyKey; + +fn cfb_key() -> ToyCfbKey { + ToyCfbKey::from_key_material(&toy_key()).expect("the toy key") +} + +/// The key the worker helpers below hand out borrowed workers over; a worker cannot outlive +/// its key, so the helpers' key lives for the whole test binary. +static KEY: std::sync::LazyLock = std::sync::LazyLock::new(cfb_key); -type ToyCfb = Cfb; -type SwappedCfb = Cfb; -type ForwardOnlyCfb = Cfb; -type SwappedFourCfb = Cfb; +fn swapped_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn swapped_four_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn forward_only_key() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// `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 +74,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 +87,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 { @@ -72,7 +99,7 @@ fn dec_chunked( } /// A pinned IV, so two runs are comparable. Encryption never accepts one, so it is fed through the -/// fixed-output RNG that `do_encrypt_init_rng` takes. +/// fixed-output RNG that `create_encryptor` takes. fn pinned_iv() -> [u8; TOY_LEN] { core::array::from_fn(|i| 0xF0 ^ (i as u8)) } @@ -81,15 +108,14 @@ fn pinned_rng(iv: [u8; TOY_LEN]) -> FixedSeedRNG<[u8; TOY_LEN]> { FixedSeedRNG::new(iv) } -fn pinned_encryptor(iv: [u8; TOY_LEN]) -> ToyCfb { - let (enc, got) = ToyCfb::::do_encrypt_init_rng(&toy_key(), &mut pinned_rng(iv)) - .expect("encrypt init"); +fn pinned_encryptor(iv: [u8; TOY_LEN]) -> ToyCfb<'static, Encrypting> { + let (enc, got) = KEY.create_encryptor(&mut pinned_rng(iv)).expect("encrypt init"); assert_eq!(got, iv, "the pinned RNG should reproduce the IV"); enc } -fn pinned_decryptor(iv: [u8; TOY_LEN]) -> ToyCfb { - ToyCfb::::do_decrypt_init(&toy_key(), &iv).expect("decrypt init") +fn pinned_decryptor(iv: [u8; TOY_LEN]) -> ToyCfb<'static, Decrypting> { + KEY.create_decryptor(&iv) } /// A test message of `len` bytes with no repeating structure at the block size. @@ -105,8 +131,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>(); + TestFrameworkStreamCipher::new().test_key::(&cfb_key(), &mut os_rng()); } // ---- the spec equations ------------------------------------------------------------------- @@ -122,7 +147,7 @@ fn cfb_conforms_to_the_stream_cipher_framework() { /// after it. /// /// This is the independent reference the mode is checked against below. It uses only -/// [`ElectronicCodeBook::encrypt_block`], because that is all the spec calls for. +/// [`BlockPermutation::encrypt_block`], because that is all the spec calls for. fn reference_cfb(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> Vec { let mut chain = iv; // I1 = IV let mut out = Vec::with_capacity(input.len()); @@ -150,9 +175,8 @@ fn reference_cfb(perm: &Toy, iv: [u8; TOY_LEN], input: &[u8], encrypt: bool) -> /// keystream block itself. #[test] fn the_mode_matches_the_spec_equations() { - let key = toy_key(); let iv = pinned_iv(); - let perm = >::new(&key).unwrap(); + let perm = >::new(&toy_key()).unwrap(); for len in [5 * TOY_LEN, 5 * TOY_LEN + 9, TOY_LEN - 1, 1] { let plaintext = message(len); @@ -191,9 +215,8 @@ 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 cbc_key = ToyKey::>::from_key_material(&toy_key()).unwrap(); + let (mut cbc, _) = cbc_key.create_encryptor(&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"); @@ -208,9 +231,8 @@ fn the_mode_matches_the_spec_equations() { /// `CIPH_K(C2)`), so both the "only segment" and "final segment" cases are covered. #[test] 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(&toy_key()).unwrap(); let mut o1 = iv; perm.encrypt_block(&mut o1); @@ -274,20 +296,22 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// encrypting anything. #[test] fn neither_direction_uses_the_inverse_cipher() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(11 * TOY_LEN + 5); - let (mut e, _) = - ForwardOnlyCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = forward_only_key(); + + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); let ct = enc(&mut e, &plaintext); // One call: two fours, then a pair, then a single, then the short segment. - let mut d = ForwardOnlyCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = forward_only_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec(&mut d, &ct), plaintext, "all paths, forward cipher only"); // Byte by byte: the byte path only. - let mut d = ForwardOnlyCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = forward_only_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "byte path, forward cipher only"); // The forward-only toy must agree with the real one, or the above proves nothing. @@ -387,27 +411,24 @@ fn call_chunking_does_not_change_the_result() { fn chunking_matches_a_single_call_over_several_batches() { fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { 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 = ToyKey::::from_perm(P::new(&key).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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .expect("encrypt init"); + let (enc, got) = + key.create_encryptor(&mut FixedSeedRNG::new(iv)).expect("encrypt init"); 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 = || key.create_decryptor(&iv); // The reference: the whole message in one call. let mut reference = plaintext.clone(); @@ -455,7 +476,6 @@ fn chunking_matches_a_single_call_over_several_batches() { /// came out right, the pair path would be dead code and every claim about it would be untested. #[test] fn the_pair_path_is_really_used() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(2 * TOY_LEN); @@ -465,21 +485,24 @@ fn the_pair_path_is_really_used() { // The swapped-pair toy encrypts identically -- CFB encryption is serial and never pairs, so its // `encrypt_2blocks` override is not reached from the encryptor at all. - let (mut e, _) = - SwappedCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = swapped_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the pair path"); // ...but decrypting the pair together must now be wrong, because the pair path is used. - let mut d = SwappedCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&iv); assert_ne!(dec(&mut d, &ct), plaintext, "decrypting a pair must go through encrypt_2blocks"); // Decrypting one block at a time avoids the pair path, so it is correct even for this toy. - let mut d = SwappedCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&iv); assert_eq!(dec_chunked(&mut d, &ct, TOY_LEN), plaintext, "the single-block path must not pair"); // So does splitting the pair across a segment boundary: 5 bytes, then 27. The second call has // an 11-byte head, one whole block and no tail, so there is no pair to form. - let mut d = SwappedCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&iv); let mut got = ct.clone(); d.do_decrypt_inplace(&mut got[..5]).unwrap(); d.do_decrypt_inplace(&mut got[5..]).unwrap(); @@ -495,7 +518,6 @@ fn the_pair_path_is_really_used() { /// decrypt correctly. Encryption is serial and never batches, so it is unaffected. #[test] fn the_four_block_path_is_really_used() { - let key = toy_key(); let iv = pinned_iv(); let plaintext = message(5 * TOY_LEN); @@ -504,17 +526,19 @@ fn the_four_block_path_is_really_used() { assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); // The rotated-four toy encrypts identically: CFB encryption is serial and never batches. - let (mut e, _) = - SwappedFourCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + let key = swapped_four_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the four path"); // ...but five blocks together must now be wrong, because the first four go through // encrypt_4blocks. - let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_ne!(dec(&mut d, &ct), plaintext, "five blocks must go through encrypt_4blocks"); // Pairs use the pair path only, so they are correct even for this toy... - let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_eq!( dec_chunked(&mut d, &ct, 2 * TOY_LEN), plaintext, @@ -522,7 +546,8 @@ fn the_four_block_path_is_really_used() { ); // ...and so is one block at a time. - let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); + let key = swapped_four_key(); + let mut d = key.create_decryptor(&iv); assert_eq!( dec_chunked(&mut d, &ct, TOY_LEN), plaintext, @@ -534,7 +559,7 @@ fn the_four_block_path_is_really_used() { /// produces, for a message ending in a short segment and one that does not, in both directions. #[test] fn one_shots_agree_with_the_streaming_api() { - let key = toy_key(); + let key = cfb_key(); let iv = pinned_iv(); for len in [3 * TOY_LEN + 7, 4 * TOY_LEN] { @@ -542,18 +567,17 @@ fn one_shots_agree_with_the_streaming_api() { let streamed = enc(&mut pinned_encryptor(iv), &plaintext); let mut buf = plaintext.clone(); - let (_, iv_b) = - ToyCfb::::encrypt_rng_inplace(&key, &mut pinned_rng(iv), &mut buf).unwrap(); + let (_, iv_b) = key.encrypt_inplace(&mut pinned_rng(iv), &mut buf).unwrap(); assert_eq!(iv_b, iv); assert_eq!(buf, streamed, "len {len}: one-shot must equal streaming"); - ToyCfb::::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + key.decrypt_inplace(&iv, &mut buf).unwrap(); assert_eq!(buf, plaintext); // The OS-RNG variant round-trips too. let mut buf = plaintext.clone(); - let (_, iv_fresh) = ToyCfb::::encrypt_inplace(&key, &mut buf).unwrap(); + let (_, iv_fresh) = key.encrypt_inplace(&mut os_rng(), &mut buf).unwrap(); assert_ne!(buf, plaintext); - ToyCfb::::decrypt_inplace(&key, &iv_fresh, &mut buf).unwrap(); + key.decrypt_inplace(&iv_fresh, &mut buf).unwrap(); assert_eq!(buf, plaintext); } } @@ -683,10 +707,10 @@ fn an_iv_bit_error_damages_only_the_first_block_through_the_cipher() { /// plaintexts, not merely their equality (see the crate docs, "Key and IV reuse"). #[test] fn each_encryption_gets_a_fresh_iv() { - let key = toy_key(); + let key = cfb_key(); let mut seen = std::collections::BTreeSet::new(); for _ in 0..64 { - let (_, iv) = ToyCfb::::do_encrypt_init(&key).unwrap(); + let (_, iv) = key.create_encryptor(&mut os_rng()).unwrap(); assert!(seen.insert(iv), "IV repeated across encryptions: {iv:02x?}"); } } @@ -694,13 +718,13 @@ fn each_encryption_gets_a_fresh_iv() { /// Identical plaintext under the same key must give different ciphertext, because the IV differs. #[test] fn identical_plaintext_gives_different_ciphertext() { - let key = toy_key(); + let key = cfb_key(); let plaintext = [0x77u8; 2 * TOY_LEN]; let mut first = plaintext; - ToyCfb::::encrypt_inplace(&key, &mut first).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut first).unwrap(); let mut second = plaintext; - ToyCfb::::encrypt_inplace(&key, &mut second).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut second).unwrap(); assert_ne!(first, second); // ...and, within one message, two identical plaintext blocks must not give identical ciphertext @@ -714,12 +738,11 @@ fn identical_plaintext_gives_different_ciphertext() { // ---- key handling ------------------------------------------------------------------------ +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. #[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()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CFB_ALG_NAME, SecurityStrength::_128bit); } // ---- every length, no padding ------------------------------------------------------------ @@ -729,11 +752,11 @@ fn a_key_of_the_wrong_type_is_rejected() { /// covers the empty message, a lone short segment, exact multiples and every partial final segment. #[test] fn every_length_round_trips_without_padding() { - let key = toy_key(); + let key = cfb_key(); for len in 0..=(3 * TOY_LEN + 1) { let plaintext = message(len); let mut data = plaintext.clone(); - let (n, iv) = ToyCfb::::encrypt_inplace(&key, &mut data).expect("encryption"); + let (n, iv) = key.encrypt_inplace(&mut os_rng(), &mut data).expect("encryption"); assert_eq!(n, len, "len {len}: encrypt must report the number of bytes written"); assert_eq!(data.len(), len, "len {len}: the ciphertext is as long as the plaintext"); // Only meaningful once the message is long enough that agreeing with the keystream by @@ -744,22 +767,23 @@ fn every_length_round_trips_without_padding() { if len >= 8 { assert_ne!(data, plaintext, "len {len}: the data must actually be encrypted"); } - ToyCfb::::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); + key.decrypt_inplace(&iv, &mut data).expect("decryption"); assert_eq!(data, plaintext, "len {len}: round trip"); } } // ---- memory ------------------------------------------------------------------------------ -/// Pins the "Memory Usage" statement in the module docs -- the state is the permutation, one -/// block and a byte count -- and the claim that CFB costs one `usize` more than CBC: the block that -/// is `Ij`, `Oj` and `I_{j+1}` in turn, plus the count of how much of it has been used. +/// Pins the "Memory Usage" statement in the module docs -- the state is a reference to the +/// permutation, one block and a byte count -- and the claim that CFB costs one `usize` more than +/// CBC: the block that is `Ij`, `Oj` and `I_{j+1}` in turn, plus the count of how much of it has +/// been used. #[test] fn sizes_match_the_documented_memory_table() { use core::mem::size_of; // The general rule the docs state. - assert_eq!(size_of::>(), size_of::() + TOY_LEN + size_of::()); + assert_eq!(size_of::>(), size_of::<&Toy>() + TOY_LEN + size_of::()); // The direction marker is free, and does not change the layout. assert_eq!(size_of::>(), size_of::>()); @@ -767,6 +791,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/cmac_tests.rs b/crypto/cipher/tests/modes/cmac_tests.rs index fd447200c..b00a685d2 100644 --- a/crypto/cipher/tests/modes/cmac_tests.rs +++ b/crypto/cipher/tests/modes/cmac_tests.rs @@ -4,16 +4,28 @@ //! `aes` crate, `crypto/aes/tests/cmac_*.rs`. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{CMAC_MIN_TAG_LEN, Cmac, CmacKeyState}; -use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_cipher::modes::{CMAC, CMAC_MIN_TAG_LEN, Cmac, ModeNames}; +use bouncycastle_core::errors::{MACError, SuspendableError}; +use bouncycastle_core::hazmat::{ElectronicCodeBook, MacMode, Mode}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; +use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use common::{TOY_LEN, Toy, toy_key}; +use toy_key::ToyKey; -type ToyCmac = Cmac; +type ToyCmac<'k> = Cmac<'k, Toy, TOY_LEN>; +type ToyCmacKey = ToyKey; + +fn cmac_key() -> ToyCmacKey { + ToyCmacKey::from_key_material(&mac_key()).expect("the toy MAC key") +} const STATE_LEN: usize = ToyCmac::SUSPENDED_STATE_LEN; @@ -21,23 +33,33 @@ fn mac_key() -> KeyMaterial { KeyMaterial::::from_bytes_as_type(toy_key().ref_to_bytes(), KeyType::MACKey).unwrap() } -fn key_state() -> CmacKeyState { - CmacKeyState::new::(Toy::new(&toy_key()).unwrap()) +/// The mode's own route onto the worker, through the hazmat mode traits the key forwards to. +fn key_state() -> >::KeyState { + >::key_state(Toy::new(&toy_key()).unwrap()) +} + +fn mode_worker(state: &>::KeyState) -> ToyCmac<'_> { + >::mac_init(state) } fn message(len: usize) -> Vec { (0..len).map(|i| (i as u8).wrapping_mul(29).wrapping_add(3)).collect() } +/// The key object and the mode's own route give the same tag, and the key passes the shared MAC +/// key suite against it. #[test] -fn the_key_state_and_the_material_constructor_agree() { +fn the_key_and_the_key_state_agree() { let state = key_state(); + let key = cmac_key(); for len in [0, 1, 15, 16, 17, 32, 33, 100] { let msg = message(len); - let from_material = ToyCmac::new(&mac_key()).unwrap().mac(&msg); - let from_state = ToyCmac::from_key_state(&state).mac(&msg); - assert_eq!(from_state, from_material, "len {len}"); - assert!(ToyCmac::from_key_state(&state).verify(&msg, &from_material)); + let from_key = key.mac(&msg); + let from_state = mode_worker(&state).mac(&msg); + assert_eq!(from_state, from_key, "len {len}"); + assert!(mode_worker(&state).verify(&msg, &from_key)); + assert!(key.verify(&msg, &from_state)); + TestFrameworkMAC::new().test_mac_key(&key, &msg, &from_state); } } @@ -45,17 +67,17 @@ fn the_key_state_and_the_material_constructor_agree() { /// out the same wherever the call boundaries fall, including a boundary exactly on a block edge. #[test] fn streaming_split_points() { - let state = key_state(); + let key = cmac_key(); for mlen in [16, 20, 32, 33, 64] { let msg = message(mlen); - let expected = ToyCmac::from_key_state(&state).mac(&msg); + let expected = key.create_generator().mac(&msg); for split in 0..=mlen { - let mut cmac = ToyCmac::from_key_state(&state); + let mut cmac = key.create_generator(); cmac.do_update(&msg[..split]); cmac.do_update(&msg[split..]); assert_eq!(cmac.do_final(), expected, "mlen {mlen}, split at {split}"); } - let mut cmac = ToyCmac::from_key_state(&state); + let mut cmac = key.create_generator(); for b in &msg { cmac.do_update(core::slice::from_ref(b)); } @@ -67,98 +89,70 @@ fn streaming_split_points() { /// same message with the `10^j` padding spelled out must differ. #[test] fn a_complete_final_block_and_its_padded_twin_differ() { - let state = key_state(); + let key = cmac_key(); let short = message(20); let mut padded = short.clone(); padded.push(0x80); padded.resize(32, 0); - assert_ne!( - ToyCmac::from_key_state(&state).mac(&short), - ToyCmac::from_key_state(&state).mac(&padded) - ); - assert_ne!(ToyCmac::from_key_state(&state).mac(&[]), ToyCmac::from_key_state(&state).mac(&[0])); + assert_ne!(key.create_generator().mac(&short), key.create_generator().mac(&padded)); + assert_ne!(key.create_generator().mac(&[]), key.create_generator().mac(&[0])); } +/// The key's construction suite: a `MACKey` of the permutation's length and strength, bound to +/// the CMAC name or to nothing; a block cipher key is not a CMAC key (SP 800-38B Sec 5.2, key +/// separation). #[test] -fn key_validation() { - // A block cipher key is not a CMAC key (SP 800-38B Sec 5.2, key separation). - assert!(matches!( - ToyCmac::new_allow_weak_key(&toy_key()), - Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) - )); - - // A full-strength MAC key of the wrong length. - let key_32 = KeyMaterial::<32>::from_bytes_as_type(&[0x55; 32], KeyType::MACKey).unwrap(); - for result in [ToyCmac::new(&key_32).err(), ToyCmac::new_allow_weak_key(&key_32).err()] { - assert_eq!(result, Some(MACError::KeyMaterialError(KeyMaterialError::InvalidLength))); - } - - // A key tagged below the permutation's strength, accepted only when asked. - let mut weak = mac_key(); - weak.set_security_strength(SecurityStrength::_112bit).unwrap(); - assert!(matches!( - ToyCmac::new(&weak), - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); - assert!(ToyCmac::new_allow_weak_key(&weak).is_ok()); - - // The all-zero key is `KeyType::Zeroized`: weak, but a permitted type. - let zero = - KeyMaterial::::from_bytes_as_type(&[0u8; TOY_LEN], KeyType::MACKey).unwrap(); - assert_eq!(zero.key_type(), KeyType::Zeroized); - assert!(matches!( - ToyCmac::new(&zero), - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); - assert!(ToyCmac::new_allow_weak_key(&zero).is_ok()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CMAC_ALG_NAME, SecurityStrength::_128bit); } #[test] fn truncation() { - let state = key_state(); + let key = cmac_key(); let msg = message(20); - let full = ToyCmac::from_key_state(&state).mac(&msg); + let full = key.create_generator().mac(&msg); assert_eq!(full.len(), TOY_LEN); - assert_eq!(ToyCmac::from_key_state(&state).output_len(), TOY_LEN); + assert_eq!(key.create_generator().output_len(), TOY_LEN); let mut out = [0xAAu8; CMAC_MIN_TAG_LEN]; - assert_eq!(ToyCmac::from_key_state(&state).mac_out(&msg, &mut out).unwrap(), 8); + assert_eq!(key.create_generator().mac_out(&msg, &mut out).unwrap(), 8); assert_eq!(out[..], full[..8]); let mut long = [0xAAu8; 24]; - assert_eq!(ToyCmac::from_key_state(&state).mac_out(&msg, &mut long).unwrap(), 16); + assert_eq!(key.create_generator().mac_out(&msg, &mut long).unwrap(), 16); assert_eq!(long[..16], full[..]); assert_eq!(long[16..], [0u8; 8]); // Below 64 bits the call fails, and the buffer is left zeroized. let mut out = [0xAAu8; CMAC_MIN_TAG_LEN - 1]; assert!(matches!( - ToyCmac::from_key_state(&state).mac_out(&msg, &mut out), + key.create_generator().mac_out(&msg, &mut out), Err(MACError::InvalidLength(_)) )); assert_eq!(out, [0u8; CMAC_MIN_TAG_LEN - 1]); // `verify` wants the full tag. - assert!(ToyCmac::from_key_state(&state).verify(&msg, &full)); - assert!(!ToyCmac::from_key_state(&state).verify(&msg, &full[..15])); + assert!(key.create_verifier().verify(&msg, &full)); + assert!(!key.create_verifier().verify(&msg, &full[..15])); let mut long = full.clone(); long.push(0); - assert!(!ToyCmac::from_key_state(&state).verify(&msg, &long)); + assert!(!key.create_verifier().verify(&msg, &long)); let mut wrong = full.clone(); wrong[15] ^= 1; - assert!(!ToyCmac::from_key_state(&state).verify(&msg, &wrong)); + assert!(!key.create_verifier().verify(&msg, &wrong)); } #[test] fn suspend_resume() { - let key = mac_key(); + let key = cmac_key(); for mlen in [0, 16, 20, 64] { let msg = message(mlen); - let expected = ToyCmac::new(&key).unwrap().mac(&msg); + let expected = key.mac(&msg); for split in 0..=mlen { - let mut cmac = ToyCmac::new(&key).unwrap(); + let mut cmac = key.create_generator(); cmac.do_update(&msg[..split]); let state: [u8; STATE_LEN] = cmac.suspend(); - let mut resumed = ToyCmac::from_suspended(state, &key).unwrap(); + let mut resumed = ToyCmac::from_suspended(state, key.state()).unwrap(); resumed.do_update(&msg[split..]); assert_eq!(resumed.do_final(), expected, "mlen {mlen}, suspended at {split}"); } @@ -167,34 +161,26 @@ fn suspend_resume() { #[test] fn suspend_resume_rejects() { - let key = mac_key(); - let mut cmac = ToyCmac::new(&key).unwrap(); + let key = cmac_key(); + let mut cmac = key.create_generator(); cmac.do_update(b"some data"); let state: [u8; STATE_LEN] = cmac.suspend(); assert_eq!(STATE_LEN, 3 + 2 * TOY_LEN + 1); let mut bad_len = state; bad_len[STATE_LEN - 1] = TOY_LEN as u8 + 1; - assert_eq!(ToyCmac::from_suspended(bad_len, &key).err(), Some(SuspendableError::InvalidData)); + assert_eq!( + ToyCmac::from_suspended(bad_len, key.state()).err(), + Some(SuspendableError::InvalidData) + ); let mut full_len = state; full_len[STATE_LEN - 1] = TOY_LEN as u8; - assert!(ToyCmac::from_suspended(full_len, &key).is_ok()); + assert!(ToyCmac::from_suspended(full_len, key.state()).is_ok()); let mut no_ver = state; no_ver[..3].fill(0); assert_eq!( - ToyCmac::from_suspended(no_ver, &key).err(), + ToyCmac::from_suspended(no_ver, key.state()).err(), Some(SuspendableError::IncompatibleVersion) ); - - let wrong_len = KeyMaterial::<32>::from_bytes_as_type(&[7u8; 32], KeyType::MACKey).unwrap(); - assert_eq!( - ToyCmac::from_suspended(state, &wrong_len).err(), - Some(SuspendableError::InvalidData) - ); - assert_eq!( - ToyCmac::from_suspended(state, &toy_key()).err(), - Some(SuspendableError::InvalidData), - "a cipher key is the wrong type" - ); } diff --git a/crypto/cipher/tests/modes/common/mod.rs b/crypto/cipher/tests/modes/common/mod.rs index b195dcfb1..fae2f7476 100644 --- a/crypto/cipher/tests/modes/common/mod.rs +++ b/crypto/cipher/tests/modes/common/mod.rs @@ -20,7 +20,7 @@ use bouncycastle_cipher::modes::ModeNames; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::Algorithm; @@ -79,7 +79,9 @@ impl ElectronicCodeBook for Toy { bytes.copy_from_slice(key.ref_to_bytes()); Ok(Self { key: bytes }) } +} +impl BlockPermutation for Toy { fn encrypt_block(&self, block: &mut [u8; TOY_LEN]) { for (b, k) in block.iter_mut().zip(self.key.iter()) { *b = b.rotate_left(1) ^ *k; @@ -152,7 +154,9 @@ impl ElectronicCodeBook for SwappedPairToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } +} +impl BlockPermutation for SwappedPairToy { fn encrypt_block(&self, block: &mut [u8; TOY_LEN]) { self.inner.encrypt_block(block); } @@ -228,7 +232,9 @@ impl ElectronicCodeBook for ForwardOnlyToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } +} +impl BlockPermutation for ForwardOnlyToy { fn encrypt_block(&self, block: &mut [u8; TOY_LEN]) { self.inner.encrypt_block(block); } @@ -286,7 +292,9 @@ impl ElectronicCodeBook for SwappedFourToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } +} +impl BlockPermutation for SwappedFourToy { fn encrypt_block(&self, block: &mut [u8; TOY_LEN]) { self.inner.encrypt_block(block); } diff --git a/crypto/cipher/tests/modes/ctr_tests.rs b/crypto/cipher/tests/modes/ctr_tests.rs index c5d89d17f..fd5a2b8e5 100644 --- a/crypto/cipher/tests/modes/ctr_tests.rs +++ b/crypto/cipher/tests/modes/ctr_tests.rs @@ -22,48 +22,82 @@ //! ACVP suite supplies the actual known-answer coverage. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{Ctr, ModeNames}; +use bouncycastle_cipher::modes::{CTR, Ctr, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, }; +use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_rng::FixedSeedRNG; use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; /// 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<'k, Dir> = Ctr<'k, Toy, Dir, TOY_LEN, NONCE_LEN>; /// 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<'k, Dir> = Ctr<'k, Toy, Dir, TOY_LEN, SHORT_CTR_NONCE_LEN>; + +type ToyCtrKey = ToyKey>; + +fn ctr_key() -> ToyCtrKey { + ctr_key_n() +} + +/// The key the worker helpers below hand out borrowed workers over; a worker cannot outlive +/// its key, so the helpers' key lives for the whole test binary. +static KEY: std::sync::LazyLock = std::sync::LazyLock::new(ctr_key); + +/// The toy key under CTR at any permitted nonce length. +fn ctr_key_n() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn tiny_key() -> ToyKey> { + ctr_key_n() +} + +fn swapped_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn swapped_four_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn forward_only_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// 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 { @@ -82,15 +116,14 @@ fn pinned_rng(nonce: [u8; NONCE_LEN]) -> FixedSeedRNG<[u8; NONCE_LEN]> { FixedSeedRNG::new(nonce) } -fn pinned_encryptor(nonce: [u8; NONCE_LEN]) -> ToyCtr { - let (e, got) = - ToyCtr::::do_encrypt_init_rng(&toy_key(), &mut pinned_rng(nonce)).unwrap(); +fn pinned_encryptor(nonce: [u8; NONCE_LEN]) -> ToyCtr<'static, Encrypting> { + let (e, got) = KEY.create_encryptor(&mut pinned_rng(nonce)).unwrap(); assert_eq!(got, nonce, "the pinned RNG should reproduce the nonce"); e } -fn pinned_decryptor(nonce: [u8; NONCE_LEN]) -> ToyCtr { - ToyCtr::::do_decrypt_init(&toy_key(), &nonce).unwrap() +fn pinned_decryptor(nonce: [u8; NONCE_LEN]) -> ToyCtr<'static, Decrypting> { + KEY.create_decryptor(&nonce) } fn message(len: usize) -> Vec { @@ -103,8 +136,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>(); + TestFrameworkStreamCipher::new().test_key::(&ctr_key(), &mut os_rng()); } // ---- the spec equations ------------------------------------------------------------------- @@ -140,9 +172,8 @@ fn reference_ctr(perm: &Toy, nonce: [u8; NONCE_LEN], input: &[u8]) -> Vec { /// encrypting zeros reveals the keystream itself. #[test] fn the_mode_matches_the_spec_equations() { - let key = toy_key(); let nonce = pinned_nonce(); - let perm = >::new(&key).unwrap(); + let perm = >::new(&toy_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); @@ -215,15 +246,12 @@ fn check_counter_blocks(blocks: usize) { TOY_LEN - N } - let key = toy_key(); - let perm = >::new(&key).unwrap(); + let perm = >::new(&toy_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 key = ctr_key_n::(); + + let (mut e, got) = key.create_encryptor(&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"); @@ -316,13 +344,11 @@ fn the_f5_counter_blocks_have_the_shape_this_type_assumes() { /// happens to be a single byte. #[test] fn the_counter_limit_is_enforced() { - let key = toy_key(); let nonce: [u8; SHORT_CTR_NONCE_LEN] = core::array::from_fn(|i| 0x5A ^ (i as u8)); + let key = tiny_key(); let encryptor = || { - let (e, got) = - TinyCtr::::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(nonce)) - .unwrap(); + let (e, got) = key.create_encryptor(&mut FixedSeedRNG::new(nonce)).unwrap(); assert_eq!(got, nonce); e }; @@ -385,15 +411,11 @@ fn the_counter_limit_is_enforced() { fn the_counter_limit_is_enforced_at_two_bytes_too() { const NONCE: usize = 14; const CAPACITY: usize = 65536 * TOY_LEN; - let key = toy_key(); let nonce: [u8; NONCE] = core::array::from_fn(|i| 0x3C ^ (i as u8)); + let key = ctr_key_n::(); let encryptor = || { - let (e, got) = Ctr::::do_encrypt_init_rng( - &key, - &mut FixedSeedRNG::new(nonce), - ) - .unwrap(); + let (e, got) = key.create_encryptor(&mut FixedSeedRNG::new(nonce)).unwrap(); assert_eq!(got, nonce); e }; @@ -415,9 +437,9 @@ fn the_counter_limit_is_enforced_at_two_bytes_too() { /// rather than decrypted with repeated keystream. #[test] fn the_counter_limit_is_enforced_when_decrypting_too() { - let key = toy_key(); let nonce: [u8; SHORT_CTR_NONCE_LEN] = core::array::from_fn(|i| 0x5A ^ (i as u8)); - let mut d = TinyCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let key = tiny_key(); + let mut d = key.create_decryptor(&nonce); let mut data = vec![0u8; TINY_CAPACITY + 1]; assert!( d.do_decrypt_inplace(&mut data).is_err(), @@ -432,23 +454,25 @@ fn the_counter_limit_is_enforced_when_decrypting_too() { /// direction may reach the inverse cipher. [`ForwardOnlyToy`] panics from every inverse entry point. #[test] fn neither_direction_uses_the_inverse_cipher() { - let key = toy_key(); let nonce = pinned_nonce(); let plaintext = message(11 * TOY_LEN + 5); - let (mut e, _) = - ForwardOnlyCtr::::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(nonce)) - .unwrap(); + let key = forward_only_key(); + + let (mut e, _) = key.create_encryptor(&mut FixedSeedRNG::new(nonce)).unwrap(); let mut ct = plaintext.clone(); e.do_encrypt_inplace(&mut ct).unwrap(); - let mut d = ForwardOnlyCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let key = forward_only_key(); + + let mut d = key.create_decryptor(&nonce); let mut back = ct.clone(); d.do_decrypt_inplace(&mut back).unwrap(); assert_eq!(back, plaintext, "all paths, forward cipher only"); // Byte by byte, so the single-block path runs too. - let mut d = ForwardOnlyCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let key = forward_only_key(); + let mut d = key.create_decryptor(&nonce); let mut back = ct.clone(); for piece in back.chunks_mut(1) { d.do_decrypt_inplace(piece).unwrap(); @@ -506,29 +530,24 @@ fn call_chunking_does_not_change_the_result() { fn chunking_matches_a_single_call_over_several_batches() { fn check(name: &str) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { 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 = ToyKey::>::from_perm(P::new(&key).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( - &key, - &mut FixedSeedRNG::new(nonce), - ) - .expect("encrypt init"); + let (e, got) = + key.create_encryptor(&mut FixedSeedRNG::new(nonce)).expect("encrypt init"); assert_eq!(got, nonce, "{name}: the pinned RNG should reproduce the nonce"); e }; - let decryptor = || { - Ctr::::do_decrypt_init(&key, &nonce) - .expect("decrypt init") - }; + let decryptor = || key.create_decryptor(&nonce); let mut reference = plaintext.clone(); encryptor().do_encrypt_inplace(&mut reference).expect("one-call encryption"); @@ -568,7 +587,6 @@ fn chunking_matches_a_single_call_over_several_batches() { /// batches too, because counter blocks do not depend on cipher output (Sec 6.5). #[test] fn the_pair_path_is_really_used_in_both_directions() { - let key = toy_key(); let nonce = pinned_nonce(); let plaintext = message(2 * TOY_LEN); @@ -576,15 +594,15 @@ fn the_pair_path_is_really_used_in_both_directions() { assert_eq!(dec(&mut pinned_decryptor(nonce), &ct), plaintext); // Encryption: two blocks together must go through encrypt_2blocks, so the swapped toy differs. - let (mut e, _) = - SwappedCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + let key = swapped_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(nonce)).unwrap(); let mut swapped = plaintext.clone(); e.do_encrypt_inplace(&mut swapped).unwrap(); assert_ne!(swapped, ct, "CTR encryption must use the pair path"); // ...but one block at a time avoids it, and then it agrees with the correct toy. - let (mut e, _) = - SwappedCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + let key = swapped_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(nonce)).unwrap(); let mut single = plaintext.clone(); for piece in single.chunks_mut(TOY_LEN) { e.do_encrypt_inplace(piece).unwrap(); @@ -592,7 +610,8 @@ fn the_pair_path_is_really_used_in_both_directions() { assert_eq!(single, ct, "the single-block path must not pair"); // Decryption: the same, on the correct ciphertext. - let mut d = SwappedCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let key = swapped_key(); + let mut d = key.create_decryptor(&nonce); let mut back = ct.clone(); d.do_decrypt_inplace(&mut back).unwrap(); assert_ne!(back, plaintext, "CTR decryption must use the pair path"); @@ -601,28 +620,30 @@ fn the_pair_path_is_really_used_in_both_directions() { /// The four-block path must be taken, in both directions, and only for full fours. #[test] fn the_four_block_path_is_really_used_in_both_directions() { - let key = toy_key(); let nonce = pinned_nonce(); let plaintext = message(5 * TOY_LEN); let ct = enc(&mut pinned_encryptor(nonce), &plaintext); - let (mut e, _) = - SwappedFourCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + let key = swapped_four_key(); + + let (mut e, _) = key.create_encryptor(&mut pinned_rng(nonce)).unwrap(); let mut swapped = plaintext.clone(); e.do_encrypt_inplace(&mut swapped).unwrap(); assert_ne!(swapped, ct, "five blocks must go through encrypt_4blocks"); // Two blocks at a time uses pairs only, so the rotated-four toy is correct there. - let (mut e, _) = - SwappedFourCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + let key = swapped_four_key(); + let (mut e, _) = key.create_encryptor(&mut pinned_rng(nonce)).unwrap(); let mut pairs = plaintext.clone(); for piece in pairs.chunks_mut(2 * TOY_LEN) { e.do_encrypt_inplace(piece).unwrap(); } assert_eq!(pairs, ct, "pairs must not use the four path"); - let mut d = SwappedFourCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let key = swapped_four_key(); + + let mut d = key.create_decryptor(&nonce); let mut back = ct.clone(); d.do_decrypt_inplace(&mut back).unwrap(); assert_ne!(back, plaintext, "decryption must batch fours too"); @@ -634,23 +655,23 @@ fn the_four_block_path_is_really_used_in_both_directions() { /// security argument: a repeated nonce repeats the counter blocks and so the keystream. #[test] fn each_encryption_gets_a_fresh_nonce() { - let key = toy_key(); + let key = ctr_key(); let mut seen = std::collections::BTreeSet::new(); for _ in 0..64 { - let (_, nonce) = ToyCtr::::do_encrypt_init(&key).unwrap(); + let (_, nonce) = key.create_encryptor(&mut os_rng()).unwrap(); assert!(seen.insert(nonce), "nonce repeated across encryptions: {nonce:02x?}"); } } #[test] fn identical_plaintext_gives_different_ciphertext() { - let key = toy_key(); + let key = ctr_key(); let plaintext = [0x77u8; 2 * TOY_LEN]; let mut first = plaintext; - ToyCtr::::encrypt_inplace(&key, &mut first).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut first).unwrap(); let mut second = plaintext; - ToyCtr::::encrypt_inplace(&key, &mut second).unwrap(); + key.encrypt_inplace(&mut os_rng(), &mut second).unwrap(); assert_ne!(first, second); // ...and two identical plaintext blocks within one message differ, because the counter moves. @@ -659,12 +680,11 @@ fn identical_plaintext_gives_different_ciphertext() { // ---- key handling -------------------------------------------------------------------------- +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. #[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()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CTR_ALG_NAME, SecurityStrength::_128bit); } // ---- every length -------------------------------------------------------------------------- @@ -673,18 +693,17 @@ fn a_key_of_the_wrong_type_is_rejected() { /// plaintext. #[test] fn every_length_round_trips_without_padding() { - let key = toy_key(); + let key = ctr_key(); for len in 0..=(3 * TOY_LEN + 1) { let plaintext = message(len); let mut data = plaintext.clone(); - let (n, nonce) = - ToyCtr::::encrypt_inplace(&key, &mut data).expect("encryption"); + let (n, nonce) = key.encrypt_inplace(&mut os_rng(), &mut data).expect("encryption"); assert_eq!(n, len, "len {len}: encrypt must report the number of bytes written"); assert_eq!(data.len(), len, "len {len}: the ciphertext is as long as the plaintext"); if len >= 8 { assert_ne!(data, plaintext, "len {len}: the data must actually be encrypted"); } - ToyCtr::::decrypt_inplace(&key, &nonce, &mut data).expect("decryption"); + key.decrypt_inplace(&nonce, &mut data).expect("decryption"); assert_eq!(data, plaintext, "len {len}: round trip"); } } @@ -697,22 +716,18 @@ fn every_length_round_trips_without_padding() { #[test] fn every_permitted_nonce_length_works() { fn round_trip() { - let key = toy_key(); 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 key = ctr_key_n::(); + + let (mut e, got) = key.create_encryptor(&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_key_n::().decrypt_inplace(&nonce, &mut ct).unwrap(); assert_eq!(ct, plaintext, "nonce length {N}: round trip"); } @@ -724,15 +739,15 @@ fn every_permitted_nonce_length_works() { // ---- memory --------------------------------------------------------------------------------- -/// Pins the layout: permutation + nonce + counter (`u64`) + keystream block + the used offset, -/// rounded up to the `u64`'s alignment. +/// Pins the layout: a reference to the permutation + nonce + counter (`u64`) + keystream block + +/// the used offset, rounded up to the `u64`'s alignment. #[test] fn sizes_match_the_documented_memory_table() { use core::mem::{align_of, size_of}; assert_eq!( size_of::>(), - (size_of::() + NONCE_LEN + size_of::() + TOY_LEN + size_of::()) + (size_of::<&Toy>() + NONCE_LEN + size_of::() + TOY_LEN + size_of::()) .next_multiple_of(align_of::()) ); diff --git a/crypto/cipher/tests/modes/ecb_tests.rs b/crypto/cipher/tests/modes/ecb_tests.rs index b58988d82..1da957071 100644 --- a/crypto/cipher/tests/modes/ecb_tests.rs +++ b/crypto/cipher/tests/modes/ecb_tests.rs @@ -12,27 +12,53 @@ mod common; -use bouncycastle_cipher::modes::Cbc; -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; -use bouncycastle_cipher::padding::{PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; + +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; +use bouncycastle_cipher::modes::{Blocks, CBC, Cbc, ModeNames}; +use bouncycastle_cipher::padding::PKCS7; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::hazmat::{ + BlockPermutation, EcbDecryptionKey, EcbEncryptionKey, ElectronicCodeBook, +}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + BlockEncryptionKey, FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ BlockCipherDecryptor, BlockCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_rng::FixedSeedRNG; use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use toy_key::ToyKey; + +type ToyEcb<'k, Dir> = Ecb<'k, Toy, Dir, TOY_LEN>; +type ToyEcbKey = ToyKey>; + +fn ecb_key() -> ToyEcbKey { + ToyEcbKey::from_key_material(&toy_key()).expect("the toy key") +} -type ToyEcb = Ecb; -type SwappedEcb = Ecb; -type SwappedFourEcb = Ecb; +/// The key the worker helpers below hand out borrowed workers over; a worker cannot outlive +/// its key, so the helpers' key lives for the whole test binary. +static KEY: std::sync::LazyLock = std::sync::LazyLock::new(ecb_key); + +fn swapped_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} + +fn swapped_four_key() -> ToyKey> { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// 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<0, TOY_LEN>, plaintext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *plaintext; @@ -42,7 +68,7 @@ fn enc_blocks( /// The implementor hook `do_decrypt_blocks_inplace`, by value. fn dec_blocks( - dec: &mut impl BlockCipherDecryptor, + dec: &mut impl BlockCipherDecryptor<0, TOY_LEN>, ciphertext: &[[u8; TOY_LEN]; N], ) -> [[u8; TOY_LEN]; N] { let mut blocks = *ciphertext; @@ -50,20 +76,19 @@ fn dec_blocks( blocks } -fn encryptor() -> ToyEcb { - ToyEcb::::do_encrypt_init(&toy_key()).unwrap().0 +fn encryptor() -> ToyEcb<'static, Encrypting> { + KEY.create_encryptor() } -fn decryptor() -> ToyEcb { - ToyEcb::::do_decrypt_init(&toy_key(), &[]).unwrap() +fn decryptor() -> ToyEcb<'static, Decrypting> { + KEY.create_decryptor() } // ---- the mode against the shared framework ------------------------------------------------ #[test] fn ecb_conforms_to_the_block_cipher_framework() { - TestFrameworkBlockCipher::new() - .test::, ToyEcb>(); + TestFrameworkBlockCipher::new().test_ecb_key::(&ecb_key()); } // ---- the spec equations ------------------------------------------------------------------- @@ -96,14 +121,11 @@ fn reference_ecb(perm: &Toy, input: &[[u8; TOY_LEN]], encrypt: bool) -> Vec<[u8; /// first block, since CBC XORs the IV in before the cipher call. #[test] fn the_mode_matches_the_spec_equations() { - let key = toy_key(); - let perm = >::new(&key).unwrap(); + let perm = >::new(&toy_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)); - let (mut enc, init) = ToyEcb::::do_encrypt_init(&key).unwrap(); - assert_eq!(init, [0u8; 0], "ECB has no init data"); - let ct = enc_blocks(&mut enc, &plaintext); + let ct = enc_blocks(&mut encryptor(), &plaintext); assert_eq!( ct.to_vec(), reference_ecb(&perm, &plaintext, true), @@ -128,11 +150,8 @@ 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( - &key, - &mut FixedSeedRNG::new(iv), - ) - .unwrap(); + let cbc_key = ToyKey::>::from_key_material(&toy_key()).unwrap(); + let (mut cbc, _) = cbc_key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); let mut first = plaintext[0]; cbc.do_encrypt_blocks_inplace(first.as_chunks_mut().0).unwrap(); assert_ne!(first, ct[0], "ECB must not agree with CBC"); @@ -146,7 +165,7 @@ fn the_mode_matches_the_spec_equations() { /// counter and stop being ECB. #[test] fn ecb_is_deterministic_and_leaks_equal_blocks() { - let key = toy_key(); + let key = ecb_key(); let block = [0x5Au8; TOY_LEN]; let plaintext = [block, [0x11; TOY_LEN], block, block]; @@ -158,50 +177,19 @@ fn ecb_is_deterministic_and_leaks_equal_blocks() { assert_eq!(ct_a[0], ct_a[3]); assert_ne!(ct_a[0], ct_a[1], "different plaintext blocks give different ciphertext blocks"); - // The one-shot sees the same thing: `encrypt` returns the empty init data and is repeatable. - // (The RNG-taking one-shot is not an alternative here -- it panics; see below.) + // The one-shot sees the same thing: the ECB key path takes no RNG and returns no init data, + // so it is repeatable. let flat: [u8; 4 * TOY_LEN] = plaintext.as_flattened().try_into().unwrap(); let mut once = flat; - let (n_a, init_a): (usize, [u8; 0]) = - ToyEcb::::encrypt_inplace(&key, &mut once).unwrap(); + let n_a = key.encrypt_inplace(&mut once).unwrap(); assert_eq!(n_a, once.len(), "encrypt must report the number of bytes written"); let mut twice = flat; - let (n_b, init_b) = ToyEcb::::encrypt_inplace(&key, &mut twice).unwrap(); + let n_b = key.encrypt_inplace(&mut twice).unwrap(); assert_eq!(n_b, twice.len(), "encrypt must report the number of bytes written"); - assert_eq!(init_a, init_b); assert_eq!(once, twice, "no init data and no randomness, so the one-shot is repeatable"); assert_eq!(once, *ct_a.as_flattened()); } -/// The RNG-taking constructor must panic rather than quietly ignore the RNG. ECB has no init data -/// to generate (SP 800-38A Table D.2 lists its IV column as "Not applicable"), so a caller reaching -/// for `do_encrypt_init_rng` has mistaken ECB for a randomized mode; -/// [`BlockCipherEncryptor::do_encrypt_init_rng`]'s contract requires an implementation with -/// `INIT_DATA_LEN == 0` to say so. It is a programmer error, not bad input, hence a panic and not a -/// [`SymmetricCipherError`]. -#[test] -#[should_panic(expected = "ECB has no initialization data")] -fn the_rng_constructor_panics() { - let key = toy_key(); - let mut rng = FixedSeedRNG::new([0u8; 0]); - let _ = ToyEcb::::do_encrypt_init_rng(&key, &mut rng); -} - -/// ...and so does the one-shot provided over it: `encrypt_rng_inplace` is `do_encrypt_init_rng` -/// followed by `do_encrypt_inplace`, so it panics in the same case and for the same reason. Pinned -/// separately because it is the call a user is most likely to reach for. -#[test] -#[should_panic(expected = "ECB has no initialization data")] -fn the_rng_one_shot_panics() { - let key = toy_key(); - let mut block = [0x42u8; TOY_LEN]; - let _ = ToyEcb::::encrypt_rng_inplace( - &key, - &mut FixedSeedRNG::new([0u8; 0]), - &mut block, - ); -} - // ---- batching: pairs and fours, in both directions ---------------------------------------- /// Sec 6.1: "multiple forward cipher functions and inverse cipher functions can be computed in @@ -210,30 +198,33 @@ fn the_rng_one_shot_panics() { /// handed over singly come out right. #[test] fn the_pair_path_is_used_in_both_directions() { - let key = toy_key(); let plaintext = [[0xA5u8; TOY_LEN], [0x5Au8; TOY_LEN]]; let ct = enc_blocks(&mut encryptor(), &plaintext); // Encryption: a pair goes through encrypt_2blocks, so the swapped toy returns them swapped. - let (mut enc, _) = SwappedEcb::::do_encrypt_init(&key).unwrap(); + let key = swapped_key(); + let mut enc = key.create_encryptor(); let swapped_ct = enc_blocks(&mut enc, &plaintext); assert_eq!(swapped_ct, [ct[1], ct[0]], "encrypting a pair must go through encrypt_2blocks"); // ...and one block at a time avoids the pair path. - let (mut enc, _) = SwappedEcb::::do_encrypt_init(&key).unwrap(); + let key = swapped_key(); + let mut enc = key.create_encryptor(); assert_eq!( [enc_blocks(&mut enc, &[plaintext[0]])[0], enc_blocks(&mut enc, &[plaintext[1]])[0]], ct ); // Decryption likewise. - let mut dec = SwappedEcb::::do_decrypt_init(&key, &[]).unwrap(); + let key = swapped_key(); + let mut dec = key.create_decryptor(); assert_eq!( dec_blocks(&mut dec, &ct), [plaintext[1], plaintext[0]], "decrypting a pair must go through decrypt_2blocks" ); - let mut dec = SwappedEcb::::do_decrypt_init(&key, &[]).unwrap(); + let key = swapped_key(); + let mut dec = key.create_decryptor(); assert_eq!([dec_blocks(&mut dec, &[ct[0]])[0], dec_blocks(&mut dec, &[ct[1]])[0]], plaintext); } @@ -243,25 +234,31 @@ fn the_pair_path_is_used_in_both_directions() { /// same blocks as two pairs or singly are right. #[test] fn the_four_block_path_is_used_in_both_directions() { - let key = toy_key(); let plaintext: [[u8; TOY_LEN]; 5] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); let ct = enc_blocks(&mut encryptor(), &plaintext); assert_eq!(dec_blocks(&mut decryptor(), &ct), plaintext); - let (mut enc, _) = SwappedFourEcb::::do_encrypt_init(&key).unwrap(); + let key = swapped_four_key(); + + let mut enc = key.create_encryptor(); let rotated = enc_blocks(&mut enc, &plaintext); assert_ne!(rotated, ct, "five blocks must go through encrypt_4blocks"); assert_eq!(rotated[4], ct[4], "the fifth block goes through the single path and is right"); assert_eq!(&rotated[..4], &[ct[1], ct[2], ct[3], ct[0]], "four rotated"); - let (mut enc, _) = SwappedFourEcb::::do_encrypt_init(&key).unwrap(); + let key = swapped_four_key(); + + let mut enc = key.create_encryptor(); let a = enc_blocks(&mut enc, &[plaintext[0], plaintext[1]]); let b = enc_blocks(&mut enc, &[plaintext[2], plaintext[3]]); assert_eq!([a, b].as_flattened(), &ct[..4], "pairs use the pair path only"); - let mut dec = SwappedFourEcb::::do_decrypt_init(&key, &[]).unwrap(); + let key = swapped_four_key(); + + let mut dec = key.create_decryptor(); assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "five blocks must go through decrypt_4blocks"); - let mut dec = SwappedFourEcb::::do_decrypt_init(&key, &[]).unwrap(); + let key = swapped_four_key(); + let mut dec = key.create_decryptor(); for (c, p) in ct.iter().zip(plaintext.iter()) { assert_eq!(&dec_blocks(&mut dec, &[*c])[0], p, "the single-block path must not batch"); } @@ -298,16 +295,16 @@ fn call_grouping_does_not_change_the_result() { /// The one-shots must agree with the block-shaped hook. #[test] fn one_shots_agree_with_the_block_hook() { - let key = toy_key(); + let key = ecb_key(); let plaintext = [[0x11u8; TOY_LEN], [0x22u8; TOY_LEN], [0x33u8; TOY_LEN]]; let flat_plaintext: [u8; 3 * TOY_LEN] = plaintext.as_flattened().try_into().unwrap(); let block_ct = enc_blocks(&mut encryptor(), &plaintext); let mut buf = flat_plaintext; - let (_, init) = ToyEcb::::encrypt_inplace(&key, &mut buf).unwrap(); + key.encrypt_inplace(&mut buf).unwrap(); assert_eq!(buf, *block_ct.as_flattened(), "one-shot must equal streaming"); - ToyEcb::::decrypt_inplace(&key, &init, &mut buf).unwrap(); + key.decrypt_inplace(&mut buf).unwrap(); assert_eq!(buf, flat_plaintext); assert_eq!(dec_blocks(&mut decryptor(), &block_ct), plaintext); @@ -348,49 +345,56 @@ fn a_ciphertext_bit_error_affects_only_its_own_block() { // ---- key handling ------------------------------------------------------------------------ +/// The key's construction suite: the type, binding and strength policy of `from_key_material`, +/// for both the block-aligned and the padded key. #[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()); +fn the_keys_enforce_the_material_policy() { + let framework = TestFrameworkFromKeyMaterial::new(); + framework.test::(Toy::ECB_ALG_NAME, SecurityStrength::_128bit); + framework + .test::>>(Toy::ECB_ALG_NAME, SecurityStrength::_128bit); } // ---- 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. +/// like the other modes: `ECB` is the symmetric key path over the adapters, with its +/// `INIT_DATA_LEN` of 0 flowing through as an empty array and the RNG unused. The RNG is a +/// `FixedSeedRNG` with no bytes at all, which refuses any draw, so this also pins that the padded +/// path draws nothing. #[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 Key = ToyKey>; + type Enc<'k> = >::Encryptor<'k>; + type Dec<'k> = >::Decryptor<'k>; + let key = Key::from_key_material(&toy_key()).expect("the toy key"); for len in 0..=(3 * TOY_LEN + 1) { let plaintext: Vec = (0..len).map(|i| (i * 5 + 3) as u8).collect(); let mut ciphertext = vec![0u8; Enc::encrypt_out_len(len)]; - let (init, written) = - Enc::encrypt_out(&toy_key(), &plaintext, &mut ciphertext).expect("padded encryption"); + let (written, init) = key + .encrypt_out(&mut FixedSeedRNG::new([]), &plaintext, &mut ciphertext) + .expect("padded encryption draws nothing from the RNG"); assert_eq!(init, [0u8; 0]); assert_eq!(written, ciphertext.len(), "len {len}"); let mut recovered = vec![0u8; Dec::decrypt_out_len(written)]; - let n = Dec::decrypt_out(&toy_key(), &init, &ciphertext, &mut recovered) - .expect("padded decryption"); + let n = key.decrypt_out(&init, &ciphertext, &mut recovered).expect("padded decryption"); assert_eq!(&recovered[..n], &plaintext[..], "len {len}: round trip through PKCS7"); } } // ---- memory ------------------------------------------------------------------------------ -/// Pins the module docs' claim that an ECB value is exactly the permutation: -/// `size_of::>() == size_of::

()`. +/// Pins the module docs' claim that an ECB value is a reference to the permutation and nothing +/// else. #[test] fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), size_of::()); + assert_eq!(size_of::>(), size_of::<&Toy>()); assert_eq!(size_of::>(), size_of::>()); // 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 8bf7efdf3..424af4ea4 100644 --- a/crypto/cipher/tests/modes/gcm_tests.rs +++ b/crypto/cipher/tests/modes/gcm_tests.rs @@ -7,17 +7,30 @@ //! `gcm_bc_java_tests.rs`. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{Gcm, ModeNames}; +use bouncycastle_cipher::modes::{GCM, GCM_NONCE_LEN, Gcm, ModeNames}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle_core::traits::{ - AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + AEADCipherDecryptor, AEADCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, }; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_rng::FixedSeedRNG; +use bouncycastle_utils::suspendable_state::LIB_VERSION_LEN; use common::{ForwardOnlyToy, TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; -type ToyGcm = Gcm; +type ToyGcmKey = ToyKey>; + +fn gcm_key() -> ToyGcmKey { + ToyGcmKey::from_key_material(&toy_key()).expect("the toy key") +} /// 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. @@ -27,14 +40,9 @@ fn toy_encrypt( seed: [u8; 12], ) -> ([u8; 12], Vec, [u8; TAG_LEN]) { let mut ct = vec![0u8; message.len()]; - let (nonce, _, tag) = ToyGcm::::encrypt_detached_rng_out( - &toy_key(), - &mut FixedSeedRNG::new(seed), - aad, - message, - &mut ct, - ) - .unwrap(); + let (_, nonce, tag) = gcm_key::() + .encrypt_detached_out(&mut FixedSeedRNG::new(seed), aad, message, &mut ct) + .unwrap(); (nonce, ct, tag) } @@ -42,8 +50,8 @@ fn toy_encrypt( /// after data has started is refused, while an empty one is always accepted as a no-op. #[test] fn aad_after_data_is_a_state_error_unless_empty() { - let key = toy_key(); - let (mut enc, _nonce) = ToyGcm::::do_encrypt_init(&key).unwrap(); + let key = gcm_key::<16>(); + let (mut enc, _nonce) = key.create_encryptor(&mut os_rng()).unwrap(); enc.do_update_aad(b"header").unwrap(); let mut out = [0u8; 8]; enc.do_encrypt_out(&[0x11u8; 8], &mut out).unwrap(); @@ -62,8 +70,8 @@ fn aad_after_data_is_a_state_error_unless_empty() { /// must be refused all the same, or it would be absorbed as if it came before the ciphertext. #[test] fn aad_after_held_back_data_is_still_a_state_error() { - let key = toy_key(); - let mut dec = ToyGcm::::do_decrypt_init(&key, &[0u8; 12]).unwrap(); + let key = gcm_key::<16>(); + let mut dec = key.create_decryptor(&[0u8; 12]); let mut nothing = [0u8; 0]; assert_eq!(dec.do_decrypt_out(&[0x22u8; 5], &mut nothing).unwrap(), 0, "all held back"); match dec.do_update_aad(b"too late") { @@ -76,7 +84,7 @@ fn aad_after_held_back_data_is_still_a_state_error() { /// must give the same ciphertext and tag as absorbing each in one call. #[test] fn chunking_is_independent_for_aad_and_data() { - let key = toy_key(); + let key = gcm_key::<16>(); let aad: [u8; 40] = core::array::from_fn(|i| i as u8); let message: [u8; 50] = core::array::from_fn(|i| (i as u8).wrapping_mul(3).wrapping_add(1)); let seed = [0x5Au8; 12]; @@ -84,9 +92,7 @@ fn chunking_is_independent_for_aad_and_data() { for aad_split in [0usize, 1, 17, 40] { for data_split in [0usize, 1, 23, 50] { - let (mut enc, got_nonce) = - ToyGcm::::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(seed)) - .unwrap(); + let (mut enc, got_nonce) = key.create_encryptor(&mut FixedSeedRNG::new(seed)).unwrap(); assert_eq!(got_nonce, nonce); enc.do_update_aad(&aad[..aad_split]).unwrap(); enc.do_update_aad(&aad[aad_split..]).unwrap(); @@ -104,7 +110,6 @@ fn chunking_is_independent_for_aad_and_data() { /// for the same inputs -- Algorithm 4 step 6's `T = MSB_t(...)`. #[test] fn tag_length_variants_round_trip_and_nest() { - let key = toy_key(); let aad = b"associated"; let message = *b"a toy message, sixteen+"; let seed = [0x6Bu8; 12]; @@ -121,8 +126,7 @@ fn tag_length_variants_round_trip_and_nest() { $n ); let mut pt = [0u8; 23]; - ToyGcm::::decrypt_detached_out(&key, &nonce, aad, &ct, &tag, &mut pt) - .unwrap(); + gcm_key::<$n>().decrypt_detached_out(&nonce, aad, &ct, &tag, &mut pt).unwrap(); assert_eq!(pt, message); }}; } @@ -137,14 +141,13 @@ fn tag_length_variants_round_trip_and_nest() { /// bytes of ciphertext against it verifies. Sec 5.2: GMAC is GCM restricted to `P = ""`. #[test] fn an_aad_only_message_is_gmac() { - let key = toy_key(); + let key = gcm_key::<16>(); let aad = b"the whole message is AAD"; let (nonce, _, tag) = toy_encrypt::<16>(aad, &[], [0x7Cu8; 12]); - ToyGcm::::decrypt_detached_out(&key, &nonce, aad, &[], &tag, &mut []).unwrap(); + key.decrypt_detached_out(&nonce, aad, &[], &tag, &mut []).unwrap(); // Wrong AAD must fail verification. - match ToyGcm::::decrypt_detached_out(&key, &nonce, b"wrong", &[], &tag, &mut []) - { + match key.decrypt_detached_out(&nonce, b"wrong", &[], &tag, &mut []) { Err(SymmetricCipherError::AEADTagCheckFailed) => {} other => panic!("expected AEADTagCheckFailed, got {other:?}"), } @@ -154,16 +157,16 @@ fn an_aad_only_message_is_gmac() { /// shorter than `TAG_LEN` is `DecryptionFailed`. #[test] fn inline_decryptor_handles_short_and_tag_only_input() { - let key = toy_key(); + let key = gcm_key::<16>(); let (nonce, _, tag) = toy_encrypt::<16>(b"", &[], [0x8Du8; 12]); let mut plaintext = [0u8; 16]; - let n = ToyGcm::::decrypt_out(&key, &nonce, &tag, &mut plaintext).unwrap(); + let n = key.decrypt_with_aad_out(&nonce, &[], &tag, &mut plaintext).unwrap(); assert_eq!(n, 0, "a tag-only input releases no plaintext"); for short_len in 0..16 { let short = &tag[..short_len]; - match ToyGcm::::decrypt_out(&key, &nonce, short, &mut plaintext) { + match key.decrypt_with_aad_out(&nonce, &[], short, &mut plaintext) { Err(SymmetricCipherError::DecryptionFailed) => {} other => panic!("len {short_len}: expected DecryptionFailed, got {other:?}"), } @@ -174,14 +177,14 @@ fn inline_decryptor_handles_short_and_tag_only_input() { /// the tail hold-back boundary. #[test] fn update_out_len_is_exact_across_irregular_chunking() { - let key = toy_key(); + let key = gcm_key::<16>(); let message: [u8; 64] = core::array::from_fn(|i| i as u8); let (nonce, ct, tag) = toy_encrypt::<16>(b"aad", &message, [0x9Eu8; 12]); let mut full_ct = [0u8; 80]; full_ct[..64].copy_from_slice(&ct); full_ct[64..].copy_from_slice(&tag); - let mut dec = ToyGcm::::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut released = 0usize; for chunk in [1usize, 15, 16, 17, 31] { @@ -208,16 +211,14 @@ fn update_out_len_is_exact_across_irregular_chunking() { /// nature) has already released plaintext before the forgery is detected. Pinning the difference. #[test] fn one_shot_releases_nothing_on_forgery_but_streaming_does() { - let key = toy_key(); + let key = gcm_key::<16>(); let message = *b"do not trust me yet"; let (nonce, ct, mut tag) = toy_encrypt::<16>(b"aad", &message, [0xAFu8; 12]); tag[0] ^= 0xFF; // forge it - // One-shot: verify-then-decrypt, so a forged tag leaves nothing but zeros behind. + // One-shot: a forged tag leaves nothing but zeros behind. let mut one_shot_buf = [0xEEu8; 19]; - match ToyGcm::::decrypt_detached_out( - &key, &nonce, b"aad", &ct, &tag, &mut one_shot_buf, - ) { + match key.decrypt_detached_out(&nonce, b"aad", &ct, &tag, &mut one_shot_buf) { Err(SymmetricCipherError::AEADTagCheckFailed) => {} other => panic!("expected AEADTagCheckFailed, got {other:?}"), } @@ -225,7 +226,7 @@ fn one_shot_releases_nothing_on_forgery_but_streaming_does() { // Streaming: everything but the held-back last 16 bytes has already been released as // plaintext by the time the final call rejects the tag. - let mut dec = ToyGcm::::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); let mut streaming_buf = [0u8; 19]; let released = dec.do_decrypt_out(&ct, &mut streaming_buf).unwrap(); @@ -241,12 +242,12 @@ fn one_shot_releases_nothing_on_forgery_but_streaming_does() { /// checks the tag, so on a forged tag it must zeroize that buffer before returning the error. #[test] fn streaming_detached_final_zeroizes_its_buffer_on_forgery() { - let key = toy_key(); + let key = ToyGcmKey::<16>::from_key_material(&toy_key()).unwrap(); let message = *b"do not trust me yet"; let (nonce, ct, mut tag) = toy_encrypt::<16>(b"aad", &message, [0xAFu8; 12]); tag[0] ^= 0xFF; // forge it - let mut dec = ToyGcm::::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); dec.do_decrypt_out(&ct, &mut [0u8; 19]).unwrap(); // Pre-filled with a non-zero byte, so a buffer the decryptor never wrote is not mistaken for @@ -268,27 +269,19 @@ 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 + Clone, { - let key = toy_key(); + let key = ToyKey::>::from_key_material(&toy_key()).unwrap(); 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::new([0x4Du8; 12]), - aad, - message, - &mut ct, - ) - .unwrap(); + let (_, nonce, tag) = key + .encrypt_detached_out(&mut FixedSeedRNG::new([0x4Du8; 12]), aad, message, &mut ct) + .unwrap(); assert_ne!(&ct[..], &message[..]); let mut pt = [0u8; 48]; - Gcm::::decrypt_detached_out( - &key, &nonce, aad, &ct, &tag, &mut pt, - ) - .unwrap(); + key.decrypt_detached_out(&nonce, aad, &ct, &tag, &mut pt).unwrap(); assert_eq!(&pt[..], &message[..]); (ct, tag) } @@ -297,40 +290,38 @@ fn neither_direction_uses_the_inverse_cipher() { assert_eq!(round_trip::(), round_trip::(), "the two toys must agree"); } -/// The whole [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] contract -- which runs the -/// symmetric-cipher suite first -- through the shared framework, over the toy at both ends of the -/// tag-length range. `FINAL_LEN` is `TAG_LEN`: GCM holds nothing back on encryption and exactly -/// the possible tag on decryption. -/// -/// [`AEADCipherEncryptor`]: bouncycastle_core::traits::AEADCipherEncryptor -/// [`AEADCipherDecryptor`]: bouncycastle_core::traits::AEADCipherDecryptor +/// The whole AEAD key contract -- which runs the symmetric-cipher suite first -- through the +/// shared framework, over the toy at both ends of the tag-length range. `FINAL_LEN` is +/// `TAG_LEN`: GCM holds nothing back on encryption and exactly the possible tag on decryption. #[test] -fn aead_trait_framework() { +fn aead_key_framework() { use bouncycastle_core_test_framework::aead::TestFrameworkAEADCipher; TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::, ToyGcm>( - ); + .test_key::(&gcm_key::<16>(), &mut os_rng()); TestFrameworkAEADCipher::new() - .test_encryptor_decryptor::, ToyGcm>( - ); + .test_key::(&gcm_key::<12>(), &mut os_rng()); } -/// The trait one-shots check the tag before decrypting anything, as the inherent -/// `decrypt_detached` does, and on a forgery leave the caller's buffer zeroized -- the trait -/// contract -- rather than holding the ciphertext they staged there. +/// The key's construction suite: the type, binding and strength policy of `from_key_material`. #[test] -fn aead_trait_one_shots_release_nothing_on_forgery() { - type Enc = ToyGcm; - type Dec = ToyGcm; +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::GCM_ALG_NAME, SecurityStrength::_128bit); +} - let key = toy_key(); +/// The key's one-shots leave the caller's buffer zeroized on a forgery -- the role contract -- +/// rather than holding the ciphertext they staged there. +#[test] +fn the_key_one_shots_release_nothing_on_forgery() { + let key = gcm_key::<16>(); let mut ct = [0u8; 32 + 16]; - let (nonce, n) = Enc::encrypt_with_aad_out(&key, b"aad", &[0x33u8; 32], &mut ct).unwrap(); + let (n, nonce) = + key.encrypt_with_aad_out(&mut os_rng(), b"aad", &[0x33u8; 32], &mut ct).unwrap(); ct[0] ^= 1; let mut out = [0xEEu8; 32]; assert!(matches!( - Dec::decrypt_with_aad_out(&key, &nonce, b"aad", &ct[..n], &mut out), + key.decrypt_with_aad_out(&nonce, b"aad", &ct[..n], &mut out), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(out, [0u8; 32], "decrypt_with_aad_out must zeroize on a failed tag check"); @@ -338,15 +329,168 @@ 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( - &key, - &nonce, - b"aad", - &ct[..32], - &tag, - &mut out - ), + key.decrypt_detached_out(&nonce, b"aad", &ct[..32], &tag, &mut out), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(out, [0u8; 32], "decrypt_detached_out must zeroize on a failed tag check"); } + +/// A suspended state carries the direction it was suspended in, and the other direction refuses +/// it: an encryptor's state cannot continue as a decryptor, whose hold-back and tag check it +/// never went through, nor the reverse. +#[test] +fn a_state_resumes_only_in_its_own_direction() { + const N: usize = Gcm::<'static, Toy, Encrypting, 16>::SUSPENDED_STATE_LEN; + let key = gcm_key::<16>(); + + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).unwrap(); + enc.do_update_aad(b"header").unwrap(); + let state: [u8; N] = enc.suspend(); + assert!(matches!( + as SuspendableKeyed<'_, N>>::from_suspended( + state, + key.state() + ), + Err(SuspendableError::InvalidData) + )); + let enc = as SuspendableKeyed<'_, N>>::from_suspended( + state, + key.state(), + ) + .expect("its own direction"); + let _ = enc.do_encrypt_final_detached().unwrap(); + + let mut dec = key.create_decryptor(&nonce); + dec.do_update_aad(b"header").unwrap(); + let state: [u8; N] = dec.suspend(); + assert!(matches!( + as SuspendableKeyed<'_, N>>::from_suspended( + state, + key.state() + ), + Err(SuspendableError::InvalidData) + )); + as SuspendableKeyed<'_, N>>::from_suspended(state, key.state()) + .expect("its own direction"); +} + +// The suspended layout, in order: the library version, the CTR half (the nonce, the next counter +// value, the pending keystream block and its used count), the GHASH state, the AAD and data byte +// counts, the call-state byte, the held-back tail and its length. Every integer is a little-endian +// `u64`. The offsets below are the ones the tests patch. +const GCM_N: usize = Gcm::<'static, Toy, Encrypting, 16>::SUSPENDED_STATE_LEN; +const NEXT_COUNTER_AT: usize = LIB_VERSION_LEN + 12; +const DATA_LEN_AT: usize = GCM_N - 8 - 16 - 1 - 8; +const AAD_LEN_AT: usize = DATA_LEN_AT - 8; + +/// SP 800-38D Sec 5.2.1.1's `len(A) <= 2^64 - 1` and `len(P) <= 2^39 - 256` bits, in bytes. +const MAX_AAD_LEN: u64 = (1 << 61) - 1; +const MAX_DATA_LEN: u64 = (1 << 36) - 32; + +fn with_u64(mut state: [u8; GCM_N], at: usize, value: u64) -> [u8; GCM_N] { + state[at..at + 8].copy_from_slice(&value.to_le_bytes()); + state +} + +/// An encryptor that has absorbed AAD and nothing else, so its AAD phase is still open and its +/// counter is at `inc32(J0)`, with the key it borrows. +fn aad_only_encryptor(key: &ToyGcmKey) -> (Gcm<'_, Toy, Encrypting, 16>, [u8; GCM_NONCE_LEN]) { + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).unwrap(); + enc.do_update_aad(b"header").unwrap(); + (enc, nonce) +} + +/// The two byte counts resume only within Sec 5.2.1.1's limits, where the length block's +/// multiplication by 8 cannot overflow, and the live path holds the same bounds: at the limit the +/// final completes and one more byte is refused. +#[test] +fn the_length_counts_are_held_to_the_spec_limits_on_resume_and_live() { + let key = gcm_key::<16>(); + let (enc, _) = aad_only_encryptor(&key); + let state: [u8; GCM_N] = enc.suspend(); + let resume = |state| { + as SuspendableKeyed<'_, GCM_N>>::from_suspended( + state, + key.state(), + ) + }; + + assert!(matches!( + resume(with_u64(state, AAD_LEN_AT, MAX_AAD_LEN + 1)), + Err(SuspendableError::InvalidData) + )); + let mut at_limit = resume(with_u64(state, AAD_LEN_AT, MAX_AAD_LEN)).expect("the limit itself"); + assert!(at_limit.do_update_aad(b"").is_ok()); + assert!(matches!(at_limit.do_update_aad(b"x"), Err(SymmetricCipherError::StateError(_)))); + let _ = at_limit.do_encrypt_final().expect("the length block is formed without overflow"); + + assert!(matches!( + resume(with_u64(state, DATA_LEN_AT, MAX_DATA_LEN + 1)), + Err(SuspendableError::InvalidData) + )); + let mut at_limit = + resume(with_u64(state, DATA_LEN_AT, MAX_DATA_LEN)).expect("the limit itself"); + let mut out = [0xEEu8; 1]; + assert!(matches!( + at_limit.do_encrypt_out(&[0x41], &mut out), + Err(SymmetricCipherError::DataLimitExceeded) + )); + assert_eq!(out, [0], "a refused call leaves the output zeroed"); + let _ = at_limit.do_encrypt_final().expect("the length block is formed without overflow"); +} + +/// A call the keystream cannot cover is refused before the caller's input is copied into the +/// output, so the buffer holds zeros rather than the plaintext, and the state is untouched: the +/// bytes that do fit still go through afterwards. +#[test] +fn a_call_past_the_keystream_limit_leaves_no_plaintext_in_the_output() { + let key = gcm_key::<16>(); + let (enc, nonce) = aad_only_encryptor(&key); + // One counter block left under this nonce: 16 bytes of keystream. + let state = with_u64(enc.suspend(), NEXT_COUNTER_AT, (1 << 32) - 1); + let mut enc = as SuspendableKeyed<'_, GCM_N>>::from_suspended( + state, + key.state(), + ) + .expect("one block left is a state the counter can be in"); + + let plaintext = [0x41u8; 32]; + let mut ciphertext = [0xEEu8; 32]; + assert!(matches!( + enc.do_encrypt_out(&plaintext, &mut ciphertext), + Err(SymmetricCipherError::DataLimitExceeded) + )); + assert_eq!(ciphertext, [0u8; 32]); + let mut last_block = [0u8; 16]; + assert_eq!(enc.do_encrypt_out(&plaintext[..16], &mut last_block).unwrap(), 16); + assert_ne!(last_block, plaintext[..16]); + assert!(matches!( + enc.do_encrypt_out(&plaintext[..1], &mut [0u8; 1]), + Err(SymmetricCipherError::DataLimitExceeded) + )); + let (tag, _) = enc.do_encrypt_final().unwrap(); + + // The decryptor at the same counter: a call that would release more than the keystream + // covers is refused with the output zeroed, and the block that fits then verifies. + let mut dec = key.create_decryptor(&nonce); + dec.do_update_aad(b"header").unwrap(); + let state = with_u64(dec.suspend(), NEXT_COUNTER_AT, (1 << 32) - 1); + let mut dec = as SuspendableKeyed<'_, GCM_N>>::from_suspended( + state, + key.state(), + ) + .unwrap(); + let mut stream = last_block.to_vec(); + stream.extend_from_slice(&last_block); + stream.extend_from_slice(&tag); + let mut out = [0xEEu8; 32]; + assert!(matches!( + dec.do_decrypt_out(&stream, &mut out), + Err(SymmetricCipherError::DataLimitExceeded) + )); + assert_eq!(out, [0u8; 32]); + let mut out = [0u8; 16]; + assert_eq!(dec.do_decrypt_out(&stream[16..], &mut out).unwrap(), 16); + assert_eq!(out, plaintext[..16]); + assert!(dec.do_decrypt_final().is_ok()); +} diff --git a/crypto/cipher/tests/modes/gmac_tests.rs b/crypto/cipher/tests/modes/gmac_tests.rs index fc1a9890c..09740f381 100644 --- a/crypto/cipher/tests/modes/gmac_tests.rs +++ b/crypto/cipher/tests/modes/gmac_tests.rs @@ -1,21 +1,28 @@ -//! Structural tests for GMAC, driven by a toy permutation: both routes onto it give the tag `Gcm` -//! gives over AAD alone, streaming splits, tag, nonce and message sensitivity, and key validation. +//! Structural tests for GMAC, driven by a toy permutation: the key and the mode's own route give +//! the tag `Gcm` gives over AAD alone, streaming splits, tag, nonce and message sensitivity, and key +//! validation. //! The known-answer tests (ACVP, Wycheproof) are in the `aes` crate, `crypto/aes/tests/gmac_*.rs`. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{GCM_NONCE_LEN, Gcm, GcmKeyState, Gmac}; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; -use bouncycastle_core::hazmat::ElectronicCodeBook; +use bouncycastle_cipher::modes::{GCM, GCM_NONCE_LEN, GMAC, ModeNames}; +use bouncycastle_core::hazmat::{ElectronicCodeBook, Mode, NonceMacMode}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::AEADCipherEncryptor; +use bouncycastle_core::symmetric_key::{AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_rng::FixedSeedRNG; use common::{TOY_LEN, Toy, toy_key}; +use toy_key::ToyKey; -type ToyGmac = Gmac; -type ToyGcm = Gcm; +type ToyGmacKey = ToyKey>; + +fn gmac_key() -> ToyGmacKey { + ToyGmacKey::from_key_material(&mac_key()).expect("the toy MAC key") +} const NONCE: [u8; GCM_NONCE_LEN] = [0x5A; GCM_NONCE_LEN]; @@ -23,8 +30,9 @@ fn mac_key() -> KeyMaterial { KeyMaterial::::from_bytes_as_type(toy_key().ref_to_bytes(), KeyType::MACKey).unwrap() } -fn key_state() -> GcmKeyState { - GcmKeyState::new::(Toy::new(&toy_key()).unwrap()) +/// The mode's own route onto the workers, through the hazmat mode trait the key forwards to. +fn key_state() -> >::KeyState { + >::key_state(Toy::new(&toy_key()).unwrap()) } fn message(len: usize) -> Vec { @@ -42,55 +50,65 @@ fn every_route_gives_gcm_s_aad_only_tag() { let state = key_state(); for len in [0, 1, 15, 16, 17, 32, 33, 100] { let msg = message(len); - let (gcm_nonce, _, expected) = ToyGcm::::encrypt_detached_rng_out( - &toy_key(), - &mut rng(), - &msg, - &[], - &mut [], - ) - .unwrap(); + let (_, gcm_nonce, expected) = ToyKey::::from_key_material(&toy_key()) + .unwrap() + .encrypt_detached_out(&mut rng(), &msg, &[], &mut []) + .unwrap(); assert_eq!(gcm_nonce, NONCE); - let (gen_, nonce) = ToyGmac::::from_key_state(&state, &mut rng()).unwrap(); + let key = gmac_key::<16>(); + let (gen_, nonce) = key.create_generator(&mut rng()).unwrap(); assert_eq!(nonce, NONCE, "len {len}"); - assert_eq!(gen_.mac(&msg), expected, "from_key_state, len {len}"); + assert_eq!(gen_.mac(&msg), expected, "key.create_generator, len {len}"); assert_eq!( - ToyGmac::::from_key_state_nonce(&state, &NONCE).mac(&msg), + key.create_generator_nonce(&NONCE).mac(&msg), expected, - "from_key_state_nonce, len {len}" + "key.create_generator_nonce, len {len}" ); - let (gen_, nonce) = ToyGmac::::new(&mac_key(), &mut rng()).unwrap(); - assert_eq!(nonce, NONCE); - assert_eq!(gen_.mac(&msg), expected, "new, len {len}"); + assert!(key.create_verifier(&NONCE).verify(&msg, &expected)); + + type Full = GMAC<16>; + type Short = GMAC<12>; + let (gen_, nonce) = + >::generate_init(&state, &mut rng()) + .unwrap(); + assert_eq!(nonce, NONCE, "len {len}"); + assert_eq!(gen_.mac(&msg), expected, "generate_init, len {len}"); assert_eq!( - ToyGmac::::new_nonce(&mac_key(), &NONCE).unwrap().mac(&msg), + >::generate_init_nonce(&state, &NONCE) + .mac(&msg), expected, - "new_nonce, len {len}" + "generate_init_nonce, len {len}" + ); + assert!( + >::verify_init(&state, &NONCE) + .verify(&msg, &expected) ); - assert!(ToyGmac::::from_key_state(&state, &NONCE).verify(&msg, &expected)); - assert!(ToyGmac::::new(&mac_key(), &NONCE).unwrap().verify(&msg, &expected)); - - let short = ToyGmac::::from_key_state_nonce(&state, &NONCE).mac(&msg); + let short = + >::generate_init_nonce(&state, &NONCE) + .mac(&msg); assert_eq!(short[..], expected[..12], "len {len}"); - assert!(ToyGmac::::from_key_state(&state, &NONCE).verify(&msg, &short)); + assert!( + >::verify_init(&state, &NONCE) + .verify(&msg, &short) + ); } } #[test] fn streaming_split_points() { - let state = key_state(); + let key = gmac_key::<16>(); for mlen in [16, 20, 32, 33, 64] { let msg = message(mlen); - let expected = ToyGmac::::from_key_state_nonce(&state, &NONCE).mac(&msg); + let expected = key.create_generator_nonce(&NONCE).mac(&msg); for split in 0..=mlen { - let mut gen_ = ToyGmac::::from_key_state_nonce(&state, &NONCE); + let mut gen_ = key.create_generator_nonce(&NONCE); gen_.do_update(&msg[..split]); gen_.do_update(&msg[split..]); assert_eq!(gen_.do_final(), expected, "generate, mlen {mlen}, split at {split}"); - let mut ver = ToyGmac::::from_key_state(&state, &NONCE); + let mut ver = key.create_verifier(&NONCE); ver.do_update(&msg[..split]); ver.do_update(&msg[split..]); assert!(ver.do_verify_final(&expected), "verify, mlen {mlen}, split at {split}"); @@ -100,11 +118,11 @@ fn streaming_split_points() { #[test] fn rejects_changed_tag_nonce_or_message() { - let state = key_state(); + let key = gmac_key::<16>(); let msg = message(100); - let tag = ToyGmac::::from_key_state_nonce(&state, &NONCE).mac(&msg); + let tag = key.create_generator_nonce(&NONCE).mac(&msg); let verify = |nonce: &[u8; GCM_NONCE_LEN], msg: &[u8], tag: &[u8; 16]| { - ToyGmac::::from_key_state(&state, nonce).verify(msg, tag) + key.create_verifier(nonce).verify(msg, tag) }; assert!(verify(&NONCE, &msg, &tag)); @@ -119,7 +137,7 @@ fn rejects_changed_tag_nonce_or_message() { assert!(!verify(&NONCE, &msg[..99], &tag)); // The empty message has a tag of its own, which is not the tag of anything else. - let empty_tag = ToyGmac::::from_key_state_nonce(&state, &NONCE).mac(&[]); + let empty_tag = key.create_generator_nonce(&NONCE).mac(&[]); assert!(verify(&NONCE, &[], &empty_tag)); assert!(!verify(&NONCE, &[0], &empty_tag)); } @@ -127,35 +145,18 @@ fn rejects_changed_tag_nonce_or_message() { /// A nonce drawn per generator: two draws from one RNG are two nonces, and so two tags. #[test] fn a_fresh_nonce_per_generator() { - let state = key_state(); + let key = gmac_key::<16>(); let mut rng = FixedSeedRNG::new(core::array::from_fn::(|i| i as u8)); - let (g1, n1) = ToyGmac::::from_key_state(&state, &mut rng).unwrap(); - let (g2, n2) = ToyGmac::::from_key_state(&state, &mut rng).unwrap(); + let (g1, n1) = key.create_generator(&mut rng).unwrap(); + let (g2, n2) = key.create_generator(&mut rng).unwrap(); assert_ne!(n1, n2); assert_ne!(g1.mac(b"m"), g2.mac(b"m")); } -/// The material route takes a `MACKey` at the permutation's strength and nothing else. +/// The key's construction suite: a `MACKey` of the permutation's length and strength, bound to +/// the GMAC name or to nothing. #[test] -fn key_validation() { - let cipher_key = toy_key(); - let is_type_error = |r: Result<(), SymmetricCipherError>| { - matches!( - r, - Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) - ) - }; - assert!(is_type_error(ToyGmac::::new(&cipher_key, &mut rng()).map(drop))); - assert!(is_type_error(ToyGmac::::new_nonce(&cipher_key, &NONCE).map(drop))); - assert!(is_type_error(ToyGmac::::new(&cipher_key, &NONCE).map(drop))); - - let mut weak = mac_key(); - weak.set_security_strength(SecurityStrength::_112bit).unwrap(); - assert!(matches!( - ToyGmac::::new_nonce(&weak, &NONCE), - Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) - )); - - let zero = KeyMaterial::::from_bytes_as_type(&[0; TOY_LEN], KeyType::MACKey).unwrap(); - assert!(ToyGmac::::new(&zero, &NONCE).is_err()); +fn the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::GMAC_ALG_NAME, SecurityStrength::_128bit); } diff --git a/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs b/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs index b0c68624a..46b596a66 100644 --- a/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs +++ b/crypto/cipher/tests/modes/symmetric_cipher_api_tests.rs @@ -17,20 +17,30 @@ //! * and that the length predictions are exact, not upper bounds. mod common; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; -use bouncycastle_cipher::modes::{Cfb, Cfb8, Ctr}; +use bouncycastle_cipher::modes::{CFB, CFB8, CTR, Ctr}; use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::hazmat::Mode; +use bouncycastle_core::symmetric_key::{ + FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSymmetricCipher; use common::{TOY_LEN, Toy, toy_key}; +use toy_key::{ToyKey, os_rng}; + +type ToyCtr<'k, Dir> = Ctr<'k, Toy, Dir, TOY_LEN, 12>; -type ToyCfb

= Cfb; -type ToyCfb8 = Cfb8; -type ToyCtr = Ctr; +/// The toy key under the stream mode `M`. +fn key_for>() -> ToyKey { + ToyKey::from_key_material(&toy_key()).expect("the toy key") +} /// 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 @@ -44,12 +54,9 @@ type ToyCtr = Ctr; #[test] fn the_stream_modes_conform_to_the_symmetric_cipher_suite() { let framework = TestFrameworkSymmetricCipher::new(); - framework - .test_encryptor_decryptor::, ToyCfb>(); - framework - .test_encryptor_decryptor::, ToyCfb8>( - ); - framework.test_encryptor_decryptor::, ToyCtr>(); + framework.test_key::(&key_for::(), &mut os_rng()); + framework.test_key::(&key_for::(), &mut os_rng()); + framework.test_key::(&key_for::>(), &mut os_rng()); } /// The separate-output API must produce exactly what the in-place API produces, for the same key @@ -57,29 +64,23 @@ 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<'k, K, const KEY_LEN: usize, const INIT_DATA_LEN: usize>(name: &str, key: &'k K) + where + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: StreamCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 63, 64, 171] { let plaintext: Vec = (0..len).map(|i| (i * 7 + 1) as u8).collect(); // The in-place API, which the mode implements directly. - let (mut enc, init) = E::do_encrypt_init(key).unwrap(); + let (mut enc, init) = key.create_encryptor(&mut os_rng()).unwrap(); let mut in_place = plaintext.clone(); enc.do_encrypt_inplace(&mut in_place).unwrap(); // The separate-output API, under the same init data. - let mut dec_as_sym = - >::do_decrypt_init( - key, &init, - ) - .unwrap(); + let mut dec_as_sym = key.create_decryptor(&init); let mut out = vec![0u8; plaintext.len()]; let n = dec_as_sym.do_decrypt_out(&in_place, &mut out).unwrap(); let (last, last_len) = dec_as_sym.do_decrypt_final().unwrap(); @@ -90,9 +91,9 @@ 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::<_, TOY_LEN, TOY_LEN>("Cfb", &key_for::()); + check::<_, TOY_LEN, TOY_LEN>("Cfb8", &key_for::()); + check::<_, TOY_LEN, 12>("Ctr", &key_for::>()); } /// The separate-output API must leave the caller's input untouched. That is the whole reason a @@ -100,15 +101,12 @@ fn the_two_apis_agree_byte_for_byte() { /// assuming. #[test] fn the_input_buffer_is_not_modified() { - let key = toy_key(); let plaintext: Vec = (0..100u8).collect(); let original = plaintext.clone(); - let (mut enc, _init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + let key = key_for::(); + + let (mut enc, _init) = key.create_encryptor(&mut os_rng()).unwrap(); let mut ciphertext = vec![0u8; plaintext.len()]; enc.do_encrypt_out(&plaintext, &mut ciphertext).unwrap(); @@ -119,22 +117,20 @@ fn the_input_buffer_is_not_modified() { /// The length predictions are exact for a stream cipher, not upper bounds: what goes in comes out. #[test] fn the_length_predictions_are_exact() { - let key = toy_key(); + let key = key_for::>(); for len in [0usize, 1, 15, 16, 17, 1000] { assert_eq!( - as SymmetricCipherEncryptor>::encrypt_out_len(len), + as SymmetricCipherEncryptor<12, 0>>::encrypt_out_len(len), len, "encrypt_out_len is the identity" ); assert_eq!( - as SymmetricCipherDecryptor>::decrypt_out_len(len), + as SymmetricCipherDecryptor<12, 0>>::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, _) = key.create_encryptor(&mut os_rng()).unwrap(); assert_eq!(enc.do_encrypt_out_len(len), len, "update_out_len is the identity"); } } @@ -145,14 +141,10 @@ fn the_length_predictions_are_exact() { fn a_short_output_buffer_is_refused_without_consuming_anything() { use bouncycastle_core::errors::SymmetricCipherError; - let key = toy_key(); + let key = key_for::(); let plaintext: Vec = (0..32u8).collect(); - let (mut enc, init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + let (mut enc, init) = key.create_encryptor(&mut os_rng()).unwrap(); let mut too_small = vec![0u8; plaintext.len() - 1]; match enc.do_encrypt_out(&plaintext, &mut too_small) { @@ -167,11 +159,8 @@ fn a_short_output_buffer_is_refused_without_consuming_anything() { let mut big_enough = vec![0u8; plaintext.len()]; enc.do_encrypt_out(&plaintext, &mut big_enough).unwrap(); - let (mut fresh, _) = ToyCfb::::do_encrypt_init_rng( - &key, - &mut bouncycastle_rng::FixedSeedRNG::new(init), - ) - .unwrap(); + let (mut fresh, _) = + key.create_encryptor(&mut bouncycastle_rng::FixedSeedRNG::new(init)).unwrap(); let mut reference = plaintext.clone(); fresh.do_encrypt_inplace(&mut reference).unwrap(); assert_eq!(big_enough, reference, "the refused call must not have advanced the keystream"); @@ -186,19 +175,15 @@ fn a_short_output_buffer_is_refused_without_consuming_anything() { fn a_short_output_buffer_is_refused_when_decrypting_too() { use bouncycastle_core::errors::SymmetricCipherError; - let key = toy_key(); + let key = key_for::(); let plaintext: Vec = (0..32u8).collect(); // Encrypt normally, then try to decrypt into a buffer one byte too small. - let (mut enc, init) = ToyCfb::::do_encrypt_init(&key).unwrap(); + let (mut enc, init) = key.create_encryptor(&mut os_rng()).unwrap(); 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 = key.create_decryptor(&init); let mut too_small = vec![0u8; ciphertext.len() - 1]; match dec.do_decrypt_out(&ciphertext, &mut too_small) { @@ -217,11 +202,7 @@ 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 = key.create_decryptor(&init); 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"); @@ -233,40 +214,22 @@ fn a_short_output_buffer_is_refused_when_decrypting_too() { /// modes: the shape a caller most often wants from this API. #[test] fn the_allocating_one_shots_round_trip() { - let key = toy_key(); let message = b"a message of no particular length at all"; // CFB128 - let (iv, ct) = as SymmetricCipherEncryptor>::encrypt( - &key, message, - ) - .unwrap(); + let key = key_for::(); + let (iv, ct) = key.encrypt(&mut os_rng(), message).unwrap(); assert_eq!(ct.len(), message.len(), "a stream cipher does not change the length"); - let back = as SymmetricCipherDecryptor>::decrypt( - &key, &iv, &ct, - ) - .unwrap(); - assert_eq!(back, message); + assert_eq!(key.decrypt(&iv, &ct).unwrap(), message); // CFB8 - let (iv, ct) = as SymmetricCipherEncryptor>::encrypt( - &key, message, - ) - .unwrap(); - let back = as SymmetricCipherDecryptor>::decrypt( - &key, &iv, &ct, - ) - .unwrap(); - assert_eq!(back, message); + let key = key_for::(); + let (iv, ct) = key.encrypt(&mut os_rng(), message).unwrap(); + assert_eq!(key.decrypt(&iv, &ct).unwrap(), message); // CTR - let (nonce, ct) = - as SymmetricCipherEncryptor>::encrypt(&key, message) - .unwrap(); + let key = key_for::>(); + let (nonce, ct) = key.encrypt(&mut os_rng(), 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(); - assert_eq!(back, message); + assert_eq!(key.decrypt(&nonce, &ct).unwrap(), message); } diff --git a/crypto/cipher/tests/padding/padded_tests.rs b/crypto/cipher/tests/padding/padded_tests.rs index 083c9701e..4ee38f966 100644 --- a/crypto/cipher/tests/padding/padded_tests.rs +++ b/crypto/cipher/tests/padding/padded_tests.rs @@ -1,108 +1,57 @@ -//! Tests for PaddedBlockCipherEncryptor / PaddedBlockCipherDecryptor. -//! -//! No real block cipher exists in the this crate, so these tests drive the adapters with a toy -//! CBC-style cipher whose "block permutation" is XOR with the key. It is cryptographically worthless -//! but exercises every code path of the adapters: IV generation, chaining state across calls, and -//! the one-block lag on decryption. - -use bouncycastle_cipher::padding::{ - NoPadding, PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor, -}; -use bouncycastle_core::errors::{KeyMaterialError, PaddingError, SymmetricCipherError}; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +//! Tests for PaddedBlockCipherEncryptor / PaddedBlockCipherDecryptor, through the key path: +//! `CBC` and `CBC` over the toy permutation in `common/toy_block_cipher.rs`. +//! They exercise every code path of the adapters: IV generation, chaining state across calls, +//! the one-block lag on decryption, and the alignment check `NoPadding` turns the adapters into. + +#[path = "../common/toy_block_cipher.rs"] +mod toy_block_cipher; +#[path = "../common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; + +use bouncycastle_cipher::modes::{Blocks, CBC, ModeNames}; +use bouncycastle_cipher::padding::{NoPadding, PKCS7}; +use bouncycastle_core::errors::{PaddingError, SymmetricCipherError}; +use bouncycastle_core::hazmat::Mode; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{ + BlockEncryptionKey, FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ - Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + BlockCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSymmetricCipher; use bouncycastle_rng::FixedSeedRNG; -use bouncycastle_rng::hash_drbg80090a::{HashDRBG80090A, HashDRBG80090AParams_SHA256}; +use toy_block_cipher::{TOY_BLOCK_LEN, ToyBlockCipher}; +use toy_key::{ToyKey, os_rng}; -const B: usize = 8; +const B: usize = TOY_BLOCK_LEN; -/// c_j = p_j ^ c_{j-1} ^ key ; p_j = c_j ^ c_{j-1} ^ key -struct ToyCbc { - key: [u8; B], - chain: [u8; B], -} +type Key = ToyKey>; +type Enc<'k> = >::Encryptor<'k>; +type Dec<'k> = >::Decryptor<'k>; +/// The same adapters over `NoPadding`: an alignment check rather than a padding scheme. +type KeyNP = ToyKey>; +type EncNP<'k> = >::Encryptor<'k>; +type DecNP<'k> = >::Decryptor<'k>; -impl ToyCbc { - fn check_key(key: &KeyMaterial) -> Result<[u8; B], SymmetricCipherError> { - if key.key_type() != KeyType::SymmetricCipherKey { - return Err(KeyMaterialError::InvalidKeyType("expected SymmetricCipherKey"))?; - } - if key.security_strength() < Self::MAX_SECURITY_STRENGTH { - return Err(KeyMaterialError::GenericError("key too weak"))?; - } - let mut k = [0u8; B]; - k.copy_from_slice(key.ref_to_bytes()); - Ok(k) - } +fn material() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&[0x5a; B], KeyType::SymmetricCipherKey).unwrap() } -impl Algorithm for ToyCbc { - const ALG_NAME: &'static str = "ToyCbc"; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +fn key_for>() -> ToyKey { + ToyKey::from_key_material(&material()).expect("the toy key") } -impl BlockCipherEncryptor for ToyCbc { - fn do_encrypt_init(key: &KeyMaterial) -> 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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; B]), SymmetricCipherError> { - let key = Self::check_key(key)?; - let mut iv = [0u8; B]; - rng.next_bytes_out(&mut iv)?; - Ok((Self { key, chain: iv }, iv)) - } - fn do_encrypt_blocks_inplace( - &mut self, - blocks: &mut [[u8; B]], - ) -> Result { - for block in blocks.iter_mut() { - for (b, (c, k)) in block.iter_mut().zip(self.chain.iter().zip(self.key.iter())) { - *b ^= c ^ k; - } - self.chain = *block; - } - Ok(blocks.len() * B) - } +fn key() -> Key { + key_for() } -impl BlockCipherDecryptor for ToyCbc { - fn do_decrypt_init(key: &KeyMaterial, iv: &[u8; B]) -> Result { - Ok(Self { key: Self::check_key(key)?, chain: *iv }) - } - fn do_decrypt_blocks_inplace( - &mut self, - blocks: &mut [[u8; B]], - ) -> Result { - let len = blocks.len() * B; - for block in blocks.iter_mut() { - let ct = *block; - for (b, (c, k)) in block.iter_mut().zip(self.chain.iter().zip(self.key.iter())) { - *b ^= c ^ k; - } - self.chain = ct; - } - Ok(len) - } -} - -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; - -fn key() -> KeyMaterial { - KeyMaterial::::from_bytes_as_type(&[0x5a; B], KeyType::SymmetricCipherKey).unwrap() +fn key_np() -> KeyNP { + key_for() } fn msg(len: usize) -> Vec { @@ -111,14 +60,24 @@ fn msg(len: usize) -> Vec { #[test] fn toy_cipher_passes_core_test_framework() { - TestFrameworkBlockCipher::new().test::(); + TestFrameworkBlockCipher::new() + .test_key::(&key_for::>(), &mut os_rng()); } -/// The padded adapters are the first implementors of `SymmetricCipherEncryptor` / -/// `SymmetricCipherDecryptor`, so this is also what exercises those traits' provided one-shots. +/// The padded adapters are what the symmetric key path hands out for a block mode, so this is +/// also what exercises the role traits' provided one-shots over a mode with a final block. #[test] fn padded_adapters_pass_the_symmetric_cipher_framework() { - TestFrameworkSymmetricCipher::new().test_encryptor_decryptor::(); + TestFrameworkSymmetricCipher::new().test_key::(&key(), &mut os_rng()); +} + +/// The construction suite for both keys: the type, binding and strength policy of +/// `from_key_material`. +#[test] +fn the_keys_enforce_the_material_policy() { + let framework = TestFrameworkFromKeyMaterial::new(); + framework.test::(ToyBlockCipher::CBC_ALG_NAME, SecurityStrength::_128bit); + framework.test::(ToyBlockCipher::CBC_ALG_NAME, SecurityStrength::_128bit); } #[test] @@ -127,12 +86,12 @@ fn one_shot_roundtrip_all_lengths() { for len in 0..=3 * B + 1 { let pt = msg(len); let mut ct = vec![0u8; Enc::encrypt_out_len(len)]; - let (iv, n) = Enc::encrypt_out(&key, &pt, &mut ct).unwrap(); + let (n, iv) = key.encrypt_out(&mut os_rng(), &pt, &mut ct).unwrap(); assert_eq!(n, ct.len()); assert_eq!(n, (len / B + 1) * B, "always one extra padding block"); let mut out = vec![0u8; Dec::decrypt_out_len(n)]; - let m = Dec::decrypt_out(&key, &iv, &ct[..n], &mut out).unwrap(); + let m = key.decrypt_out(&iv, &ct[..n], &mut out).unwrap(); assert_eq!(&out[..m], &pt[..]); } } @@ -145,7 +104,7 @@ fn streaming_matches_one_shot_for_every_chunking() { for chunk in [1usize, 2, 3, 7, 8, 9, 15, 16, 17, len] { // encrypt in chunks - let (mut enc, iv) = Enc::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut ct = Vec::new(); for piece in pt.chunks(chunk) { let expect = enc.do_encrypt_out_len(piece.len()); @@ -161,11 +120,11 @@ fn streaming_matches_one_shot_for_every_chunking() { // one-shot decrypt let mut out = vec![0u8; Dec::decrypt_out_len(ct.len())]; - let m = Dec::decrypt_out(&key, &iv, &ct, &mut out).unwrap(); + let m = key.decrypt_out(&iv, &ct, &mut out).unwrap(); assert_eq!(&out[..m], &pt[..], "chunk {chunk}"); // decrypt in the same chunks - let mut dec = Dec::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let mut rec = Vec::new(); for piece in ct.chunks(chunk) { let expect = dec.do_decrypt_out_len(piece.len()); @@ -185,11 +144,11 @@ fn decryptor_lags_by_exactly_one_block() { let key = key(); let (iv, ct) = { let mut ct = vec![0u8; Enc::encrypt_out_len(2 * B)]; - let (iv, _) = Enc::encrypt_out(&key, &msg(2 * B), &mut ct).unwrap(); + let (_, iv) = key.encrypt_out(&mut os_rng(), &msg(2 * B), &mut ct).unwrap(); (iv, ct) }; assert_eq!(ct.len(), 3 * B); - let mut dec = Dec::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let mut out = [0u8; 3 * B]; // first block: nothing can be released yet assert_eq!(dec.do_decrypt_out_len(B), 0); @@ -208,7 +167,7 @@ fn decryptor_lags_by_exactly_one_block() { #[test] fn final_out_variants() { let key = key(); - let (mut enc, iv) = Enc::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut ct = [0u8; 2 * B]; let n = enc.do_encrypt_out(&msg(B + 2), &mut ct).unwrap(); assert_eq!(n, B); @@ -216,7 +175,7 @@ fn final_out_variants() { assert_eq!(enc.do_encrypt_final_out(&mut last).unwrap(), B); ct[B..].copy_from_slice(&last); - let mut dec = Dec::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); let mut out = [0u8; B]; assert_eq!(dec.do_decrypt_out(&ct, &mut out).unwrap(), B); let mut last_pt = [0u8; B]; @@ -232,14 +191,14 @@ fn tampered_final_block_is_rejected() { let key = key(); for len in [0, 1, B - 1, B, B + 5] { let mut ct = vec![0u8; Enc::encrypt_out_len(len)]; - let (iv, n) = Enc::encrypt_out(&key, &msg(len), &mut ct).unwrap(); + let (n, iv) = key.encrypt_out(&mut os_rng(), &msg(len), &mut ct).unwrap(); // flipping the low bit of the final byte corrupts the PKCS7 length byte ct[n - 1] ^= 0x01; // Pre-filled with a non-zero byte, so a buffer the decryptor never wrote is not mistaken // for one it zeroized. At `B + 5` the first block has been decrypted into `out` by the // time the padding fails, and `decrypt_out` must wipe it. let mut out = vec![0xEEu8; n]; - match Dec::decrypt_out(&key, &iv, &ct, &mut out) { + match key.decrypt_out(&iv, &ct, &mut out) { Err(SymmetricCipherError::PaddingError(PaddingError::InvalidPadding)) => {} other => panic!("len {len}: expected InvalidPadding, got {other:?}"), } @@ -255,20 +214,20 @@ fn malformed_ciphertext_lengths_are_rejected() { // empty assert!(matches!( - Dec::decrypt_out(&key, &iv, &[], &mut out), + key.decrypt_out(&iv, &[], &mut out), Err(SymmetricCipherError::DecryptionFailed) )); // not a multiple of the block length assert!(matches!( - Dec::decrypt_out(&key, &iv, &[0u8; B + 1], &mut out), + key.decrypt_out(&iv, &[0u8; B + 1], &mut out), Err(SymmetricCipherError::DataNotBlockAligned) )); // streaming: partial trailing block at final - let mut dec = Dec::do_decrypt_init(&key, &iv).unwrap(); + let mut dec = key.create_decryptor(&iv); dec.do_decrypt_out(&[0u8; B + 3], &mut out).unwrap(); assert!(matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::DataNotBlockAligned))); // streaming: nothing fed at all - let dec = Dec::do_decrypt_init(&key, &iv).unwrap(); + let dec = key.create_decryptor(&iv); assert!(matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::DecryptionFailed))); } @@ -278,12 +237,12 @@ fn output_buffer_too_small_reports_required_length() { let pt = msg(2 * B + 1); let mut small = [0u8; 2 * B]; - match Enc::encrypt_out(&key, &pt, &mut small) { + match key.encrypt_out(&mut os_rng(), &pt, &mut small) { Err(SymmetricCipherError::OutputBufferTooSmall(need)) => assert_eq!(need, 3 * B), other => panic!("{other:?}"), } - let (mut enc, iv) = Enc::do_encrypt_init(&key).unwrap(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut tiny = [0u8; B - 1]; match enc.do_encrypt_out(&pt, &mut tiny) { Err(SymmetricCipherError::OutputBufferTooSmall(need)) => assert_eq!(need, 2 * B), @@ -293,7 +252,7 @@ fn output_buffer_too_small_reports_required_length() { let ct = [0u8; 3 * B]; let mut small = [0u8; 3 * B - 2]; - match Dec::decrypt_out(&key, &iv, &ct, &mut small) { + match key.decrypt_out(&iv, &ct, &mut small) { Err(SymmetricCipherError::OutputBufferTooSmall(need)) => { assert_eq!(need, 3 * B - 1) } @@ -301,19 +260,6 @@ 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(); - assert!(matches!( - Enc::do_encrypt_init(&mac_key), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::do_decrypt_init(&mac_key, &[0u8; B]), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); -} - // ---- NoPadding through the adapters -------------------------------------------------------- /// With `NoPadding` the adapters enforce alignment: the framework is told that only multiples of @@ -323,14 +269,14 @@ 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_key::(&key_np(), &mut os_rng()); } /// An aligned message passes through with its length unchanged -- no final block is added -- and the /// ciphertext is exactly what the bare mode produces: NoPadding is a check, not a transformation. #[test] fn no_padding_adds_nothing_to_aligned_data() { - let key = key(); + let key_np = key_np(); for blocks in 0..=4usize { let len = blocks * B; let pt = msg(len); @@ -338,22 +284,23 @@ fn no_padding_adds_nothing_to_aligned_data() { assert_eq!(DecNP::decrypt_out_len(len), len); let mut ct = vec![0u8; len]; - let (iv, n) = EncNP::encrypt_out(&key, &pt, &mut ct).unwrap(); + let (n, iv) = key_np.encrypt_out(&mut os_rng(), &pt, &mut ct).unwrap(); assert_eq!(n, len, "{blocks} blocks: output length equals input length"); // Byte for byte the bare cipher's output under the same IV. let mut bare = pt.clone(); - let (mut enc, _) = ToyCbc::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(iv)).unwrap(); + let key = key_for::>(); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::new(iv)).unwrap(); let (blocks_mut, _) = bare.as_chunks_mut::(); enc.do_encrypt_blocks_inplace(blocks_mut).unwrap(); assert_eq!(ct, bare, "{blocks} blocks: the adapter must not alter the ciphertext"); let mut out = vec![0u8; len]; - let m = DecNP::decrypt_out(&key, &iv, &ct, &mut out).unwrap(); + let m = key_np.decrypt_out(&iv, &ct, &mut out).unwrap(); assert_eq!(&out[..m], &pt[..], "{blocks} blocks: round trip"); // Streaming: do_encrypt_final reports zero output bytes. - let (mut enc, _) = EncNP::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = key_np.create_encryptor(&mut os_rng()).unwrap(); let mut buf = vec![0u8; enc.do_encrypt_out_len(len)]; assert_eq!(enc.do_encrypt_out(&pt, &mut buf).unwrap(), len); let (_, last_len) = enc.do_encrypt_final().unwrap(); @@ -365,19 +312,19 @@ fn no_padding_adds_nothing_to_aligned_data() { /// streaming `do_encrypt_final`, and nothing is written for the final block. #[test] fn no_padding_refuses_unaligned_data() { - let key = key(); + let key_np = key_np(); for len in [1usize, B - 1, B + 1, 2 * B + 3, 3 * B - 1] { let pt = msg(len); let mut ct = vec![0u8; len + B]; assert!( matches!( - EncNP::encrypt_out(&key, &pt, &mut ct), + key_np.encrypt_out(&mut os_rng(), &pt, &mut ct), Err(SymmetricCipherError::DataNotBlockAligned) ), "len {len}: one-shot must refuse an unaligned message" ); - let (mut enc, _) = EncNP::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = key_np.create_encryptor(&mut os_rng()).unwrap(); let whole = len / B * B; let mut buf = vec![0u8; whole]; assert_eq!(enc.do_encrypt_out(&pt, &mut buf).unwrap(), whole, "whole blocks still stream"); @@ -392,18 +339,18 @@ fn no_padding_refuses_unaligned_data() { /// demand), and an unaligned ciphertext is still malformed. #[test] fn no_padding_decryptor_accepts_empty_and_rejects_unaligned() { - let key = key(); + let key_np = key_np(); let iv = [0x11u8; B]; let mut out = [0u8; 0]; - assert_eq!(DecNP::decrypt_out(&key, &iv, &[], &mut out).unwrap(), 0); - let dec = DecNP::do_decrypt_init(&key, &iv).unwrap(); + assert_eq!(key_np.decrypt_out(&iv, &[], &mut out).unwrap(), 0); + let dec = key_np.create_decryptor(&iv); assert_eq!(dec.do_decrypt_final().unwrap().1, 0); for len in [1usize, B - 1, B + 1, 2 * B + 5] { let mut out = vec![0u8; len]; assert!( matches!( - DecNP::decrypt_out(&key, &iv, &msg(len), &mut out), + key_np.decrypt_out(&iv, &msg(len), &mut out), Err(SymmetricCipherError::DataNotBlockAligned) ), "len {len}: an unaligned ciphertext is malformed" diff --git a/crypto/cipher/tests/suspend_tests.rs b/crypto/cipher/tests/suspend_tests.rs index 7397e0024..71256137b 100644 --- a/crypto/cipher/tests/suspend_tests.rs +++ b/crypto/cipher/tests/suspend_tests.rs @@ -8,11 +8,19 @@ //! The AES aliases get the same impls through these generic types, so this is where they are //! pinned; `bouncycastle-aes` only checks that each alias reaches them. -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; -use bouncycastle_cipher::modes::{Cbc, CcmDecryptor, CcmEncryptor, Cfb, Cfb8, Ctr, Gcm}; +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; +use bouncycastle_cipher::modes::{ + Blocks, CBC, CCM_Packet, CFB, CFB8, CTR, Cbc, CcmPacketDecryptor, CcmPacketEncryptor, Cfb, + Cfb8, Ctr, GCM, Gcm, +}; use bouncycastle_cipher::padding::{PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor}; use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_core::hazmat::{EcbDecryptionKey, EcbEncryptionKey, Mode}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{ + AeadDecryptionKey, AeadEncryptionKey, BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, + SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, @@ -22,37 +30,51 @@ use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendabl #[path = "common/toy_block_cipher.rs"] mod toy_block_cipher; +#[path = "common/toy_key.rs"] +#[allow(dead_code)] +mod toy_key; use toy_block_cipher::ToyBlockCipher; +use toy_key::{ToyKey, os_rng}; -type ToyEcb = Ecb; -type ToyCbc = Cbc; -type ToyCfb = Cfb; -type ToyCfb8 = Cfb8; -type ToyCtr = Ctr; -type ToyGcm = Gcm; +type ToyEcb<'k, Dir> = Ecb<'k, ToyBlockCipher, Dir, 16>; +type ToyCbc<'k, Dir> = Cbc<'k, ToyBlockCipher, Dir, 16>; +type ToyCfb<'k, Dir> = Cfb<'k, ToyBlockCipher, Dir, 16>; +type ToyCfb8<'k, Dir> = Cfb8<'k, ToyBlockCipher, Dir, 16>; +type ToyCtr<'k, Dir> = Ctr<'k, ToyBlockCipher, Dir, 16, 12>; +type ToyGcm<'k, Dir> = Gcm<'k, ToyBlockCipher, Dir, 16>; // A 64-byte AAD capacity and 37-byte frames, the message `ccm_both_directions` uses. -type ToyCcmEnc = CcmEncryptor; -type ToyCcmDec = CcmDecryptor; -type ToyPaddedEnc = PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>; -type ToyPaddedDec = PaddedBlockCipherDecryptor, PKCS7, 16, 16, 16>; +type ToyCcmPacket = CCM_Packet<12, 16, 64, 37>; +type ToyCcmEnc<'k> = CcmPacketEncryptor<'k, ToyBlockCipher, 16, 12, 16, 64, 37>; +type ToyCcmDec<'k> = CcmPacketDecryptor<'k, ToyBlockCipher, 16, 12, 16, 64, 37>; +type ToyPaddedEnc<'k> = PaddedBlockCipherEncryptor, PKCS7, 16, 16>; +type ToyPaddedDec<'k> = PaddedBlockCipherDecryptor, PKCS7, 16, 16>; fn key() -> KeyMaterial<16> { KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap() } +/// The toy key under the mode `M`; its workers borrow it, and resume takes its state. +fn key_for>() -> ToyKey { + ToyKey::from_key_material(&key()).expect("the toy key") +} + fn message(len: usize) -> Vec { (0..len).map(|i| (i as u8).wrapping_mul(7).wrapping_add(3)).collect() } -/// Runs the framework suite on `cipher`, then suspends a clone, resumes it, and finishes both -/// with `finish`. Whatever `finish` returns must be identical for the two. -fn round_trip(cipher: C, finish: impl Fn(C) -> Vec) -> Vec +/// Runs the framework suite on `cipher`, then suspends a clone, resumes it over `key`, the mode's +/// key state the cipher borrows, and finishes both with `finish`. Whatever `finish` returns must +/// be identical for the two. +fn round_trip<'k, const N: usize, C>( + cipher: C, + key: &'k C::Key, + finish: impl Fn(C) -> Vec, +) -> Vec where - C: SuspendableKeyed> + Clone, + C: SuspendableKeyed<'k, N> + Clone, { - let key = key(); - TestFrameworkSuspendableKeyedState::new().test(&cipher, &key); - let resumed = C::from_suspended(cipher.clone().suspend(), &key).unwrap(); + TestFrameworkSuspendableKeyedState::new().test(&cipher, key); + let resumed = C::from_suspended(cipher.clone().suspend(), key).unwrap(); let original_output = finish(cipher); assert_eq!(original_output, finish(resumed), "the resumed cipher must continue identically"); original_output @@ -60,37 +82,49 @@ where #[test] fn cbc_both_directions() { - let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key()).unwrap(); + let key = key_for::>(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut first = [0x11u8; 16]; enc.do_encrypt_blocks_inplace(first.as_chunks_mut().0).unwrap(); - let rest = round_trip::<{ ToyCbc::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut data = [0x22u8; 48]; - e.do_encrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); - data.to_vec() - }); + let rest = round_trip::<{ ToyCbc::::SUSPENDED_STATE_LEN }, _>( + enc, + key.state(), + |mut e| { + let mut data = [0x22u8; 48]; + e.do_encrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); + data.to_vec() + }, + ); - let mut dec = ToyCbc::::do_decrypt_init(&key(), &iv).unwrap(); + let key = key_for::>(); + let mut dec = key.create_decryptor(&iv); dec.do_decrypt_blocks_inplace(first.as_chunks_mut().0).unwrap(); assert_eq!(first, [0x11u8; 16]); - let plain = round_trip::<{ ToyCbc::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut data: [u8; 48] = rest.as_slice().try_into().unwrap(); - d.do_decrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); - data.to_vec() - }); + let plain = round_trip::<{ ToyCbc::::SUSPENDED_STATE_LEN }, _>( + dec, + key.state(), + |mut d| { + let mut data: [u8; 48] = rest.as_slice().try_into().unwrap(); + d.do_decrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); + data.to_vec() + }, + ); assert_eq!(plain, vec![0x22u8; 48]); } #[test] fn ecb_both_directions() { - let (mut enc, _) = ToyEcb::::do_encrypt_init(&key()).unwrap(); + let key = key_for::>(); + let mut enc = key.create_encryptor(); enc.do_encrypt_blocks_inplace(&mut [[0x11u8; 16]]).unwrap(); - round_trip::<{ ToyEcb::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { + round_trip::<{ ToyEcb::::SUSPENDED_STATE_LEN }, _>(enc, key.state(), |mut e| { let mut data = [0x22u8; 32]; e.do_encrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); data.to_vec() }); - let dec = ToyEcb::::do_decrypt_init(&key(), &[]).unwrap(); - round_trip::<{ ToyEcb::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { + let key = key_for::>(); + let dec = key.create_decryptor(); + round_trip::<{ ToyEcb::::SUSPENDED_STATE_LEN }, _>(dec, key.state(), |mut d| { let mut data = [0x33u8; 32]; d.do_decrypt_blocks_inplace(data.as_chunks_mut().0).unwrap(); data.to_vec() @@ -100,65 +134,95 @@ fn ecb_both_directions() { #[test] fn cfb_mid_segment_both_directions() { let msg = message(40); - let (mut enc, iv) = ToyCfb::::do_encrypt_init(&key()).unwrap(); + let key = key_for::(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut head = msg[..7].to_vec(); enc.do_encrypt_inplace(&mut head).unwrap(); - let tail = round_trip::<{ ToyCfb::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut data = msg[7..].to_vec(); - e.do_encrypt_inplace(&mut data).unwrap(); - data - }); + let tail = round_trip::<{ ToyCfb::::SUSPENDED_STATE_LEN }, _>( + enc, + key.state(), + |mut e| { + let mut data = msg[7..].to_vec(); + e.do_encrypt_inplace(&mut data).unwrap(); + data + }, + ); - let mut dec = ToyCfb::::do_decrypt_init(&key(), &iv).unwrap(); + let key = key_for::(); + let mut dec = key.create_decryptor(&iv); dec.do_decrypt_inplace(&mut head).unwrap(); assert_eq!(head, msg[..7]); - let plain = round_trip::<{ ToyCfb::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut data = tail.clone(); - d.do_decrypt_inplace(&mut data).unwrap(); - data - }); + let plain = round_trip::<{ ToyCfb::::SUSPENDED_STATE_LEN }, _>( + dec, + key.state(), + |mut d| { + let mut data = tail.clone(); + d.do_decrypt_inplace(&mut data).unwrap(); + data + }, + ); assert_eq!(plain, msg[7..]); } #[test] fn cfb8_both_directions() { let msg = message(20); - let (mut enc, iv) = ToyCfb8::::do_encrypt_init(&key()).unwrap(); + let key = key_for::(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut head = msg[..5].to_vec(); enc.do_encrypt_inplace(&mut head).unwrap(); - let tail = round_trip::<{ ToyCfb8::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut data = msg[5..].to_vec(); - e.do_encrypt_inplace(&mut data).unwrap(); - data - }); - let mut dec = ToyCfb8::::do_decrypt_init(&key(), &iv).unwrap(); + let tail = round_trip::<{ ToyCfb8::::SUSPENDED_STATE_LEN }, _>( + enc, + key.state(), + |mut e| { + let mut data = msg[5..].to_vec(); + e.do_encrypt_inplace(&mut data).unwrap(); + data + }, + ); + let key = key_for::(); + let mut dec = key.create_decryptor(&iv); dec.do_decrypt_inplace(&mut head).unwrap(); - let plain = round_trip::<{ ToyCfb8::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut data = tail.clone(); - d.do_decrypt_inplace(&mut data).unwrap(); - data - }); + let plain = round_trip::<{ ToyCfb8::::SUSPENDED_STATE_LEN }, _>( + dec, + key.state(), + |mut d| { + let mut data = tail.clone(); + d.do_decrypt_inplace(&mut data).unwrap(); + data + }, + ); assert_eq!(plain, msg[5..]); } #[test] fn ctr_mid_block_both_directions() { let msg = message(50); - let (mut enc, nonce) = ToyCtr::::do_encrypt_init(&key()).unwrap(); + let key = key_for::>(); + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).unwrap(); let mut head = msg[..7].to_vec(); enc.do_encrypt_inplace(&mut head).unwrap(); - let tail = round_trip::<{ ToyCtr::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut data = msg[7..].to_vec(); - e.do_encrypt_inplace(&mut data).unwrap(); - data - }); - let mut dec = ToyCtr::::do_decrypt_init(&key(), &nonce).unwrap(); + let tail = round_trip::<{ ToyCtr::::SUSPENDED_STATE_LEN }, _>( + enc, + key.state(), + |mut e| { + let mut data = msg[7..].to_vec(); + e.do_encrypt_inplace(&mut data).unwrap(); + data + }, + ); + let key = key_for::>(); + let mut dec = key.create_decryptor(&nonce); dec.do_decrypt_inplace(&mut head).unwrap(); - let plain = round_trip::<{ ToyCtr::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut data = tail.clone(); - d.do_decrypt_inplace(&mut data).unwrap(); - data - }); + let plain = round_trip::<{ ToyCtr::::SUSPENDED_STATE_LEN }, _>( + dec, + key.state(), + |mut d| { + let mut data = tail.clone(); + d.do_decrypt_inplace(&mut data).unwrap(); + data + }, + ); assert_eq!(plain, msg[7..]); } @@ -167,34 +231,44 @@ fn gcm_both_directions_with_aad() { let msg = message(45); let aad = b"authenticated header"; - let (mut enc, nonce) = ToyGcm::::do_encrypt_init(&key()).unwrap(); + let key = key_for::>(); + let (mut enc, nonce) = key.create_encryptor(&mut os_rng()).unwrap(); enc.do_update_aad(aad).unwrap(); let mut head = [0u8; 5]; enc.do_encrypt_out(&msg[..5], &mut head).unwrap(); // Ciphertext of the rest, then the tag. - let tail = round_trip::<{ ToyGcm::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut out = vec![0u8; 40]; - e.do_encrypt_out(&msg[5..], &mut out).unwrap(); - let (tag, tag_len) = e.do_encrypt_final().unwrap(); - out.extend_from_slice(&tag[..tag_len]); - out - }); + let tail = round_trip::<{ ToyGcm::::SUSPENDED_STATE_LEN }, _>( + enc, + key.state(), + |mut e| { + let mut out = vec![0u8; 40]; + e.do_encrypt_out(&msg[5..], &mut out).unwrap(); + let (tag, tag_len) = e.do_encrypt_final().unwrap(); + out.extend_from_slice(&tag[..tag_len]); + out + }, + ); let mut ciphertext = head.to_vec(); ciphertext.extend_from_slice(&tail); // The decryptor holds the last 16 bytes back, so after 10 bytes nothing has been released, // but data has started and the AAD phase is closed: a state worth suspending. - let mut dec = ToyGcm::::do_decrypt_init(&key(), &nonce).unwrap(); + let key = key_for::>(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut nothing = [0u8; 0]; assert_eq!(dec.do_decrypt_out(&ciphertext[..10], &mut nothing).unwrap(), 0); - let plain = round_trip::<{ ToyGcm::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut out = vec![0u8; ciphertext.len()]; - let n = d.do_decrypt_out(&ciphertext[10..], &mut out).unwrap(); - let (_, last) = d.do_decrypt_final().expect("the tag must verify after a resume"); - out.truncate(n + last); - out - }); + let plain = round_trip::<{ ToyGcm::::SUSPENDED_STATE_LEN }, _>( + dec, + key.state(), + |mut d| { + let mut out = vec![0u8; ciphertext.len()]; + let n = d.do_decrypt_out(&ciphertext[10..], &mut out).unwrap(); + let (_, last) = d.do_decrypt_final().expect("the tag must verify after a resume"); + out.truncate(n + last); + out + }, + ); assert_eq!(plain, msg); } @@ -206,9 +280,10 @@ fn ccm_both_directions() { // Suspended with half the AAD held back and no payload yet, and again mid-payload; both // must continue to the one answer. - let mut enc = ToyCcmEnc::do_encrypt_init_nonce(&key(), &nonce).unwrap(); + let key = key_for::(); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(&aad[..3]).unwrap(); - let sealed = round_trip::<{ ToyCcmEnc::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { + let sealed = round_trip::<{ ToyCcmEnc::SUSPENDED_STATE_LEN }, _>(enc, key.state(), |mut e| { e.do_update_aad(&aad[3..]).unwrap(); let mut out = vec![0u8; 37]; e.do_encrypt_out(&msg, &mut out).unwrap(); @@ -216,11 +291,12 @@ fn ccm_both_directions() { out.extend_from_slice(&tag); out }); - let mut enc = ToyCcmEnc::do_encrypt_init_nonce(&key(), &nonce).unwrap(); + let key = key_for::(); + let mut enc = key.create_encryptor_nonce(&nonce); enc.do_update_aad(aad).unwrap(); let mut head = vec![0u8; 9]; enc.do_encrypt_out(&msg[..9], &mut head).unwrap(); - let tail = round_trip::<{ ToyCcmEnc::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { + let tail = round_trip::<{ ToyCcmEnc::SUSPENDED_STATE_LEN }, _>(enc, key.state(), |mut e| { let mut out = vec![0u8; 28]; e.do_encrypt_out(&msg[9..], &mut out).unwrap(); let (_, _, tag) = e.do_encrypt_final_detached().unwrap(); @@ -231,11 +307,12 @@ fn ccm_both_directions() { let (ct, tag) = sealed.split_at(37); let tag: [u8; 16] = tag.try_into().unwrap(); - let mut dec = ToyCcmDec::do_decrypt_init(&key(), &nonce).unwrap(); + let key = key_for::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut opened_head = vec![0u8; 9]; dec.do_decrypt_out(&ct[..9], &mut opened_head).unwrap(); - let plain = round_trip::<{ ToyCcmDec::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { + let plain = round_trip::<{ ToyCcmDec::SUSPENDED_STATE_LEN }, _>(dec, key.state(), |mut d| { let mut out = vec![0u8; 28]; d.do_decrypt_out(&ct[9..], &mut out).unwrap(); d.do_decrypt_final_detached(&tag).expect("the tag must verify after a resume"); @@ -244,11 +321,12 @@ fn ccm_both_directions() { assert_eq!([&opened_head[..], &plain[..]].concat(), msg); // ...and with part of an inline tag already held back at the suspend point. - let mut dec = ToyCcmDec::do_decrypt_init(&key(), &nonce).unwrap(); + let key = key_for::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut opened = vec![0u8; 37]; dec.do_decrypt_out(&sealed[..40], &mut opened).unwrap(); - round_trip::<{ ToyCcmDec::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { + round_trip::<{ ToyCcmDec::SUSPENDED_STATE_LEN }, _>(dec, key.state(), |mut d| { d.do_decrypt_out(&sealed[40..], &mut []).unwrap(); d.do_decrypt_final().expect("the held-back tag must survive a resume"); Vec::new() @@ -259,11 +337,12 @@ fn ccm_both_directions() { #[test] fn padded_cbc_both_directions() { let msg = message(45); - let (mut enc, iv) = ToyPaddedEnc::do_encrypt_init(&key()).unwrap(); + let key = key_for::>(); + let (mut enc, iv) = key.create_encryptor(&mut os_rng()).unwrap(); let mut head = [0u8; 16]; // 20 bytes in: one block out, four buffered. assert_eq!(enc.do_encrypt_out(&msg[..20], &mut head).unwrap(), 16); - let tail = round_trip::<{ ToyPaddedEnc::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { + let tail = round_trip::<{ ToyPaddedEnc::SUSPENDED_STATE_LEN }, _>(enc, key.state(), |mut e| { let mut out = vec![0u8; 32]; let n = e.do_encrypt_out(&msg[20..], &mut out).unwrap(); out.truncate(n); @@ -276,17 +355,19 @@ fn padded_cbc_both_directions() { assert_eq!(ciphertext.len(), 48); // 20 bytes in: one block released, one held back, four buffered. - let mut dec = ToyPaddedDec::do_decrypt_init(&key(), &iv).unwrap(); + let key = key_for::>(); + let mut dec = key.create_decryptor(&iv); let mut first = [0u8; 16]; assert_eq!(dec.do_decrypt_out(&ciphertext[..36], &mut first).unwrap(), 16); - let plain = round_trip::<{ ToyPaddedDec::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut out = vec![0u8; 32]; - let n = d.do_decrypt_out(&ciphertext[36..], &mut out).unwrap(); - out.truncate(n); - let (last, data_len) = d.do_decrypt_final().unwrap(); - out.extend_from_slice(&last[..data_len]); - out - }); + let plain = + round_trip::<{ ToyPaddedDec::SUSPENDED_STATE_LEN }, _>(dec, key.state(), |mut d| { + let mut out = vec![0u8; 32]; + let n = d.do_decrypt_out(&ciphertext[36..], &mut out).unwrap(); + out.truncate(n); + let (last, data_len) = d.do_decrypt_final().unwrap(); + out.extend_from_slice(&last[..data_len]); + out + }); let mut recovered = first.to_vec(); recovered.extend_from_slice(&plain); assert_eq!(recovered, msg); diff --git a/crypto/core-test-framework/src/aead.rs b/crypto/core-test-framework/src/aead.rs index 191fc6bab..c5d1e5e8d 100644 --- a/crypto/core-test-framework/src/aead.rs +++ b/crypto/core-test-framework/src/aead.rs @@ -16,10 +16,12 @@ //! [`SymmetricCipherDecryptor`]: bouncycastle_core::traits::SymmetricCipherDecryptor use crate::DUMMY_SEED; -use crate::symmetric_ciphers::TestFrameworkSymmetricCipher; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor, Algorithm}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, RNG, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, +}; use bouncycastle_rng::FixedSeedRNG; /// Instance of the test framework. @@ -39,103 +41,76 @@ impl TestFrameworkAEADCipher { Self { fixed_message_len: None } } - /// Exercises the [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] streaming contract for a - /// paired implementor. The counterpart of [`TestFrameworkBlockCipher::test`] for an - /// authenticated cipher. + /// The AEAD contract over an [`AeadEncryptionKey`] / [`AeadDecryptionKey`] pair: the AAD and + /// tag contract reached through a key object, with `rng` supplying every nonce draw that is not + /// pinned. The key-policy checks are `TestFrameworkFromKeyMaterial`'s, and the no-AAD + /// symmetric contract is reached here as the AEAD one with an empty AAD. /// - /// Checks, in order: - /// * the whole [`TestFrameworkSymmetricCipher::test_encryptor_decryptor`] suite, since an AEAD - /// with no associated data and the tag inline *is* a [`SymmetricCipherEncryptor`] / - /// [`SymmetricCipherDecryptor`] pair, and that `FINAL_LEN` has room for the tag; - /// * the detached one-shot round trip for every message length from 0 to a few times - /// `TAG_LEN`, and that the tag is not the all-zero array; - /// * the inline layout with associated data, one-shot and streaming, is exactly the detached - /// ciphertext with the tag appended, and a stream shorter than the tag is a failed - /// decryption; - /// * streaming in every chunking, of both the AAD and the data, agrees with `update_out_len` - /// on every call and gives the one-shot's ciphertext and tag byte for byte, and decrypts in - /// every chunking; - /// * an empty AAD is a no-op -- it gives what absorbing no AAD at all gives -- and a message - /// with no data still authenticates its AAD; - /// * `do_update_aad` with non-empty AAD after the first `do_update_out` is refused with a - /// [`SymmetricCipherError::StateError`], and the refusal leaves the value usable; - /// * a tampered ciphertext, tag, AAD or nonce all fail the tag check, and the one-shots leave - /// no plaintext behind when they do; - /// * two encryptions under the same key draw different nonces; - /// * every AEAD method that writes into a caller's buffer accepts one larger than needed, - /// returns the number of bytes that call wrote, and zeroes every byte past that count; - /// * a key of the wrong [`KeyType`] is rejected, and the security-strength policy matches - /// [`Algorithm::MAX_SECURITY_STRENGTH`]. + /// A key whose workers take one message length ([`fixed_message_len`](Self::fixed_message_len)) + /// takes it through the one-shots too, since those are written over the workers; every other + /// length is checked to be refused. /// - /// `bouncycastle-core`'s own `tests/aead_buffering_toy_tests.rs` separately pins that a cipher - /// which holds back more than the tag is handled correctly by the traits' default one-shots, - /// since `E`/`D` here are supplied by the caller and might not hold anything back. - /// - /// [`Algorithm::MAX_SECURITY_STRENGTH`]: bouncycastle_core::traits::Algorithm::MAX_SECURITY_STRENGTH - /// - /// [`TestFrameworkBlockCipher::test`]: crate::block_cipher::TestFrameworkBlockCipher::test - /// [`TestFrameworkSymmetricCipher::test_encryptor_decryptor`]: crate::symmetric_ciphers::TestFrameworkSymmetricCipher::test_encryptor_decryptor - /// [`SymmetricCipherEncryptor`]: bouncycastle_core::traits::SymmetricCipherEncryptor - /// [`SymmetricCipherDecryptor`]: bouncycastle_core::traits::SymmetricCipherDecryptor - pub fn test_encryptor_decryptor< + /// The array-returning detached finals are checked against the `_out` ones, and the no-AAD + /// inline layout is run as the symmetric-cipher contract (the inline `do_*_final_out` finals, + /// the per-call output counts, a short streaming buffer, a corrupted ciphertext), since an + /// AEAD key cannot be handed to `TestFrameworkSymmetricCipher`. + pub fn test_key< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, - D: AEADCipherDecryptor + Algorithm, + K: AeadEncryptionKey + + AeadDecryptionKey, >( &self, + key: &K, + rng: &mut dyn RNG, ) { assert!( FINAL_LEN >= TAG_LEN, "FINAL_LEN must have room for the inline tag the decryptor holds back" ); - // 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::(); - - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); + let encrypt_out_len = |len: usize| { + as SymmetricCipherEncryptor>::encrypt_out_len( + len, + ) + }; + let encrypt_detached_out_len = |len: usize| { + as AEADCipherEncryptor>::encrypt_detached_out_len(len) + }; + let decrypt_out_len = |len: usize| { + as SymmetricCipherDecryptor>::decrypt_out_len( + len, + ) + }; + let decrypt_detached_out_len = |len: usize| { + as AEADCipherDecryptor>::decrypt_detached_out_len(len) + }; let aad: &[u8] = b"some associated data"; let pinned = [0xA5u8; NONCE_LEN]; + let fixed_rng = || FixedSeedRNG::new(pinned); // one-shot round trip, every length up to a few times the tag length (and up to the fixed - // length, if there is one, so that the streaming checks inside the loop reach it) + // length, if there is one) let max_len = (3 * TAG_LEN.max(1) + 5).max(self.fixed_message_len.unwrap_or(0)); assert!(max_len <= DUMMY_SEED.len(), "the fixed message length must fit the seed buffer"); for len in 0..=max_len { let msg = &DUMMY_SEED[..len]; - // a fixed-length pair's streaming methods take only that length; its one-shots, with - // the whole message in hand, take any length and must round trip it + // a fixed-length key takes only that length, one-shot or streamed if let Some(fixed) = self.fixed_message_len && fixed != len { - let mut ct = vec![0u8; E::encrypt_detached_out_len(len)]; - let (nonce, n, tag) = E::encrypt_detached_out(&key, aad, msg, &mut ct) - .unwrap_or_else(|e| { - panic!( - "fixed length: a {len}-byte detached one-shot must be accepted: {e:?}" - ) - }); - let mut pt = vec![0u8; D::decrypt_detached_out_len(n)]; - let m = - D::decrypt_detached_out(&key, &nonce, aad, &ct[..n], &tag, &mut pt).unwrap(); - assert_eq!(&pt[..m], msg, "fixed length: {len}-byte detached one-shot round trip"); - let mut inline = vec![0u8; E::encrypt_out_len(len)]; - let (nonce, n) = E::encrypt_with_aad_out(&key, aad, msg, &mut inline) - .unwrap_or_else(|e| { - panic!("fixed length: a {len}-byte inline one-shot must be accepted: {e:?}") - }); - let mut pt = vec![0u8; D::decrypt_out_len(n)]; - let m = D::decrypt_with_aad_out(&key, &nonce, aad, &inline[..n], &mut pt).unwrap(); - assert_eq!(&pt[..m], msg, "fixed length: {len}-byte inline one-shot round trip"); - // ...while the streaming methods refuse it, at the update or at the final - let (mut enc, _) = E::do_encrypt_init(&key).unwrap(); + let mut ct = vec![0u8; encrypt_detached_out_len(len)]; + assert!( + key.encrypt_detached_out(rng, aad, msg, &mut ct).is_err(), + "fixed length: a {len}-byte detached one-shot must be refused" + ); + let mut inline = vec![0u8; encrypt_out_len(len)]; + assert!( + key.encrypt_with_aad_out(rng, aad, msg, &mut inline).is_err(), + "fixed length: a {len}-byte inline one-shot must be refused" + ); + let (mut enc, _) = key.create_encryptor(rng).unwrap(); enc.do_update_aad(aad).unwrap(); let mut buf = vec![0u8; enc.do_encrypt_out_len(len)]; assert!( @@ -144,8 +119,8 @@ impl TestFrameworkAEADCipher { ); continue; } - let mut ct = vec![0u8; E::encrypt_detached_out_len(len)]; - let (nonce, ct_len, tag) = E::encrypt_detached_out(&key, aad, msg, &mut ct).unwrap(); + let mut ct = vec![0u8; encrypt_detached_out_len(len)]; + let (ct_len, nonce, tag) = key.encrypt_detached_out(rng, aad, msg, &mut ct).unwrap(); ct.truncate(ct_len); assert_ne!(tag, [0u8; TAG_LEN], "len {len}: the tag must not be all zeros"); // Only assert the ciphertext differs from the plaintext once there is enough of it for @@ -153,139 +128,130 @@ impl TestFrameworkAEADCipher { if len >= 8 { assert_ne!(&ct[..], msg, "len {len}: the ciphertext must not be the plaintext"); } - let mut pt = vec![0u8; D::decrypt_detached_out_len(ct.len())]; - let pt_len = D::decrypt_detached_out(&key, &nonce, aad, &ct, &tag, &mut pt).unwrap(); + let mut pt = vec![0xEEu8; decrypt_detached_out_len(ct.len())]; + let pt_len = key.decrypt_detached_out(&nonce, aad, &ct, &tag, &mut pt).unwrap(); pt.truncate(pt_len); assert_eq!(&pt[..], msg, "one-shot round trip, len {len}"); // the std one-shots agree with the _out ones for the same nonce - let (nonce2, ct2, tag2) = E::encrypt_detached(&key, aad, msg).unwrap(); + let (nonce2, ct2, tag2) = key.encrypt_detached(rng, aad, msg).unwrap(); assert_eq!(ct2.len(), ct_len, "encrypt_detached must return exactly the bytes written"); - let pt2 = D::decrypt_detached(&key, &nonce2, aad, &ct2, &tag2).unwrap(); + let pt2 = key.decrypt_detached(&nonce2, aad, &ct2, &tag2).unwrap(); assert_eq!(pt2, msg, "std round trip, len {len}"); - let pt3 = D::decrypt_detached(&key, &nonce, aad, &ct, &tag).unwrap(); + let pt3 = key.decrypt_detached(&nonce, aad, &ct, &tag).unwrap(); assert_eq!(pt3, msg, "decrypt_detached must agree with decrypt_detached_out"); - // the inline `ciphertext || tag` layout with AAD: `encrypt_with_aad_out` must write exactly - // the detached ciphertext with the tag appended -- the same bytes under the same - // nonce -- and both the one-shot and the streaming finalizer must round trip it. - let mut detached = vec![0u8; E::encrypt_detached_out_len(len)]; - let (pinned_nonce, detached_len, detached_tag) = E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - aad, - msg, - &mut detached, - ) - .unwrap(); + // the inline `ciphertext || tag` layout with AAD must be exactly the detached + // ciphertext with the tag appended -- the same bytes under the same nonce -- and both + // the one-shot and the streaming finalizer must round trip it. + let mut detached = vec![0u8; encrypt_detached_out_len(len)]; + let (detached_len, pinned_nonce, detached_tag) = + key.encrypt_detached_out(&mut fixed_rng(), aad, msg, &mut detached).unwrap(); + assert_eq!(pinned_nonce, pinned, "a fixed RNG must yield its stream as the nonce"); detached.truncate(detached_len); detached.extend_from_slice(&detached_tag); - let mut inline = vec![0u8; E::encrypt_out_len(len)]; - let (inline_nonce, inline_len) = - E::encrypt_with_aad_out(&key, aad, msg, &mut inline).unwrap(); + let mut inline = vec![0u8; encrypt_out_len(len)]; + let (inline_len, inline_nonce) = + key.encrypt_with_aad_out(rng, aad, msg, &mut inline).unwrap(); assert_eq!( inline_len, - E::encrypt_detached_out_len(len) + TAG_LEN, + encrypt_detached_out_len(len) + TAG_LEN, "encrypt_with_aad_out must write the ciphertext plus the tag, len {len}" ); - let mut pt4 = vec![0u8; D::decrypt_out_len(inline_len)]; - let pt4_len = - D::decrypt_with_aad_out(&key, &inline_nonce, aad, &inline[..inline_len], &mut pt4) - .unwrap(); + let mut pt4 = vec![0xEEu8; decrypt_out_len(inline_len)]; + let pt4_len = key + .decrypt_with_aad_out(&inline_nonce, aad, &inline[..inline_len], &mut pt4) + .unwrap(); assert_eq!(&pt4[..pt4_len], msg, "tagged one-shot round trip, len {len}"); - // ...and so must the RNG-driven and allocating inline-with-AAD one-shots. The roomy - // buffer is deliberate: see the `encrypt_detached_rng_out` probe below. - let mut inline_rng = vec![0u8; E::encrypt_out_len(len) + 3]; - let (rng_nonce, rng_len) = E::encrypt_with_aad_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - aad, - msg, - &mut inline_rng, - ) - .unwrap(); + // the pinned inline one-shot, into a roomy buffer and an exact one. The roomy buffer + // is deliberate: an implementation that sizes its work by the buffer rather than the + // message would write past the ciphertext, and the exact one catches the off-by-one + // in the other direction. + let mut inline_rng = vec![0u8; encrypt_out_len(len) + 3]; + let (rng_len, rng_nonce) = + key.encrypt_with_aad_out(&mut fixed_rng(), aad, msg, &mut inline_rng).unwrap(); assert_eq!(rng_nonce, pinned_nonce, "the same RNG stream must give the same nonce"); assert_eq!( &inline_rng[..rng_len], &detached[..], - "len {len}: encrypt_with_aad_rng_out must be the detached ciphertext and its tag" + "len {len}: the inline layout must be the detached ciphertext and its tag" ); - // exactly the length it asks for must be enough too - let mut exact = vec![0u8; E::encrypt_out_len(len)]; - let (_, exact_len) = E::encrypt_with_aad_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - aad, - msg, - &mut exact, - ) - .unwrap(); + let mut exact = vec![0u8; encrypt_out_len(len)]; + let (exact_len, _) = + key.encrypt_with_aad_out(&mut fixed_rng(), aad, msg, &mut exact).unwrap(); assert_eq!(&exact[..exact_len], &detached[..], "len {len}: exact-size buffer"); // the supplied-nonce forms: the nonce the pinned RNG produced, supplied directly, - // must give the same bytes from the one-shots, the allocating one-shots and the - // streaming constructor alike - let mut by_nonce = vec![0u8; E::encrypt_detached_out_len(len)]; + // must give the same bytes from the one-shots, the allocating one-shots, the in-place + // one-shots and the streaming constructor alike + let mut by_nonce = vec![0u8; encrypt_detached_out_len(len)]; let (n_len, n_tag) = - E::encrypt_detached_nonce_out(&key, &pinned_nonce, aad, msg, &mut by_nonce) - .unwrap(); + key.encrypt_detached_nonce_out(&pinned_nonce, aad, msg, &mut by_nonce).unwrap(); by_nonce.truncate(n_len); by_nonce.extend_from_slice(&n_tag); assert_eq!(by_nonce, detached, "len {len}: encrypt_detached_nonce_out under the nonce"); - let mut by_nonce = vec![0u8; E::encrypt_out_len(len)]; - let n_len = E::encrypt_with_aad_nonce_out(&key, &pinned_nonce, aad, msg, &mut by_nonce) - .unwrap(); + let mut by_nonce = vec![0u8; encrypt_out_len(len)]; + let n_len = + key.encrypt_with_aad_nonce_out(&pinned_nonce, aad, msg, &mut by_nonce).unwrap(); assert_eq!(&by_nonce[..n_len], &detached[..], "len {len}: encrypt_with_aad_nonce_out"); - let mut short = vec![0u8; E::encrypt_out_len(len) - 1]; - match E::encrypt_with_aad_nonce_out(&key, &pinned_nonce, aad, msg, &mut short) { + let mut short = vec![0u8; encrypt_out_len(len) - 1]; + match key.encrypt_with_aad_nonce_out(&pinned_nonce, aad, msg, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, E::encrypt_out_len(len)) + assert_eq!(n, encrypt_out_len(len)) } other => panic!("encrypt_with_aad_nonce_out into a short buffer: {other:?}"), } let (mut alloc_ct, alloc_tag) = - E::encrypt_detached_nonce(&key, &pinned_nonce, aad, msg).unwrap(); + key.encrypt_detached_nonce(&pinned_nonce, aad, msg).unwrap(); alloc_ct.extend_from_slice(&alloc_tag); assert_eq!(alloc_ct, detached, "len {len}: encrypt_detached_nonce"); assert_eq!( - E::encrypt_with_aad_nonce(&key, &pinned_nonce, aad, msg).unwrap(), + key.encrypt_with_aad_nonce(&pinned_nonce, aad, msg).unwrap(), detached, "len {len}: encrypt_with_aad_nonce" ); - let mut enc_n = E::do_encrypt_init_nonce(&key, &pinned_nonce).unwrap(); + let mut enc_n = key.create_encryptor_nonce(&pinned_nonce); enc_n.do_update_aad(aad).unwrap(); let mut streamed = vec![0u8; enc_n.do_encrypt_out_len(len)]; let w = enc_n.do_encrypt_out(msg, &mut streamed).unwrap(); streamed.truncate(w); let (last_n, last_n_len) = enc_n.do_encrypt_final().unwrap(); streamed.extend_from_slice(&last_n[..last_n_len]); - assert_eq!(streamed, detached, "len {len}: do_encrypt_init_nonce streaming"); - let mut short = vec![0u8; E::encrypt_out_len(len) - 1]; - match E::encrypt_with_aad_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - aad, - msg, - &mut short, - ) { + assert_eq!(streamed, detached, "len {len}: create_encryptor_nonce streaming"); + let mut short = vec![0u8; encrypt_out_len(len) - 1]; + match key.encrypt_with_aad_out(&mut fixed_rng(), aad, msg, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, E::encrypt_out_len(len)) + assert_eq!(n, encrypt_out_len(len)) } - other => panic!("encrypt_with_aad_rng_out into a short buffer: {other:?}"), + other => panic!("encrypt_with_aad_out into a short buffer: {other:?}"), } - let (alloc_nonce, alloc_ct) = E::encrypt_with_aad(&key, aad, msg).unwrap(); + let (alloc_nonce, alloc_ct) = key.encrypt_with_aad(rng, aad, msg).unwrap(); assert_eq!( alloc_ct.len(), inline_len, "encrypt_with_aad must return the bytes written" ); - let alloc_pt = D::decrypt_with_aad(&key, &alloc_nonce, aad, &alloc_ct).unwrap(); + let alloc_pt = key.decrypt_with_aad(&alloc_nonce, aad, &alloc_ct).unwrap(); assert_eq!(alloc_pt, msg, "allocating inline-with-AAD round trip, len {len}"); - let (mut enc5, nonce5) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(pinned)).unwrap(); + // the in-place one-shots, drawing and supplied-nonce + let mut buf = msg.to_vec(); + let (ip_nonce, ip_tag) = + key.encrypt_detached_in_place(&mut fixed_rng(), aad, &mut buf).unwrap(); + assert_eq!(ip_nonce, pinned_nonce, "len {len}: in place, the pinned nonce"); + assert_eq!((&buf[..], ip_tag), (&detached[..len], detached_tag), "len {len}: in place"); + key.decrypt_detached_in_place(&ip_nonce, aad, &mut buf, &ip_tag).unwrap(); + assert_eq!(&buf[..], msg, "len {len}: in-place round trip"); + let ip_tag = key.encrypt_detached_in_place_nonce(&pinned_nonce, aad, &mut buf).unwrap(); + assert_eq!((&buf[..], ip_tag), (&detached[..len], detached_tag), "len {len}: in place"); + key.decrypt_detached_in_place(&pinned_nonce, aad, &mut buf, &ip_tag).unwrap(); + assert_eq!(&buf[..], msg, "len {len}: in-place round trip under the nonce"); + + // the streaming constructor under the pinned RNG draws the pinned nonce, and the + // inline stream decrypts + let (mut enc5, nonce5) = key.create_encryptor(&mut fixed_rng()).unwrap(); assert_eq!(nonce5, pinned_nonce, "the same RNG stream must give the same nonce"); enc5.do_update_aad(aad).unwrap(); let mut inline5 = vec![0u8; enc5.do_encrypt_out_len(len)]; @@ -302,7 +268,7 @@ impl TestFrameworkAEADCipher { inline5, detached, "len {len}: the inline layout must be the detached ciphertext followed by its tag" ); - let mut dec5 = D::do_decrypt_init(&key, &nonce5).unwrap(); + let mut dec5 = key.create_decryptor(&nonce5); dec5.do_update_aad(aad).unwrap(); let mut pt5 = vec![0u8; dec5.do_decrypt_out_len(inline5.len())]; let got5 = dec5.do_decrypt_out(&inline5, &mut pt5).unwrap(); @@ -311,10 +277,9 @@ impl TestFrameworkAEADCipher { pt5.extend_from_slice(&last[..data_len]); assert_eq!(pt5, msg, "tagged streaming round trip, len {len}"); - // a stream that ends before a whole tag has been seen is not a short buffer, it - // is a failed decryption + // a stream that ends before a whole tag has been seen is a failed decryption if TAG_LEN > 0 { - let mut dec6 = D::do_decrypt_init(&key, &nonce5).unwrap(); + let mut dec6 = key.create_decryptor(&nonce5); dec6.do_update_aad(aad).unwrap(); let short = &inline5[..TAG_LEN - 1]; let mut scratch = vec![0u8; dec6.do_decrypt_out_len(short.len())]; @@ -327,93 +292,50 @@ impl TestFrameworkAEADCipher { // too-short output buffers on the one-shots are refused with the required length, // before any work is done - let need = E::encrypt_detached_out_len(len); + let need = encrypt_detached_out_len(len); if need > 0 { let mut short = vec![0u8; need - 1]; - match E::encrypt_detached_out(&key, aad, msg, &mut short) { - Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, need) - } + match key.encrypt_detached_out(rng, aad, msg, &mut short) { + Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("encrypt_detached_out into a short buffer: {other:?}"), } - let mut short = vec![0u8; need - 1]; - match E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new([0xA5u8; NONCE_LEN]), - aad, - msg, - &mut short, - ) { - Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, need) - } - other => panic!("encrypt_detached_rng_out into a short buffer: {other:?}"), - } - // ...and one with room to spare must be accepted: without this the guard can be - // flipped to `>` and every short-buffer probe still "passes", because the error - // then comes from `do_update_out` behind it with the same variant and length. let mut roomy = vec![0u8; need + 3]; - let (_, n, _) = E::encrypt_detached_out(&key, aad, msg, &mut roomy).unwrap(); + let (n, _, _) = key.encrypt_detached_out(rng, aad, msg, &mut roomy).unwrap(); assert_eq!(n, need, "encrypt_detached_out into a roomy buffer"); - let mut roomy = vec![0u8; need + 3]; - let (_, n, _) = E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new([0xA5u8; NONCE_LEN]), - aad, - msg, - &mut roomy, - ) - .unwrap(); - assert_eq!( - n, need, - "encrypt_detached_rng_out must write exactly encrypt_detached_out_len bytes" - ); } - let need = E::encrypt_out_len(len); + let need = encrypt_out_len(len); let mut short = vec![0u8; need - 1]; - match E::encrypt_with_aad_out(&key, aad, msg, &mut short) { + match key.encrypt_with_aad_out(rng, aad, msg, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("encrypt_with_aad_out into a short buffer: {other:?}"), } - let need = D::decrypt_detached_out_len(ct.len()); + let need = decrypt_detached_out_len(ct.len()); if need > 0 { let mut short = vec![0u8; need - 1]; - match D::decrypt_detached_out(&key, &nonce, aad, &ct, &tag, &mut short) { - Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, need) - } + match key.decrypt_detached_out(&nonce, aad, &ct, &tag, &mut short) { + Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("decrypt_detached_out into a short buffer: {other:?}"), } } - let need = D::decrypt_out_len(inline_len); + let need = decrypt_out_len(inline_len); if need > 0 { let mut short = vec![0u8; need - 1]; - match D::decrypt_with_aad_out(&key, &inline_nonce, aad, &inline, &mut short) { - Err(SymmetricCipherError::OutputBufferTooSmall(n)) => { - assert_eq!(n, need) - } + match key.decrypt_with_aad_out(&inline_nonce, aad, &inline, &mut short) { + Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("decrypt_with_aad_out into a short buffer: {other:?}"), } } } // streaming in every chunking agrees with the one-shot, for both the AAD and the data. - // The pinned RNG is what makes the nonce -- and so the ciphertext -- comparable. let msg = &DUMMY_SEED[..self.fixed_message_len.unwrap_or(max_len.max(17))]; - let mut ct_ref = vec![0u8; E::encrypt_detached_out_len(msg.len())]; - let (nonce_ref, ct_ref_len, tag_ref) = E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - aad, - msg, - &mut ct_ref, - ) - .unwrap(); + let mut ct_ref = vec![0u8; encrypt_detached_out_len(msg.len())]; + let (ct_ref_len, nonce_ref, tag_ref) = + key.encrypt_detached_out(&mut fixed_rng(), aad, msg, &mut ct_ref).unwrap(); ct_ref.truncate(ct_ref_len); for chunk in [1usize, 2, 3, 7, TAG_LEN.max(1), TAG_LEN + 1, msg.len().max(1)] { - let (mut enc, nonce) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(pinned)).unwrap(); + let (mut enc, nonce) = key.create_encryptor(&mut fixed_rng()).unwrap(); assert_eq!(nonce, nonce_ref, "the same RNG stream must give the same nonce"); for piece in aad.chunks(chunk) { enc.do_update_aad(piece).unwrap(); @@ -436,8 +358,7 @@ impl TestFrameworkAEADCipher { assert_eq!(ct, ct_ref, "chunk {chunk}: streaming must give the one-shot ciphertext"); assert_eq!(tag, tag_ref, "chunk {chunk}: streaming must give the one-shot tag"); - // ...and the decryptor agrees in every chunking too - let mut dec = D::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); for piece in aad.chunks(chunk) { dec.do_update_aad(piece).unwrap(); } @@ -455,9 +376,25 @@ impl TestFrameworkAEADCipher { assert_eq!(pt, msg, "chunk {chunk}: streaming round trip"); } - // the array-returning finals agree with the `_out` ones the chunked loop above used - let (mut enc, nonce) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(pinned)).unwrap(); + // a wrong detached tag is refused at the final + let mut wrong_tag = tag_ref; + wrong_tag[0] ^= 0xFF; + let mut dec = key.create_decryptor(&nonce_ref); + dec.do_update_aad(aad).unwrap(); + let mut pt = vec![0u8; dec.do_decrypt_out_len(ct_ref.len())]; + dec.do_decrypt_out(&ct_ref, &mut pt).unwrap(); + let mut last = [0u8; FINAL_LEN]; + assert!( + matches!( + dec.do_decrypt_final_detached_out(&wrong_tag, &mut last), + Err(SymmetricCipherError::AEADTagCheckFailed) + ), + "do_decrypt_final_detached_out must check the tag" + ); + assert!(last.iter().all(|&b| b == 0), "a failed detached final must leave zeros"); + + // the array-returning detached finals agree with the `_out` ones the chunked loop used + let (mut enc, nonce) = key.create_encryptor(&mut fixed_rng()).unwrap(); enc.do_update_aad(aad).unwrap(); let mut ct = vec![0u8; enc.do_encrypt_out_len(msg.len())]; let n = enc.do_encrypt_out(msg, &mut ct).unwrap(); @@ -466,7 +403,7 @@ impl TestFrameworkAEADCipher { ct.extend_from_slice(&last[..last_len]); assert_eq!(ct, ct_ref, "do_encrypt_final_detached must give the one-shot ciphertext"); assert_eq!(tag, tag_ref, "do_encrypt_final_detached must give the one-shot tag"); - let mut dec = D::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut pt = vec![0u8; dec.do_decrypt_out_len(ct.len())]; let n = dec.do_decrypt_out(&ct, &mut pt).unwrap(); @@ -474,9 +411,7 @@ impl TestFrameworkAEADCipher { let (last, data_len) = dec.do_decrypt_final_detached(&tag).unwrap(); pt.extend_from_slice(&last[..data_len]); assert_eq!(pt, msg, "do_decrypt_final_detached must round trip"); - let mut wrong_tag = tag; - wrong_tag[0] ^= 0xFF; - let mut dec = D::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut pt = vec![0u8; dec.do_decrypt_out_len(ct.len())]; dec.do_decrypt_out(&ct, &mut pt).unwrap(); @@ -488,76 +423,58 @@ impl TestFrameworkAEADCipher { "do_decrypt_final_detached must check the tag" ); - // an empty AAD is a no-op: it must give exactly what absorbing no AAD at all gives - let mut with_empty = vec![0u8; E::encrypt_detached_out_len(msg.len())]; - let (nonce_empty, len_empty, tag_empty) = E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - b"", - msg, - &mut with_empty, - ) - .unwrap(); + // With no AAD and the tag inline, an AEAD pair is a symmetric-cipher pair, and the + // provided `do_*_final_out` finals and the streaming output-buffer contract are reached + // only this way, since an AEAD key does not carry the symmetric role. + self.symmetric_contract_over_the_pair(key, rng, &encrypt_out_len, &decrypt_out_len); + + // an empty AAD is a no-op: it must give exactly what absorbing no AAD at all gives, and + // the inline layout with an empty AAD is the no-AAD symmetric contract + let mut with_empty = vec![0u8; encrypt_detached_out_len(msg.len())]; + let (len_empty, nonce_empty, tag_empty) = + key.encrypt_detached_out(&mut fixed_rng(), b"", msg, &mut with_empty).unwrap(); with_empty.truncate(len_empty); - let mut without = vec![0u8; E::encrypt_detached_out_len(msg.len())]; - let (nonce_none, len_none, tag_none) = E::encrypt_detached_rng_out( - &key, - &mut FixedSeedRNG::new(pinned), - &[], - msg, - &mut without, - ) - .unwrap(); + let mut without = vec![0u8; encrypt_detached_out_len(msg.len())]; + let (len_none, nonce_none, tag_none) = + key.encrypt_detached_out(&mut fixed_rng(), &[], msg, &mut without).unwrap(); without.truncate(len_none); assert_eq!(nonce_empty, nonce_none); assert_eq!(tag_empty, tag_none, "an empty AAD must be a no-op"); assert_eq!(with_empty, without, "an empty AAD must be a no-op"); - - // ...and no AAD at all is what the inherited `SymmetricCipherEncryptor` one-shot gives - let mut plain = vec![0u8; E::encrypt_out_len(msg.len())]; - let (nonce_plain, len_plain) = - E::encrypt_rng_out(&key, &mut FixedSeedRNG::new(pinned), msg, &mut plain).unwrap(); + let mut plain = vec![0u8; encrypt_out_len(msg.len())]; + let (len_plain, nonce_plain) = + key.encrypt_with_aad_out(&mut fixed_rng(), &[], msg, &mut plain).unwrap(); assert_eq!(nonce_plain, nonce_none); assert_eq!(&plain[..len_plain - TAG_LEN], &without[..], "no-AAD inline ciphertext"); assert_eq!(&plain[len_plain - TAG_LEN..len_plain], &tag_none, "no-AAD inline tag"); - // a message with no data at all still authenticates its AAD (unless the pair's fixed - // length rules an empty message out) + // a message with no data at all still authenticates its AAD if self.fixed_message_len.is_none_or(|fixed| fixed == 0) { - let (nonce, _ct_len, tag) = E::encrypt_detached_out(&key, aad, &[], &mut []).unwrap(); - D::decrypt_detached_out(&key, &nonce, aad, &[], &tag, &mut []).unwrap(); - match D::decrypt_detached_out( - &key, - &nonce, - b"different associated data", - &[], - &tag, - &mut [], - ) { + let (_ct_len, nonce, tag) = key.encrypt_detached_out(rng, aad, &[], &mut []).unwrap(); + key.decrypt_detached_out(&nonce, aad, &[], &tag, &mut []).unwrap(); + match key.decrypt_detached_out(&nonce, b"different associated data", &[], &tag, &mut []) + { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("an empty message must still authenticate its AAD, got {other:?}"), }; } // the AAD phase is over once data has been fed in -- on both sides, and on the decrypting - // side even when all of it is still being held back as a possible tag. (Not for a message - // with no data at all, where there is no data call to end it.) + // side even when all of it is still being held back as a possible tag. if !msg.is_empty() { - let (mut enc, nonce) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, nonce) = key.create_encryptor(rng).unwrap(); let mut ct = vec![0u8; enc.do_encrypt_out_len(msg.len())]; enc.do_encrypt_out(msg, &mut ct).unwrap(); match enc.do_update_aad(aad) { Err(SymmetricCipherError::StateError(_)) => { /* good */ } other => panic!("AAD after data must be refused, got {other:?}"), }; - // an empty AAD stays a no-op even here, and the refused call must not have disturbed - // the state: the value is still good for the rest of the flow. enc.do_update_aad(b"").unwrap(); let mut final_buf = [0u8; FINAL_LEN]; let (final_len, tag) = enc.do_encrypt_final_detached_out(&mut final_buf).unwrap(); ct.extend_from_slice(&final_buf[..final_len]); - let mut dec = D::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); let mut pt = vec![0u8; dec.do_decrypt_out_len(1)]; let mut got = dec.do_decrypt_out(&ct[..1], &mut pt).unwrap(); pt.truncate(got); @@ -580,15 +497,15 @@ impl TestFrameworkAEADCipher { // the decryptor never wrote is not mistaken for one it zeroized. A message long enough to // have a byte 3 to flip, unless the pair's fixed length says otherwise. let msg = &DUMMY_SEED[..self.fixed_message_len.unwrap_or(max_len.max(17))]; - let mut ct = vec![0u8; E::encrypt_detached_out_len(msg.len())]; - let (nonce, ct_len, tag) = E::encrypt_detached_out(&key, aad, msg, &mut ct).unwrap(); + let mut ct = vec![0u8; encrypt_detached_out_len(msg.len())]; + let (ct_len, nonce, tag) = key.encrypt_detached_out(rng, aad, msg, &mut ct).unwrap(); ct.truncate(ct_len); if ct.len() > 3 { let mut tampered = ct.clone(); tampered[3] ^= 0xFF; - let mut buf = vec![0xEEu8; D::decrypt_detached_out_len(tampered.len())]; - match D::decrypt_detached_out(&key, &nonce, aad, &tampered, &tag, &mut buf) { + let mut buf = vec![0xEEu8; decrypt_detached_out_len(tampered.len())]; + match key.decrypt_detached_out(&nonce, aad, &tampered, &tag, &mut buf) { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("a modified ciphertext must fail the tag check, got {other:?}"), }; @@ -596,21 +513,26 @@ impl TestFrameworkAEADCipher { buf.iter().all(|&b| b == 0), "the one-shot decrypt must zeroize the buffer when the tag check fails" ); + let mut buf = tampered.clone(); + match key.decrypt_detached_in_place(&nonce, aad, &mut buf, &tag) { + Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } + other => panic!("a modified ciphertext must fail in place too, got {other:?}"), + }; + assert!( + buf.iter().all(|&b| b == 0), + "the in-place decrypt must zeroize the buffer when the tag check fails" + ); } let mut tampered_inline = ct.clone(); tampered_inline.extend_from_slice(&tag); tampered_inline[3] ^= 0xFF; for with_aad in [false, true] { - let mut buf = vec![0xEEu8; D::decrypt_out_len(tampered_inline.len())]; - let result = if with_aad { - D::decrypt_with_aad_out(&key, &nonce, aad, &tampered_inline, &mut buf) - } else { - D::decrypt_out(&key, &nonce, &tampered_inline, &mut buf) - }; + let mut buf = vec![0xEEu8; decrypt_out_len(tampered_inline.len())]; + let aad_used: &[u8] = if with_aad { aad } else { &[] }; // Without the AAD the tag was never going to verify; either way what matters is the // failure and the zeroized buffer. - match result { + match key.decrypt_with_aad_out(&nonce, aad_used, &tampered_inline, &mut buf) { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("a modified inline ciphertext must fail, got {other:?}"), }; @@ -619,23 +541,22 @@ impl TestFrameworkAEADCipher { "the inline one-shot (aad {with_aad}) must zeroize the buffer on a failed check" ); } - match D::decrypt_with_aad(&key, &nonce, aad, &tampered_inline) { + match key.decrypt_with_aad(&nonce, aad, &tampered_inline) { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("decrypt_with_aad of a modified ciphertext must fail, got {other:?}"), }; let mut wrong_tag = tag; wrong_tag[0] ^= 0xFF; - let mut buf = vec![0xEEu8; D::decrypt_detached_out_len(ct.len())]; - match D::decrypt_detached_out(&key, &nonce, aad, &ct, &wrong_tag, &mut buf) { + let mut buf = vec![0xEEu8; decrypt_detached_out_len(ct.len())]; + match key.decrypt_detached_out(&nonce, aad, &ct, &wrong_tag, &mut buf) { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("a modified tag must fail the tag check, got {other:?}"), }; assert!(buf.iter().all(|&b| b == 0), "a modified tag must leave the buffer zeroized"); - let mut buf = vec![0xEEu8; D::decrypt_detached_out_len(ct.len())]; - match D::decrypt_detached_out( - &key, + let mut buf = vec![0xEEu8; decrypt_detached_out_len(ct.len())]; + match key.decrypt_detached_out( &nonce, b"not the right associated data", &ct, @@ -650,25 +571,21 @@ impl TestFrameworkAEADCipher { if NONCE_LEN > 0 { let mut wrong_nonce = nonce; wrong_nonce[0] ^= 0xFF; - let mut buf = vec![0xEEu8; D::decrypt_detached_out_len(ct.len())]; - match D::decrypt_detached_out(&key, &wrong_nonce, aad, &ct, &tag, &mut buf) { + let mut buf = vec![0xEEu8; decrypt_detached_out_len(ct.len())]; + match key.decrypt_detached_out(&wrong_nonce, aad, &ct, &tag, &mut buf) { Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } other => panic!("a modified nonce must fail the tag check, got {other:?}"), }; assert!(buf.iter().all(|&b| b == 0), "a modified nonce must leave the buffer zeroized"); // two encryptions under the same key must not reuse a nonce - let (_enc1, nonce1) = E::do_encrypt_init(&key).unwrap(); - let (_enc2, nonce2) = E::do_encrypt_init(&key).unwrap(); + let (_enc1, nonce1) = key.create_encryptor(rng).unwrap(); + let (_enc2, nonce2) = key.create_encryptor(rng).unwrap(); assert_ne!(nonce1, nonce2); } - // Output-buffer contract for the AEAD `_out` methods, as the symmetric suite above checks it - // for the inherited ones: a buffer larger than needed is accepted, the returned count is - // what that call wrote, and every byte past it is zeroed, whatever the buffer held on the - // way in. Each buffer is pre-filled with a non-zero sentinel, so a byte left as the caller - // had it shows up. The detached finals' buffers are `[u8; FINAL_LEN]` by type and so - // cannot be oversized; for them only the zeroed tail is checked. + // Output-buffer contract for the AEAD `_out` methods: a buffer larger than needed is + // accepted, the returned count is what that call wrote, and every byte past it is zeroed. const SENTINEL: u8 = 0xA5; const EXTRA: usize = 7; let assert_tail_zeroed = |buf: &[u8], n: usize, what: &str| { @@ -685,45 +602,30 @@ impl TestFrameworkAEADCipher { let len = self.fixed_message_len.unwrap_or(max_len); let msg = &DUMMY_SEED[..len]; - // the detached one-shots - let need = E::encrypt_detached_out_len(len); + let need = encrypt_detached_out_len(len); let mut ct = vec![SENTINEL; need + EXTRA]; - let (nonce, n, tag) = E::encrypt_detached_out(&key, aad, msg, &mut ct).unwrap(); + let (n, nonce, tag) = key.encrypt_detached_out(rng, aad, msg, &mut ct).unwrap(); assert_eq!(n, need, "encrypt_detached_out into an oversized buffer"); assert_tail_zeroed(&ct, n, "encrypt_detached_out"); ct.truncate(n); - let mut buf = vec![SENTINEL; need + EXTRA]; - let (_, n, _) = - E::encrypt_detached_rng_out(&key, &mut FixedSeedRNG::new(pinned), aad, msg, &mut buf) - .unwrap(); - assert_eq!(n, need, "encrypt_detached_rng_out into an oversized buffer"); - assert_tail_zeroed(&buf, n, "encrypt_detached_rng_out"); - let mut pt = vec![SENTINEL; D::decrypt_detached_out_len(ct.len()) + EXTRA]; - let n = D::decrypt_detached_out(&key, &nonce, aad, &ct, &tag, &mut pt).unwrap(); + let mut pt = vec![SENTINEL; decrypt_detached_out_len(ct.len()) + EXTRA]; + let n = key.decrypt_detached_out(&nonce, aad, &ct, &tag, &mut pt).unwrap(); assert_eq!(&pt[..n], msg, "decrypt_detached_out into an oversized buffer"); assert_tail_zeroed(&pt, n, "decrypt_detached_out"); - // the inline one-shots with associated data - let need = E::encrypt_out_len(len); + let need = encrypt_out_len(len); let mut sealed = vec![SENTINEL; need + EXTRA]; - let (nonce, n) = E::encrypt_with_aad_out(&key, aad, msg, &mut sealed).unwrap(); + let (n, nonce) = key.encrypt_with_aad_out(rng, aad, msg, &mut sealed).unwrap(); assert_eq!(n, need, "encrypt_with_aad_out into an oversized buffer"); assert_tail_zeroed(&sealed, n, "encrypt_with_aad_out"); sealed.truncate(n); - let mut buf = vec![SENTINEL; need + EXTRA]; - let (_, n) = - E::encrypt_with_aad_rng_out(&key, &mut FixedSeedRNG::new(pinned), aad, msg, &mut buf) - .unwrap(); - assert_eq!(n, need, "encrypt_with_aad_rng_out into an oversized buffer"); - assert_tail_zeroed(&buf, n, "encrypt_with_aad_rng_out"); - let mut pt = vec![SENTINEL; D::decrypt_out_len(sealed.len()) + EXTRA]; - let n = D::decrypt_with_aad_out(&key, &nonce, aad, &sealed, &mut pt).unwrap(); + let mut pt = vec![SENTINEL; decrypt_out_len(sealed.len()) + EXTRA]; + let n = key.decrypt_with_aad_out(&nonce, aad, &sealed, &mut pt).unwrap(); assert_eq!(&pt[..n], msg, "decrypt_with_aad_out into an oversized buffer"); assert_tail_zeroed(&pt, n, "decrypt_with_aad_out"); - // the detached finals: each reports only what it wrote itself, so with what the update - // released it adds up to exactly the message - let (mut enc, nonce) = E::do_encrypt_init(&key).unwrap(); + // the detached finals: each reports only what it wrote itself + let (mut enc, nonce) = key.create_encryptor(rng).unwrap(); enc.do_update_aad(aad).unwrap(); let mut ct = vec![0u8; enc.do_encrypt_out_len(len)]; let written = enc.do_encrypt_out(msg, &mut ct).unwrap(); @@ -734,10 +636,10 @@ impl TestFrameworkAEADCipher { ct.extend_from_slice(&last[..last_len]); assert_eq!( ct.len(), - E::encrypt_detached_out_len(len), + encrypt_detached_out_len(len), "do_encrypt_out and do_encrypt_final_detached_out must report only their own bytes" ); - let mut dec = D::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(aad).unwrap(); let mut rec = vec![0u8; dec.do_decrypt_out_len(ct.len())]; let released = dec.do_decrypt_out(&ct, &mut rec).unwrap(); @@ -750,9 +652,159 @@ impl TestFrameworkAEADCipher { rec, msg, "do_decrypt_out and do_decrypt_final_detached_out must report only their own bytes" ); + } + + /// The parts of the symmetric-cipher suite an AEAD pair cannot reach through + /// `TestFrameworkSymmetricCipher::test_key` (whose bound is the symmetric role): no AAD, the + /// tag inline, over the key's `create_*`. Streaming through the inline `do_*_final_out` + /// finals in every chunking, a refused short streaming buffer, the per-call output counts + /// at the cuts that catch a running total, and a corrupted inline ciphertext failing at + /// every position. + fn symmetric_contract_over_the_pair< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, + K: AeadEncryptionKey + + AeadDecryptionKey, + >( + &self, + key: &K, + rng: &mut dyn RNG, + encrypt_out_len: &dyn Fn(usize) -> usize, + decrypt_out_len: &dyn Fn(usize) -> usize, + ) { + // the pair's one length if it has one, which may be zero; chunk sizes stay positive + let len = self.fixed_message_len.unwrap_or(3 * TAG_LEN.max(1) + 5); + let msg = &DUMMY_SEED[..len]; + + // streaming in every chunking, finished through the inline `_out` finals, round trips + // and agrees with the one-shot's length + let chunkings: [usize; 6] = + [1, 3, TAG_LEN.max(1), TAG_LEN + 1, 2 * TAG_LEN + 3, len.max(1)]; + for chunk in chunkings { + let (mut enc, nonce) = key.create_encryptor(rng).unwrap(); + let mut ct = Vec::new(); + for piece in msg.chunks(chunk) { + let expect = enc.do_encrypt_out_len(piece.len()); + let mut buf = vec![0u8; expect]; + let n = enc.do_encrypt_out(piece, &mut buf).unwrap(); + assert_eq!(n, expect, "chunk {chunk}: do_encrypt_out_len must be exact"); + ct.extend_from_slice(&buf[..n]); + } + let mut last = [0u8; FINAL_LEN]; + let last_len = enc.do_encrypt_final_out(&mut last).unwrap(); + assert!( + last_len <= FINAL_LEN, + "do_encrypt_final_out must not claim more than FINAL_LEN" + ); + ct.extend_from_slice(&last[..last_len]); + assert_eq!(ct.len(), encrypt_out_len(len), "chunk {chunk}: streaming total"); + + let mut dec = key.create_decryptor(&nonce); + let mut rec = Vec::new(); + for piece in ct.chunks(chunk) { + let expect = dec.do_decrypt_out_len(piece.len()); + let mut buf = vec![0u8; expect]; + let n = dec.do_decrypt_out(piece, &mut buf).unwrap(); + assert_eq!(n, expect, "chunk {chunk}: do_decrypt_out_len must be exact"); + rec.extend_from_slice(&buf[..n]); + } + let mut block = [0u8; FINAL_LEN]; + let data_len = dec.do_decrypt_final_out(&mut block).unwrap(); + rec.extend_from_slice(&block[..data_len]); + assert_eq!(rec, msg, "chunk {chunk}: streamed round trip through do_decrypt_final_out"); + } + + // a too-short streaming buffer is refused with the required length, before any work + let (mut enc, _) = key.create_encryptor(rng).unwrap(); + let need = enc.do_encrypt_out_len(len); + if need > 0 { + let mut short = vec![0u8; need - 1]; + match enc.do_encrypt_out(msg, &mut short) { + Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), + other => panic!("do_encrypt_out into a short buffer: {other:?}"), + } + } - // The key-type and security-strength checks on `do_encrypt_init` / `do_decrypt_init` are - // covered by the `TestFrameworkSymmetricCipher` suite run above. + // Streaming in pieces of 1 byte, FINAL_LEN + 1 bytes and the rest: a cipher that holds + // data back then releases nothing on some calls after earlier calls released data, which is + // where a running total and a per-call count differ. Each call must report its own + // `do_*_out_len`, and the per-call counts must add up to exactly the whole stream's + // length, which a running total would overshoot. The sentinel past the count must be + // zeroed by every call. + const SENTINEL: u8 = 0xA5; + const EXTRA: usize = 7; + let assert_tail_zeroed = |buf: &[u8], n: usize, what: &str| { + assert!( + n <= buf.len(), + "{what}: claims {n} bytes written to a {}-byte buffer", + buf.len() + ); + assert!( + buf[n..].iter().all(|&b| b == 0), + "{what}: the bytes past the {n} written must be zeroed" + ); + }; + let cuts = |total: usize| [0, 1.min(total), (FINAL_LEN + 2).min(total), total]; + let c = cuts(len); + let (mut enc, nonce) = key.create_encryptor(rng).unwrap(); + let mut streamed = Vec::new(); + for w in c.windows(2) { + let piece = &msg[w[0]..w[1]]; + let expect = enc.do_encrypt_out_len(piece.len()); + let mut buf = vec![SENTINEL; expect + EXTRA]; + let n = enc.do_encrypt_out(piece, &mut buf).unwrap(); + assert_eq!(n, expect, "do_encrypt_out must report the bytes this call wrote"); + assert_tail_zeroed(&buf, n, "do_encrypt_out"); + streamed.extend_from_slice(&buf[..n]); + } + let mut last = [SENTINEL; FINAL_LEN]; + let last_len = enc.do_encrypt_final_out(&mut last).unwrap(); + assert_tail_zeroed(&last, last_len, "do_encrypt_final_out"); + streamed.extend_from_slice(&last[..last_len]); + assert_eq!( + streamed.len(), + encrypt_out_len(len), + "the per-call counts must sum to encrypt_out_len, not overshoot it as running totals" + ); + let c = cuts(streamed.len()); + let mut dec = key.create_decryptor(&nonce); + let mut rec = Vec::new(); + for w in c.windows(2) { + let piece = &streamed[w[0]..w[1]]; + let expect = dec.do_decrypt_out_len(piece.len()); + let mut buf = vec![SENTINEL; expect + EXTRA]; + let n = dec.do_decrypt_out(piece, &mut buf).unwrap(); + assert_eq!(n, expect, "do_decrypt_out must report the bytes this call wrote"); + assert_tail_zeroed(&buf, n, "do_decrypt_out"); + rec.extend_from_slice(&buf[..n]); + } + let mut last = [SENTINEL; FINAL_LEN]; + let data_len = dec.do_decrypt_final_out(&mut last).unwrap(); + assert_tail_zeroed(&last, data_len, "do_decrypt_final_out"); + rec.extend_from_slice(&last[..data_len]); + assert_eq!(rec, msg, "the per-call counts must sum to the plaintext"); + + // a corrupted inline ciphertext fails the tag check at every position, through the + // no-AAD one-shot + let ct_len = streamed.len(); + for flip in [0usize, ct_len / 2, ct_len.saturating_sub(1)] { + if ct_len == 0 { + break; + } + let mut bad = streamed.clone(); + bad[flip] ^= 0x80; + let mut pt = vec![0xEEu8; decrypt_out_len(ct_len)]; + match key.decrypt_with_aad_out(&nonce, &[], &bad, &mut pt) { + Err(SymmetricCipherError::AEADTagCheckFailed) => {} + other => panic!("corrupted byte {flip} must fail the tag check, got {other:?}"), + } + assert!( + pt.iter().all(|&b| b == 0), + "corrupted byte {flip}: the buffer must be zeroized" + ); + } } } @@ -776,105 +828,68 @@ impl TestFrameworkAEADTaggedLayout { Self {} } - /// Exercises the inline-layout contract for one encryptor/decryptor pair. - /// - /// Checks, in order: - /// * at every message length from 0 to `4 * TAG_LEN + 5`: the one-shot pair round-trips, the - /// detached one-shot is the same ciphertext with the tag split off, and for every chunking - /// the streaming pair agrees with the one-shot byte for byte -- with the decryptor, not the - /// caller, holding back the possible tag, in both the inline and the detached finalization; - /// * a tampered inline stream fails at finalization on both entry points and the one-shots - /// zeroize their buffer, and an input shorter than the tag is `DecryptionFailed` rather - /// than a panic on the short slice; - /// * every inline one-shot refuses an output buffer one byte short, naming the length it - /// needs, and accepts one of exactly that length. - pub fn test< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, - D: AEADCipherDecryptor + Algorithm, - >( - &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::( - &key, - ); - Self::undersized_buffers_are_rejected::(&key); - } - /// The pinned RNG every encryption draws its nonce from, so that all paths use one nonce. fn rng() -> FixedSeedRNG<[u8; NONCE_LEN]> { - FixedSeedRNG::new(core::array::from_fn(|i| 0xA5 ^ (i as u8))) + FixedSeedRNG::new(core::array::from_fn::(|i| 0xA5 ^ (i as u8))) } - /// Encrypts `msg` into the inline layout with the one-shot, under the pinned nonce, and - /// returns it with that nonce. - fn tagged_ct< + /// The two tag layouts' contract over an [`AeadEncryptionKey`] / + /// [`AeadDecryptionKey`] pair; every nonce is drawn from the pinned RNG. + pub fn test_key< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, + K: AeadEncryptionKey + + AeadDecryptionKey, >( - key: &KeyMaterial, - msg: &[u8], - ) -> (Vec, [u8; NONCE_LEN]) { - let mut ct = vec![0u8; E::encrypt_out_len(msg.len())]; - let (nonce, written) = - E::encrypt_with_aad_rng_out(key, &mut Self::rng::(), AAD, msg, &mut ct) - .unwrap(); - assert_eq!(written, msg.len() + TAG_LEN, "inline layout is ciphertext || tag"); - ct.truncate(written); - (ct, nonce) - } - - fn round_trip_at_every_length_and_chunking< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, - D: AEADCipherDecryptor + Algorithm, - >( - key: &KeyMaterial, + &self, + key: &K, ) { + let encrypt_out_len = |len: usize| { + as SymmetricCipherEncryptor>::encrypt_out_len( + len, + ) + }; + let encrypt_detached_out_len = |len: usize| { + as AEADCipherEncryptor>::encrypt_detached_out_len(len) + }; + let decrypt_out_len = |len: usize| { + as SymmetricCipherDecryptor>::decrypt_out_len( + len, + ) + }; + // Encrypts `msg` into the inline layout with the one-shot, under the pinned nonce. + let tagged_ct = |msg: &[u8]| -> (Vec, [u8; NONCE_LEN]) { + let mut ct = vec![0u8; encrypt_out_len(msg.len())]; + let (written, nonce) = + key.encrypt_with_aad_out(&mut Self::rng::(), AAD, msg, &mut ct).unwrap(); + assert_eq!(written, msg.len() + TAG_LEN, "inline layout is ciphertext || tag"); + ct.truncate(written); + (ct, nonce) + }; + + // round trip at every length and chunking for len in 0..=(4 * TAG_LEN + 5) { let msg = &DUMMY_SEED[..len]; - let (ct, nonce) = - Self::tagged_ct::(key, msg); + let (ct, nonce) = tagged_ct(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(); + let mut pt = vec![0xEEu8; decrypt_out_len(ct.len())]; + let n = key.decrypt_with_aad_out(&nonce, AAD, &ct, &mut pt).unwrap(); assert_eq!(&pt[..n], msg, "len {len}: one-shot round trip"); // The detached layout is the same ciphertext with the tag split off. - let mut detached = vec![0u8; E::encrypt_detached_out_len(len)]; - let (d_nonce, d_len, d_tag) = E::encrypt_detached_rng_out( - key, - &mut Self::rng::(), - AAD, - msg, - &mut detached, - ) - .unwrap(); + let mut detached = vec![0u8; encrypt_detached_out_len(len)]; + let (d_len, d_nonce, d_tag) = key + .encrypt_detached_out(&mut Self::rng::(), AAD, msg, &mut detached) + .unwrap(); assert_eq!(d_nonce, nonce, "len {len}: the pinned RNG must give the same nonce"); assert_eq!(&detached[..d_len], &ct[..len], "len {len}: detached ciphertext"); assert_eq!(&d_tag[..], &ct[len..], "len {len}: detached tag"); for chunk in [1usize, 2, 3, TAG_LEN.max(1), len.max(1)] { - // Encrypt in chunks, finishing with the tag appended by the streaming finalizer. let (mut enc, stream_nonce) = - E::do_encrypt_init_rng(key, &mut Self::rng::()).unwrap(); + key.create_encryptor(&mut Self::rng::()).unwrap(); assert_eq!(stream_nonce, nonce, "len {len}: streaming init draws the same nonce"); enc.do_update_aad(AAD).unwrap(); let mut stream_ct = vec![0u8; len + FINAL_LEN]; @@ -882,8 +897,7 @@ impl TestFrameworkAEADTaggedLayout { for piece in msg.chunks(chunk) { written += enc.do_encrypt_out(piece, &mut stream_ct[written..]).unwrap(); } - let mut last = [0u8; FINAL_LEN]; - let last_len = enc.do_encrypt_final_out(&mut last).unwrap(); + let (last, last_len) = enc.do_encrypt_final().unwrap(); stream_ct[written..written + last_len].copy_from_slice(&last[..last_len]); written += last_len; stream_ct.truncate(written); @@ -892,10 +906,8 @@ impl TestFrameworkAEADTaggedLayout { "len {len}, chunk {chunk}: streaming must match the one-shot" ); - // Decrypt in chunks, tag and all: the decryptor holds the tag back itself, so - // nothing past the plaintext is ever released, and the final call releases - // whatever plaintext it was still holding and nothing more. - let mut dec = D::do_decrypt_init(key, &stream_nonce).unwrap(); + // the decryptor holds the tag back itself + let mut dec = key.create_decryptor(&stream_nonce); dec.do_update_aad(AAD).unwrap(); let mut out = vec![0u8; stream_ct.len() + FINAL_LEN]; let mut written = 0; @@ -914,7 +926,7 @@ impl TestFrameworkAEADTaggedLayout { assert_eq!(out, msg, "len {len}, chunk {chunk}: streaming round trip"); // The same held-back bytes are ciphertext if the tag is detached. - let mut dec = D::do_decrypt_init(key, &stream_nonce).unwrap(); + let mut dec = key.create_decryptor(&stream_nonce); dec.do_update_aad(AAD).unwrap(); let mut out = vec![0u8; len + FINAL_LEN]; let mut written = 0; @@ -933,42 +945,29 @@ impl TestFrameworkAEADTaggedLayout { assert_eq!(out, msg, "len {len}, chunk {chunk}: detached streaming round trip"); } } - } - fn tampering_and_short_input_are_rejected< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, - D: AEADCipherDecryptor + Algorithm, - >( - key: &KeyMaterial, - ) { + // tampering and short input are rejected let msg = &DUMMY_SEED[..10]; - let (ct, nonce) = Self::tagged_ct::(key, msg); - + let (ct, nonce) = tagged_ct(msg); let mut tampered = ct.clone(); tampered[0] ^= 0xFF; let mut pt = vec![0xEEu8; tampered.len()]; assert!(matches!( - D::decrypt_with_aad_out(key, &nonce, AAD, &tampered, &mut pt), + key.decrypt_with_aad_out(&nonce, AAD, &tampered, &mut pt), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(pt, vec![0u8; tampered.len()], "the one-shot zeroizes on a failed tag check"); - - let mut dec = D::do_decrypt_init(key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(AAD).unwrap(); dec.do_decrypt_out(&tampered, &mut pt).unwrap(); assert!(matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::AEADTagCheckFailed))); - // A wrong detached tag fails, and `decrypt_detached_out` zeroizes what it wrote. let mut wrong_tag = [0u8; TAG_LEN]; wrong_tag.copy_from_slice(&ct[msg.len()..]); wrong_tag[0] ^= 0xFF; let mut pt = vec![0xEEu8; msg.len()]; assert!(matches!( - D::decrypt_detached_out(key, &nonce, AAD, &ct[..msg.len()], &wrong_tag, &mut pt), + key.decrypt_detached_out(&nonce, AAD, &ct[..msg.len()], &wrong_tag, &mut pt), Err(SymmetricCipherError::AEADTagCheckFailed) )); assert_eq!(pt, vec![0u8; msg.len()], "the detached one-shot zeroizes on a failed check"); @@ -977,56 +976,38 @@ impl TestFrameworkAEADTaggedLayout { let mut pt = vec![0u8; TAG_LEN]; assert!( matches!( - D::decrypt_with_aad_out(key, &nonce, AAD, &ct[..short_len], &mut pt), + key.decrypt_with_aad_out(&nonce, AAD, &ct[..short_len], &mut pt), Err(SymmetricCipherError::DecryptionFailed) ), "{short_len} bytes cannot carry a {TAG_LEN}-byte tag (one-shot)" ); - let mut dec = D::do_decrypt_init(key, &nonce).unwrap(); + let mut dec = key.create_decryptor(&nonce); assert_eq!(dec.do_decrypt_out(&ct[..short_len], &mut pt).unwrap(), 0); assert!( matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::DecryptionFailed)), "{short_len} bytes cannot carry a {TAG_LEN}-byte tag (streaming)" ); } - } - fn undersized_buffers_are_rejected< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, - E: AEADCipherEncryptor + Algorithm, - D: AEADCipherDecryptor + Algorithm, - >( - key: &KeyMaterial, - ) { + // undersized buffers are rejected, and one of exactly the length asked for is accepted let msg = &DUMMY_SEED[..8]; - let (ct, nonce) = Self::tagged_ct::(key, msg); - - let needed = E::encrypt_out_len(msg.len()); + let (ct, nonce) = tagged_ct(msg); + let needed = encrypt_out_len(msg.len()); assert_eq!(needed, msg.len() + TAG_LEN); let mut short = vec![0u8; needed - 1]; - match E::encrypt_with_aad_rng_out(key, &mut Self::rng::(), AAD, msg, &mut short) - { + match key.encrypt_with_aad_out(&mut Self::rng::(), AAD, msg, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, needed), other => panic!("encrypt_with_aad_out into a short buffer: {other:?}"), } - - let needed = D::decrypt_out_len(ct.len()); + let needed = decrypt_out_len(ct.len()); assert_eq!(needed, msg.len()); let mut short = vec![0u8; needed - 1]; - match D::decrypt_with_aad_out(key, &nonce, AAD, &ct, &mut short) { + match key.decrypt_with_aad_out(&nonce, AAD, &ct, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, needed), other => panic!("decrypt_with_aad_out into a short buffer: {other:?}"), } - - // A buffer of exactly the length it asks for must be accepted. Without this the - // `plaintext.len() < needed` guard can be weakened to `<=` or `==` without any test - // noticing: a too-short buffer is caught either way, by the guard or by `do_update_out` - // behind it, and both report the same error with the same length. let mut exact = vec![0u8; needed]; - let n = D::decrypt_with_aad_out(key, &nonce, AAD, &ct, &mut exact).unwrap(); + let n = key.decrypt_with_aad_out(&nonce, AAD, &ct, &mut exact).unwrap(); assert_eq!(&exact[..n], msg, "a buffer of exactly `needed` bytes must be enough"); } } diff --git a/crypto/core-test-framework/src/block_cipher.rs b/crypto/core-test-framework/src/block_cipher.rs index bee518b7e..3370ae60b 100644 --- a/crypto/core-test-framework/src/block_cipher.rs +++ b/crypto/core-test-framework/src/block_cipher.rs @@ -3,10 +3,9 @@ use crate::DUMMY_SEED; 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::{Algorithm, BlockCipherDecryptor, BlockCipherEncryptor}; +use bouncycastle_core::hazmat::{EcbDecryptionKey, EcbEncryptionKey}; +use bouncycastle_core::symmetric_key::{BlockDecryptionKey, BlockEncryptionKey}; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, RNG}; use bouncycastle_rng::FixedSeedRNG; /// Instance of the test framework. @@ -20,98 +19,127 @@ impl TestFrameworkBlockCipher { Self {} } - /// - pub fn test< + /// The block-cipher contract over a [`BlockEncryptionKey`] / [`BlockDecryptionKey`] pair, with + /// `rng` supplying every init data draw. The key-policy checks are + /// `TestFrameworkFromKeyMaterial`'s. + pub fn test_key< const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, - E: BlockCipherEncryptor + Algorithm, - D: BlockCipherDecryptor + Algorithm, + K: BlockEncryptionKey + + BlockDecryptionKey, >( &self, + key: &K, + rng: &mut dyn RNG, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .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 - // decrypt together. - let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap(); - let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap(); + let (mut encryptor, iv) = key.create_encryptor(rng).unwrap(); + let mut decryptor = key.create_decryptor(&iv); + Self::round_trip_blocks(&mut encryptor, &mut decryptor); - for msg_pair in DUMMY_SEED.as_chunks::().0.as_chunks::<2>().0.iter() { - // encrypt together, decrypt together - let mut buf = *msg_pair; - encryptor.do_encrypt_blocks_inplace(&mut buf).unwrap(); - decryptor.do_decrypt_blocks_inplace(&mut buf).unwrap(); - assert_eq!(msg_pair, &buf); + let (mut encryptor, iv) = key.create_encryptor(rng).unwrap(); + let mut decryptor = key.create_decryptor(&iv); + Self::round_trip_pairs(&mut encryptor, &mut decryptor); - // encrypt together, decrypt one at a time - let mut buf = *msg_pair; - encryptor.do_encrypt_blocks_inplace(&mut buf).unwrap(); - for (msg_chunk, block) in msg_pair.iter().zip(buf.iter_mut()) { - decryptor.do_decrypt_blocks_inplace(block.as_chunks_mut().0).unwrap(); - assert_eq!(msg_chunk, block); - } + // one-shot API: a block-aligned byte array, in place + let one_block: &[u8; BLOCK_LEN] = &DUMMY_SEED.as_chunks::().0[0]; + let mut buf = *one_block; + let (n, iv) = key.encrypt_inplace(rng, &mut buf).unwrap(); + assert_eq!(n, BLOCK_LEN, "encrypt must report the number of bytes written"); + let ct = buf; + let n = key.decrypt_inplace(&iv, &mut buf).unwrap(); + assert_eq!(n, BLOCK_LEN, "decrypt must report the number of bytes written"); + assert_eq!(buf, *one_block); + let mut streamed = key.create_decryptor(&iv); + let mut buf = ct; + streamed.do_decrypt_blocks_inplace(core::slice::from_mut(&mut buf)).unwrap(); + assert_eq!(buf, *one_block); - // encrypt one at a time, decrypt together - let mut buf = *msg_pair; - for block in buf.iter_mut() { - encryptor.do_encrypt_blocks_inplace(block.as_chunks_mut().0).unwrap(); - } - decryptor.do_decrypt_blocks_inplace(&mut buf).unwrap(); - assert_eq!(msg_pair, &buf); + // the RNG-driven one-shot must give the streaming API's answer for the same RNG stream + let pinned = [0xA5u8; INIT_DATA_LEN]; + let mut expected = *one_block; + let (mut streamed, iv_streamed) = + key.create_encryptor(&mut FixedSeedRNG::new(pinned)).unwrap(); + streamed.do_encrypt_blocks_inplace(core::slice::from_mut(&mut expected)).unwrap(); + let mut buf = *one_block; + let (n, iv) = key.encrypt_inplace(&mut FixedSeedRNG::new(pinned), &mut buf).unwrap(); + assert_eq!(n, BLOCK_LEN, "encrypt_inplace must report the number of bytes written"); + assert_eq!(iv, iv_streamed); + assert_eq!(buf, expected); + + // The one-shots take a runtime length, so they must refuse data that is not a whole number + // of blocks, before touching it. + for len in [1, BLOCK_LEN - 1, BLOCK_LEN + 1, 2 * BLOCK_LEN + 1] { + let mut bad = DUMMY_SEED[..len].to_vec(); + assert!( + matches!( + key.encrypt_inplace(rng, &mut bad), + Err(SymmetricCipherError::DataNotBlockAligned) + ), + "len {len}: an unaligned plaintext must be refused" + ); + assert!( + matches!( + key.decrypt_inplace(&iv, &mut bad), + Err(SymmetricCipherError::DataNotBlockAligned) + ), + "len {len}: an unaligned ciphertext must be refused" + ); + assert_eq!(bad, DUMMY_SEED[..len], "len {len}: refused data must be untouched"); } - // one-shot API: a block-aligned byte array, in place. It must round-trip and agree with the - // streaming API for the same key and init data. Only LEN = BLOCK_LEN can be formed - // generically here (`2 * BLOCK_LEN` needs generic_const_exprs); multi-block one-shots are - // covered by the modes crate's tests with a concrete BLOCK_LEN. + // the iv is random: not the same on two runs + let (_encryptor, iv1) = key.create_encryptor(rng).unwrap(); + let (_encryptor, iv2) = key.create_encryptor(rng).unwrap(); + assert_ne!(iv1, iv2); + } + + /// The same contract over an [`EcbEncryptionKey`] / [`EcbDecryptionKey`] pair: no init + /// data, so nothing is drawn and nothing can differ between two runs. + pub fn test_ecb_key< + const KEY_LEN: usize, + const BLOCK_LEN: usize, + K: EcbEncryptionKey + EcbDecryptionKey, + >( + &self, + key: &K, + ) { + let mut encryptor = key.create_encryptor(); + let mut decryptor = key.create_decryptor(); + Self::round_trip_blocks(&mut encryptor, &mut decryptor); + + let mut encryptor = key.create_encryptor(); + let mut decryptor = key.create_decryptor(); + Self::round_trip_pairs(&mut encryptor, &mut decryptor); + let one_block: &[u8; BLOCK_LEN] = &DUMMY_SEED.as_chunks::().0[0]; let mut buf = *one_block; - let (n, iv) = E::encrypt_inplace(&key, &mut buf).unwrap(); + let n = key.encrypt_inplace(&mut buf).unwrap(); assert_eq!(n, BLOCK_LEN, "encrypt must report the number of bytes written"); let ct = buf; - let n = D::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + assert_ne!(ct, *one_block, "encryption must change the block"); + let n = key.decrypt_inplace(&mut buf).unwrap(); assert_eq!(n, BLOCK_LEN, "decrypt must report the number of bytes written"); assert_eq!(buf, *one_block); - // ...and it must agree with the streaming API under the same init data. - let mut streamed = D::do_decrypt_init(&key, &iv).unwrap(); + let mut streamed = key.create_decryptor(); let mut buf = ct; - streamed.do_decrypt_blocks_inplace(buf.as_chunks_mut().0).unwrap(); + streamed.do_decrypt_blocks_inplace(core::slice::from_mut(&mut buf)).unwrap(); assert_eq!(buf, *one_block); // The one-shots take a runtime length, so they must refuse data that is not a whole number // of blocks, before touching it. - for len in [1, BLOCK_LEN - 1, BLOCK_LEN + 1, 2 * BLOCK_LEN + 1] { + for len in [1, BLOCK_LEN - 1, BLOCK_LEN + 1] { let mut bad = DUMMY_SEED[..len].to_vec(); assert!( matches!( - E::encrypt_inplace(&key, &mut bad), + key.encrypt_inplace(&mut bad), Err(SymmetricCipherError::DataNotBlockAligned) ), "len {len}: an unaligned plaintext must be refused" ); - // The RNG one-shot is only driven for a cipher with init data; see below. - if INIT_DATA_LEN > 0 { - assert!( - matches!( - E::encrypt_rng_inplace( - &key, - &mut FixedSeedRNG::new([0xA5u8; INIT_DATA_LEN]), - &mut bad - ), - Err(SymmetricCipherError::DataNotBlockAligned) - ), - "len {len}: an unaligned plaintext must be refused by the RNG one-shot" - ); - } assert!( matches!( - D::decrypt_inplace(&key, &iv, &mut bad), + key.decrypt_inplace(&mut bad), Err(SymmetricCipherError::DataNotBlockAligned) ), "len {len}: an unaligned ciphertext must be refused" @@ -119,86 +147,61 @@ impl TestFrameworkBlockCipher { assert_eq!(bad, DUMMY_SEED[..len], "len {len}: refused data must be untouched"); } - // The RNG-taking constructor is only exercised for a cipher that has init data to - // generate. Its contract requires an implementation with `INIT_DATA_LEN == 0` (ECB) to - // panic instead, so driving it here would fail that implementor for conforming. - if INIT_DATA_LEN > 0 { - // the RNG-taking one-shot must give the streaming API's answer for the same RNG stream - let pinned = [0xA5u8; INIT_DATA_LEN]; - let mut expected = *one_block; - let (mut streamed, iv_streamed) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(pinned)).unwrap(); - streamed.do_encrypt_blocks_inplace(expected.as_chunks_mut().0).unwrap(); - let mut buf = *one_block; - let (n, iv) = - E::encrypt_rng_inplace(&key, &mut FixedSeedRNG::new(pinned), &mut buf).unwrap(); - assert_eq!(n, BLOCK_LEN, "encrypt_rng must report the number of bytes written"); - assert_eq!(iv, iv_streamed); - assert_eq!(buf, expected); - } + // with no init data the codebook is fixed: the same block encrypts the same way twice + let mut again = *one_block; + key.encrypt_inplace(&mut again).unwrap(); + assert_eq!(again, ct); + } - // test that the iv is random (ie not the same on two runs). A mode with no init data at all - // (ECB, INIT_DATA_LEN == 0) has nothing to compare: two empty arrays are always equal. - if INIT_DATA_LEN > 0 { - let (_encryptor, iv1) = E::do_encrypt_init(&key).unwrap(); - let (_encryptor, iv2) = E::do_encrypt_init(&key).unwrap(); - assert_ne!(iv1, iv2); + /// One block at a time, through the flat streaming methods (LEN = BLOCK_LEN), in place. + fn round_trip_blocks< + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + E: BlockCipherEncryptor, + D: BlockCipherDecryptor, + >( + encryptor: &mut E, + decryptor: &mut D, + ) { + for msg_chunk in DUMMY_SEED.as_chunks::().0.iter() { + let mut buf = *msg_chunk; + encryptor.do_encrypt_blocks_inplace(core::slice::from_mut(&mut buf)).unwrap(); + decryptor.do_decrypt_blocks_inplace(core::slice::from_mut(&mut buf)).unwrap(); + assert_eq!(msg_chunk, &buf); } + } + + /// Two blocks at a time through the implementor hook `do_*_blocks_inplace`: blocks encrypted + /// together must decrypt both together and one at a time, and blocks encrypted one at a time + /// must decrypt together. + fn round_trip_pairs< + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + E: BlockCipherEncryptor, + D: BlockCipherDecryptor, + >( + encryptor: &mut E, + decryptor: &mut D, + ) { + for msg_pair in DUMMY_SEED.as_chunks::().0.as_chunks::<2>().0.iter() { + let mut buf = *msg_pair; + encryptor.do_encrypt_blocks_inplace(&mut buf).unwrap(); + decryptor.do_decrypt_blocks_inplace(&mut buf).unwrap(); + assert_eq!(msg_pair, &buf); - // error case: KeyMaterial of wrong type - let mac_key = - KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) - .unwrap(); - match E::do_encrypt_init(&mac_key) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("Unexpected error"), - }; - - // error case: security strengths too weak and too strong - let mut key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - let security_strengths = [ - SecurityStrength::None, - SecurityStrength::_112bit, - SecurityStrength::_128bit, - SecurityStrength::_192bit, - SecurityStrength::_256bit, - ]; - let mut strengths_tested = 0; - for ss in security_strengths.iter() { - // `set_security_strength` enforces its key-length guard even inside a - // do_hazardous_operations() closure -- a KEY_LEN-byte key cannot be tagged at a - // strength above `from_bytes(KEY_LEN)` -- so skip the strengths this key cannot carry - // rather than unwrapping an error. (A 16-byte key can reach 128-bit and no higher.) - // Do NOT "fix" this by relaxing that guard in `KeyMaterial`: core's - // `test_hazardous_ops_error_handling` requires it to stay enforced. - if ss > &SecurityStrength::from_bytes(KEY_LEN) { - continue; + let mut buf = *msg_pair; + encryptor.do_encrypt_blocks_inplace(&mut buf).unwrap(); + for (msg_chunk, block) in msg_pair.iter().zip(buf.iter_mut()) { + decryptor.do_decrypt_blocks_inplace(block.as_chunks_mut().0).unwrap(); + assert_eq!(msg_chunk, block); + } + + let mut buf = *msg_pair; + for block in buf.iter_mut() { + encryptor.do_encrypt_blocks_inplace(block.as_chunks_mut().0).unwrap(); } - // Inside a do_hazardous_operations() closure set_security_strength() raises the - // strength without complaining; any error here is a framework bug, hence unwrap(). - do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); - strengths_tested += 1; - - match E::do_encrypt_init(&key) { - Ok(_) => { - if ss >= &E::MAX_SECURITY_STRENGTH { /* good */ - } else { - panic!("Should have been a strong enough key"); - } - } - Err(SymmetricCipherError::KeyMaterialError(_)) => { - if ss < &E::MAX_SECURITY_STRENGTH { /* good */ - } else { - panic!("Should not have accepted a key weaker than algorithm"); - } - } - _ => panic!("Unexpected error"), - }; + decryptor.do_decrypt_blocks_inplace(&mut buf).unwrap(); + assert_eq!(msg_pair, &buf); } - assert!(strengths_tested > 0, "strength sweep must not be vacuous"); } } diff --git a/crypto/core-test-framework/src/from_key_material.rs b/crypto/core-test-framework/src/from_key_material.rs new file mode 100644 index 000000000..92161569d --- /dev/null +++ b/crypto/core-test-framework/src/from_key_material.rs @@ -0,0 +1,220 @@ +//! Generic behaviour tests for the key constructors [`FromKeyMaterial`] and +//! [`FromAnyKeyMaterial`]: the key-type and security-strength policy that the cipher and MAC +//! suites used to check at every `do_*_init`, now checked once where the key is made. + +use crate::DUMMY_SEED; +use bouncycastle_core::errors::{KeyMaterialError, MACError, SymmetricCipherError}; +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial512, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, FromKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::{AlgorithmInfo, MAC}; +use bouncycastle_rng::FixedSeedRNG; + +/// Instance of the test framework. +pub struct TestFrameworkFromKeyMaterial { + // Put any config options here +} + +impl Default for TestFrameworkFromKeyMaterial { + fn default() -> Self { + Self::new() + } +} + +impl TestFrameworkFromKeyMaterial { + /// + pub fn new() -> Self { + Self {} + } + + /// Exercises the [`FromKeyMaterial`] contract for one key type, named `alg_name` in the + /// failure messages, whose strength is `max_strength`. + /// + /// Checks, in order: + /// * material of the right type is accepted; + /// * material of the wrong [`KeyType`] is refused with [`KeyMaterialError::InvalidKeyType`]; + /// * the security-strength policy matches `max_strength`; + /// * [`FromKeyMaterial::generate`] draws a key from an RNG of sufficient strength, tagged + /// [`FromKeyMaterial::KEY_TYPE`], and refuses one instantiated below the key's strength. + pub fn test>( + &self, + alg_name: &'static str, + max_strength: SecurityStrength, + ) { + let key = KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], K::KEY_TYPE) + .unwrap(); + K::from_key_material(&key).unwrap_or_else(|e| { + panic!("{alg_name}: material of the key's own type must be accepted: {e:?}") + }); + + // the wrong key type + let other_type = match K::KEY_TYPE { + KeyType::MACKey => KeyType::SymmetricCipherKey, + _ => KeyType::MACKey, + }; + let wrong = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], other_type).unwrap(); + match K::from_key_material(&wrong) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + Ok(_) => panic!("a key of the wrong KeyType must be refused"), + Err(e) => panic!("a key of the wrong KeyType must be refused as InvalidKeyType: {e:?}"), + } + + // security strengths too weak, and strong enough + let mut key = key; + for ss in [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ] { + // Skip the strengths a KEY_LEN-byte key cannot carry; see + // `TestFrameworkElectronicCodeBook`. + if ss > SecurityStrength::from_bytes(KEY_LEN) { + continue; + } + do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); + match K::from_key_material(&key) { + Ok(_) => assert!( + ss >= max_strength, + "should have required a key at least as strong as the algorithm ({ss:?})" + ), + Err(SymmetricCipherError::KeyMaterialError(_)) => assert!( + ss < max_strength, + "should not have rejected a key strong enough for the algorithm ({ss:?})" + ), + Err(e) => panic!("unexpected error for a {ss:?} key: {e:?}"), + } + } + + // generate: from a strong enough RNG, and not from a weaker one + let mut rng = + FixedSeedRNG::new(core::array::from_fn::(|i| DUMMY_SEED[200 + i])); + K::generate(&mut rng).expect("generate from a 256-bit RNG"); + let mut weak = + FixedSeedRNG::new(core::array::from_fn::(|i| DUMMY_SEED[200 + i])); + weak.set_security_strength(SecurityStrength::None); + match K::generate(&mut weak) { + Ok(_) => assert!( + max_strength <= SecurityStrength::None, + "generate must refuse an RNG below the key's strength" + ), + Err(SymmetricCipherError::KeyMaterialError(_)) => {} + Err(e) => panic!("unexpected error from generate: {e:?}"), + } + } +} + +/// Instance of the test framework. +pub struct TestFrameworkFromAnyKeyMaterial { + // Put any config options here +} + +impl Default for TestFrameworkFromAnyKeyMaterial { + fn default() -> Self { + Self::new() + } +} + +impl TestFrameworkFromAnyKeyMaterial { + /// + pub fn new() -> Self { + Self {} + } + + /// Exercises the [`FromAnyKeyMaterial`] contract for one MAC key type, given a key its + /// construction accepts (the strength check is what `from_key_material_allow_weak` skips). + /// + /// Checks, in order: + /// * `from_key_material_allow_weak` accepts `key` and produces a working generator; + /// * a key tagged at no security strength is refused by `from_key_material` with + /// [`KeyMaterialError::SecurityStrength`]; + /// * a key one step below the construction's strength is refused by `from_key_material` and + /// accepted by `from_key_material_allow_weak`; + /// * material of the wrong [`KeyType`] is refused with [`KeyMaterialError::InvalidKeyType`]; + /// * [`FromAnyKeyMaterial::generate`] draws a usable key. + pub fn test(&self, key: &impl KeyMaterialTrait) { + let weak_ok = K::from_key_material_allow_weak(key).expect("allow_weak must accept the key"); + let strength = weak_ok.create_generator().max_security_strength(); + let mut generator = weak_ok.create_generator(); + generator.do_update(b"Hi There"); + assert_eq!(generator.do_final().len(), weak_ok.create_generator().output_len()); + + // a no-security (and non-zero) key is refused at any strength + let mut key_none = + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[0..64], KeyType::MACKey).unwrap(); + key_none.set_security_strength(SecurityStrength::None).unwrap(); + match K::from_key_material(&key_none) { + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => {} + _ => panic!("a key of no security strength must be refused as SecurityStrength"), + } + + // one step below the construction's strength: refused, unless weak keys are allowed. + // The key takes the caller's length where that fits, so a construction that accepts + // exactly one length is tested at that length. + let weak_key_len = |default_len: usize, min_len: usize| match key.key_len() { + 1..=64 => key.key_len().max(min_len), + _ => default_len, + }; + let mut low = + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut low, |low| { + match strength { + SecurityStrength::None => { + low.set_key_len(weak_key_len(13, 0))?; + low.set_security_strength(SecurityStrength::None) + } + SecurityStrength::_112bit => { + low.set_key_len(weak_key_len(28, 0))?; + low.set_security_strength(SecurityStrength::None) + } + SecurityStrength::_128bit => { + low.set_key_len(weak_key_len(32, 14))?; + low.set_security_strength(SecurityStrength::_112bit) + } + SecurityStrength::_192bit => { + low.set_key_len(weak_key_len(48, 16))?; + low.set_security_strength(SecurityStrength::_128bit) + } + SecurityStrength::_256bit => { + low.set_key_len(weak_key_len(64, 24))?; + low.set_security_strength(SecurityStrength::_192bit) + } + // `SecurityStrength` is `#[non_exhaustive]`, so this arm is required. + _ => panic!("unhandled SecurityStrength variant -- add a case for it here"), + } + }) + .unwrap(); + assert!(low.security_strength() < strength); + match K::from_key_material(&low) { + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => {} + _ => panic!("a key below the construction's strength must be refused"), + } + let allowed = K::from_key_material_allow_weak(&low).expect("allow_weak must accept it"); + let mut generator = allowed.create_generator(); + generator.do_update(b"Hi There"); + generator.do_final(); + + // the wrong key type + let cipher_key = + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::SymmetricCipherKey) + .unwrap(); + match K::from_key_material(&cipher_key) { + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + _ => panic!("a key that is not a MAC key must be refused as InvalidKeyType"), + } + match K::from_key_material_allow_weak(&cipher_key) { + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + _ => panic!("allow_weak must still refuse a key that is not a MAC key"), + } + + // generate + let mut rng = FixedSeedRNG::new(core::array::from_fn::(|i| DUMMY_SEED[300 + i])); + let generated = K::generate(&mut rng).expect("generate from a 256-bit RNG"); + let mut generator = generated.create_generator(); + generator.do_update(b"Hi There"); + assert_eq!(generator.do_final().len(), generated.create_generator().output_len()); + } +} diff --git a/crypto/core-test-framework/src/key_stream.rs b/crypto/core-test-framework/src/key_stream.rs index 87bedfb67..3f46646ac 100644 --- a/crypto/core-test-framework/src/key_stream.rs +++ b/crypto/core-test-framework/src/key_stream.rs @@ -1,12 +1,7 @@ //! Shared conformance tests for [`KeyStream`] implementors. use crate::DUMMY_SEED; -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::Algorithm; /// Instance of the test framework. pub struct TestFrameworkKeyStream { @@ -25,7 +20,8 @@ impl TestFrameworkKeyStream { Self {} } - /// Exercises the trait contract for one implementor. + /// Exercises the trait contract for one implementor, built by `new` over the init data it is + /// given, over a permutation the caller keys once. /// /// Checks, in order: /// * `apply_blocks` XORs: applied to [`DUMMY_SEED`] it gives `DUMMY_SEED` XOR the keystream it @@ -35,31 +31,25 @@ impl TestFrameworkKeyStream { /// * every chunking of the blocks into `apply_blocks` calls gives the one-call answer -- /// including chunk sizes that are not multiples of any batch width the implementor uses; /// * `remaining_blocks` goes down by exactly one per block applied (a keystream that reports - /// `u64::MAX`, no practical limit, may stay there); - /// * a key of the wrong [`KeyType`] is rejected; - /// * the security-strength policy matches [`Algorithm::MAX_SECURITY_STRENGTH`]. + /// `u64::MAX`, no practical limit, may stay there). /// - /// [`Algorithm::MAX_SECURITY_STRENGTH`]: bouncycastle_core::traits::Algorithm::MAX_SECURITY_STRENGTH + /// The key-policy checks (type, strength, binding) belong to the key object that owns the + /// permutation, and are `TestFrameworkFromKeyMaterial`'s. pub fn test< - const KEY_LEN: usize, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize, - KS: KeyStream + Algorithm, + KS: KeyStream, >( &self, + new: impl Fn(&[u8; INIT_DATA_LEN]) -> KS, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); let init_data = [0xA5u8; INIT_DATA_LEN]; let blocks = DUMMY_SEED.as_chunks::().0; let n = blocks.len(); // The keystream itself: XORed into zeros. let mut keystream = vec![[0u8; BLOCK_LEN]; n]; - KS::new(&key, &init_data).unwrap().apply_blocks(&mut keystream); + new(&init_data).apply_blocks(&mut keystream); assert!( keystream.iter().any(|b| b.iter().any(|&x| x != 0)), "apply_blocks must produce keystream, not leave its input unchanged" @@ -67,7 +57,7 @@ impl TestFrameworkKeyStream { // XOR semantics: applied to data, the result is data XOR keystream. let mut data = blocks.to_vec(); - KS::new(&key, &init_data).unwrap().apply_blocks(&mut data); + new(&init_data).apply_blocks(&mut data); for ((d, k), p) in data.iter().zip(keystream.iter()).zip(blocks.iter()) { let expected: [u8; BLOCK_LEN] = core::array::from_fn(|i| p[i] ^ k[i]); assert_eq!(d, &expected, "apply_blocks must XOR the keystream into its input"); @@ -75,18 +65,18 @@ impl TestFrameworkKeyStream { // Deterministic in (key, init data), and dependent on the init data. let mut again = vec![[0u8; BLOCK_LEN]; n]; - KS::new(&key, &init_data).unwrap().apply_blocks(&mut again); + new(&init_data).apply_blocks(&mut again); assert_eq!(again, keystream, "the same key and init data must give the same keystream"); if INIT_DATA_LEN > 0 { let mut other = vec![[0u8; BLOCK_LEN]; n]; - KS::new(&key, &[0x5Au8; INIT_DATA_LEN]).unwrap().apply_blocks(&mut other); + new(&[0x5Au8; INIT_DATA_LEN]).apply_blocks(&mut other); assert_ne!(other, keystream, "different init data must give a different keystream"); } // Every chunking agrees with the single call, and `remaining_blocks` counts down by one per // block. The chunk sizes straddle the batch widths an implementor is likely to use. for chunk in [1usize, 2, 3, 4, 5, 7, 8, 9, n - 1, n] { - let mut ks = KS::new(&key, &init_data).unwrap(); + let mut ks = new(&init_data); let mut chunked = vec![[0u8; BLOCK_LEN]; n]; for piece in chunked.chunks_mut(chunk) { let before = ks.remaining_blocks(); @@ -104,59 +94,12 @@ impl TestFrameworkKeyStream { } // An empty call produces nothing and consumes nothing. - let mut ks = KS::new(&key, &init_data).unwrap(); + let mut ks = new(&init_data); let before = ks.remaining_blocks(); ks.apply_blocks(&mut []); assert_eq!(ks.remaining_blocks(), before, "an empty call must not consume keystream"); let mut first = [[0u8; BLOCK_LEN]]; ks.apply_blocks(&mut first); assert_eq!(first[0], keystream[0], "an empty call must not advance the keystream"); - - // error case: KeyMaterial of the wrong type - let mac_key = - KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) - .unwrap(); - match KS::new(&mac_key, &init_data) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("A key that is not a SymmetricCipherKey should have been rejected"), - }; - - // error case: security strengths too weak, and strong enough - let mut key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - let security_strengths = [ - SecurityStrength::None, - SecurityStrength::_112bit, - SecurityStrength::_128bit, - SecurityStrength::_192bit, - SecurityStrength::_256bit, - ]; - for ss in security_strengths.iter() { - // `set_security_strength` enforces its key-length guard even inside a - // do_hazardous_operations() closure, so skip the strengths a KEY_LEN-byte key cannot - // carry. Do NOT relax that guard in `KeyMaterial`: core's - // `test_hazardous_ops_error_handling` requires it to stay enforced. - if ss > &SecurityStrength::from_bytes(KEY_LEN) { - continue; - } - - // 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) { - Ok(_) => assert!( - ss >= &KS::MAX_SECURITY_STRENGTH, - "should have required a key at least as strong as the algorithm" - ), - Err(SymmetricCipherError::KeyMaterialError(_)) => assert!( - ss < &KS::MAX_SECURITY_STRENGTH, - "should not have rejected a key strong enough for the algorithm" - ), - _ => panic!("Unexpected error"), - }; - } } } diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index e4a47c75b..fb563a5bc 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -17,6 +17,7 @@ pub mod aead; pub mod block_cipher; pub mod electronic_code_book; +pub mod from_key_material; pub mod hash; pub mod kdf; pub mod kem; diff --git a/crypto/core-test-framework/src/mac.rs b/crypto/core-test-framework/src/mac.rs index 8116510ed..7067bf9e2 100644 --- a/crypto/core-test-framework/src/mac.rs +++ b/crypto/core-test-framework/src/mac.rs @@ -1,10 +1,6 @@ -//! Generic behaviour tests for anything that implements [`MAC`]. +//! Generic behaviour tests for any MAC key object, through the [`MAC`] workers it hands out. -use crate::DUMMY_SEED; -use bouncycastle_core::errors::{KeyMaterialError, MACError}; -use bouncycastle_core::hazmat::do_hazardous_operations; -use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; -use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::MAC; /// Instance of the test framework. @@ -18,137 +14,74 @@ impl TestFrameworkMAC { Self {} } - /// Test all the members of trait Hash against the given input-output pair. - /// This gives good baseline test coverage, but is not exhaustive. - pub fn test_mac( + /// The one-shots and the workers a [`MacGenerationKey`] / [`MacVerificationKey`] hands out, + /// against the given input-output pair. This gives good baseline test coverage, but is not + /// exhaustive; the key-policy checks are `TestFrameworkFromAnyKeyMaterial`'s. + pub fn test_mac_key( &self, - key: &impl KeyMaterialTrait, + key: &K, input: &[u8], expected_output: &[u8], ) { - // Test ::mac() - let out = M::new_allow_weak_key(key).unwrap().mac(input); - assert_eq!(out, expected_output); - - // Test ::mac_out + // the key's one-shots + assert_eq!(key.mac(input), expected_output); let mut out = vec![0u8; expected_output.len()]; - let bytes_written = M::new_allow_weak_key(key).unwrap().mac_out(input, &mut out).unwrap(); + let bytes_written = key.mac_out(input, &mut out).unwrap(); assert_eq!(bytes_written, expected_output.len()); assert_eq!(out, expected_output); - - // Test an output buffer that's too small (should truncate) + // an output buffer that's too small truncates let mut out = vec![0u8; expected_output.len() - 2]; - let bytes_written = M::new_allow_weak_key(key).unwrap().mac_out(input, &mut out).unwrap(); + let bytes_written = key.mac_out(input, &mut out).unwrap(); assert_eq!(bytes_written, expected_output.len() - 2); assert_eq!(out, expected_output[..expected_output.len() - 2]); - - // Test an output buffer that's too big (expect the first L bytes to get filled) - let mut out = vec![0u8; 2 * expected_output.len()]; - let bytes_written = M::new_allow_weak_key(key).unwrap().mac_out(input, &mut out).unwrap(); + // an output buffer that's too big gets the first L bytes filled and the rest zeroed + let mut out = vec![0xA5u8; 2 * expected_output.len()]; + let bytes_written = key.mac_out(input, &mut out).unwrap(); assert_eq!(bytes_written, expected_output.len()); assert_eq!(&out[..expected_output.len()], expected_output); assert_eq!(&out[expected_output.len()..], vec![0u8; expected_output.len()]); - - // Test ::verify() - assert!(M::new_allow_weak_key(key).unwrap().verify(input, expected_output)); - - // Test .new(), .do_update(), .do_mac_final() - // At the same time, test .output_len() - let mut mac = M::new_allow_weak_key(key).unwrap(); - let output_len = mac.output_len(); - mac.do_update(input); - let out = mac.do_final(); + assert!(key.verify(input, expected_output)); + assert!(!key.verify(input, &expected_output[..expected_output.len() - 1])); + assert!(!key.verify(b"not the input", expected_output)); + + // the workers' streaming methods, and the consuming one-shots on the workers + let mut generator = key.create_generator(); + let output_len = generator.output_len(); + generator.do_update(input); + let out = generator.do_final(); assert_eq!(out, expected_output); - - // Test .output_len() assert_eq!(output_len, out.len()); - // Test .init(), .do_update(), .do_mac_final_out() - let mut mac = M::new_allow_weak_key(key).unwrap(); - mac.do_update(input); - let mut out = vec![0u8; mac.output_len()]; - let out_len = mac.do_final_out(&mut *out).unwrap(); + let mut generator = key.create_generator(); + generator.do_update(input); + let mut out = vec![0u8; generator.output_len()]; + let out_len = generator.do_final_out(&mut *out).unwrap(); assert_eq!(out, expected_output); assert_eq!(out_len, out.len()); - // Test .init(), .do_update(), .do_verify_final_out() - let mut mac = M::new_allow_weak_key(key).unwrap(); - mac.do_update(input); - mac.do_verify_final(expected_output); - - // entropy of input key - - // MACs of all security strengths should throw an error on a no-security (and non-zero) key. - - let mut key_none = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[0..64], KeyType::MACKey).unwrap(); - key_none.set_security_strength(SecurityStrength::None).unwrap(); - - match M::new(&key_none) { - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* fine */ } - _ => panic!( - "This should have thrown a KeyMaterialError::SecurityStrength error but it didn't" - ), - } - - // A MAC keyed by a block cipher (CMAC) accepts exactly one key length, so the weak key - // takes the length of the caller's key whenever that fits in the buffer, raised if need be - // to the shortest length `KeyMaterial` lets carry the lowered strength; the per-strength - // defaults apply only to a key that does not fit (empty, or longer than 64 bytes). - let weak_key_len = |default_len: usize, min_len: usize| match key.key_len() { - 1..=64 => key.key_len().max(min_len), - _ => default_len, - }; - let mut low_security_key = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); - do_hazardous_operations(&mut low_security_key, |low_security_key| { - match M::new_allow_weak_key(key).unwrap().max_security_strength() { - SecurityStrength::None => { - low_security_key.set_key_len(weak_key_len(13, 0)).unwrap(); // truncates should be infallible - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_112bit => { - low_security_key.set_key_len(weak_key_len(28, 0)).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_128bit => { - low_security_key.set_key_len(weak_key_len(32, 14)).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::_112bit).unwrap(); - } - SecurityStrength::_192bit => { - low_security_key.set_key_len(weak_key_len(48, 16)).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::_128bit).unwrap(); - } - SecurityStrength::_256bit => { - low_security_key.set_key_len(weak_key_len(64, 24)).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::_192bit).unwrap(); - } - // `SecurityStrength` is `#[non_exhaustive]`, so this arm is required. - _ => panic!( - "unhandled SecurityStrength variant -- add a case for it in the MAC test framework" - ), - }; - Ok(()) - }) - .unwrap(); - - // init - assert!( - low_security_key.security_strength() - < M::new_allow_weak_key(key).unwrap().max_security_strength() - ); - // complains at first - match M::new(&low_security_key) { - Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* fine */ } - _ => { - panic!( - "This should have thrown a KeyMaterialError::SecurityStrength error but it didn't" - ) - } - } - // but fine if you do it with .allow_weak_keys() - let mut hmac = M::new_allow_weak_key(&low_security_key).unwrap(); - hmac.do_update(b"Hi There"); - hmac.do_final(); + assert_eq!(key.create_generator().mac(input), expected_output); + let mut out = vec![0u8; expected_output.len()]; + assert_eq!(key.create_generator().mac_out(input, &mut out).unwrap(), out.len()); + assert_eq!(out, expected_output); + assert!(key.create_verifier().verify(input, expected_output)); + + let mut verifier = key.create_verifier(); + verifier.do_update(input); + assert!(verifier.do_verify_final(expected_output)); + let mut verifier = key.create_verifier(); + verifier.do_update(input); + assert!(!verifier.do_verify_final(&expected_output[1..])); + + // several workers in flight from one key, finished in the other order, and the key + // unchanged by use + let mut a = key.create_generator(); + let mut b = key.create_generator(); + a.do_update(input); + b.do_update(b"another message"); + let tag_b = b.do_final(); + let tag_a = a.do_final(); + assert_eq!(tag_a, expected_output); + assert_eq!(tag_b, key.mac(b"another message")); + assert_eq!(key.mac(input), expected_output); } } diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index d9fa5e1a2..aa52789d3 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -107,12 +107,13 @@ impl TestFrameworkSuspendableKeyedState { /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. pub fn test< + 'k, const SERIALIZED_STATE_LEN: usize, - S: SuspendableKeyed + Clone, + S: SuspendableKeyed<'k, SERIALIZED_STATE_LEN> + Clone, >( &self, instance: &S, - key: &S::Key, + key: &'k S::Key, ) { // There's not a lot we can test here in the abstract, but we can test a few things to // ensure that the SerializableState trait has been impl'd correctly. diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs index f77aa114c..dfd3463ec 100644 --- a/crypto/core-test-framework/src/symmetric_ciphers.rs +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -4,24 +4,22 @@ use crate::DUMMY_SEED; 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::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; use bouncycastle_core::traits::{ - Algorithm, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, + RNG, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; use bouncycastle_rng::FixedSeedRNG; /// Instance of the test framework. pub struct TestFrameworkSymmetricCipher { - /// For [`test_encryptor_decryptor`](Self::test_encryptor_decryptor): the plaintext length + /// For [`test_key`](Self::test_key): the plaintext length /// granularity the pair accepts. 1 (the default) means every length round-trips. A larger value /// -- the block length, for a `PaddedBlockCipherEncryptor` over `NoPadding` -- means only /// multiples of it round-trip, and every other length must be *rejected* by `do_encrypt_final` /// / `encrypt_out` with `DataNotBlockAligned`, which the test then asserts instead. pub required_alignment: usize, - /// For [`test_encryptor_decryptor`](Self::test_encryptor_decryptor): the one message length + /// For [`test_key`](Self::test_key): the one message length /// the pair's streaming methods accept, if they accept only one. `None` (the default) means /// any length. A cipher whose payload length is fixed by its type -- CCM, whose `B0` block /// encodes the payload length, through its `DATA_LEN` parameter -- sets it here: the @@ -37,60 +35,44 @@ impl TestFrameworkSymmetricCipher { Self { required_alignment: 1, fixed_message_len: None } } - /// Exercises the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] contract for a - /// paired implementor. - /// - /// Checks, in order: - /// * the one-shot `encrypt_out` / `decrypt_out` round-trip for every plaintext length from - /// 0 to a few times `FINAL_LEN`, writing exactly `encrypt_out_len` bytes and at most - /// `decrypt_out_len`; - /// * the `std` one-shots agree with the `_out` ones; - /// * streaming in every chunking agrees with the one-shot, `update_out_len` is exact on every - /// call, and `do_encrypt_final_out` / `do_decrypt_final_out` agree with `do_encrypt_final` / - /// `do_decrypt_final`; - /// * a driven RNG reproduces its init data, and the same key and init data give the same - /// ciphertext through `do_encrypt_init_rng` and `encrypt_rng_out`; - /// * a corrupted ciphertext either fails to decrypt or decrypts to something else; - /// * an output buffer that is too short is refused, naming the required length, before any - /// work is done; - /// * every method that writes into a caller's buffer accepts one larger than needed, returns - /// the number of bytes that call wrote (not a running total over the stream), and zeroes - /// every byte of the buffer past that count; - /// * a key of the wrong [`KeyType`] is rejected, and the security-strength policy matches - /// [`Algorithm::MAX_SECURITY_STRENGTH`]. - /// - /// [`Algorithm::MAX_SECURITY_STRENGTH`]: bouncycastle_core::traits::Algorithm::MAX_SECURITY_STRENGTH - pub fn test_encryptor_decryptor< + /// The [`SymmetricEncryptionKey`] / [`SymmetricDecryptionKey`] form of + /// the symmetric-cipher contract reached + /// through a key object, with `rng` supplying every init data draw. The key-policy checks + /// are not here, since the key is already made; see `TestFrameworkFromKeyMaterial`. + pub fn test_key< const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, - E: SymmetricCipherEncryptor + Algorithm, - D: SymmetricCipherDecryptor + Algorithm, + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, >( &self, + key: &K, + rng: &mut dyn RNG, ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .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); if let Some(fixed) = self.fixed_message_len { assert!(fixed <= DUMMY_SEED.len(), "fixed_message_len must fit the seed buffer"); } + let encrypt_out_len = |len: usize| { + as SymmetricCipherEncryptor>::encrypt_out_len(len) + }; + let decrypt_out_len = |len: usize| { + as SymmetricCipherDecryptor>::decrypt_out_len(len) + }; // one-shot round trip, every (accepted) length; every other length must be refused for len in 0..=max_len { let msg = &DUMMY_SEED[..len]; if !len.is_multiple_of(align) { - let mut ct = vec![0u8; E::encrypt_out_len(len) + FINAL_LEN]; - match E::encrypt_out(&key, msg, &mut ct) { + let mut ct = vec![0u8; encrypt_out_len(len) + FINAL_LEN]; + match key.encrypt_out(rng, msg, &mut ct) { Err(SymmetricCipherError::DataNotBlockAligned) => {} other => panic!("len {len} is not aligned and must be refused, got {other:?}"), } - let (mut enc, _) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = key.create_encryptor(rng).unwrap(); let mut buf = vec![0u8; enc.do_encrypt_out_len(len)]; enc.do_encrypt_out(msg, &mut buf).unwrap(); assert!( @@ -102,19 +84,17 @@ impl TestFrameworkSymmetricCipher { ); continue; } - // a fixed-length pair's streaming methods take only that length; its one-shots take - // any length and must round trip it + // a fixed-length pair takes only that length, through the key's one-shots as well as + // its streams, since the one-shots are written over the streams if let Some(fixed) = self.fixed_message_len && fixed != len { - let mut ct = vec![0u8; E::encrypt_out_len(len)]; - let (init, n) = E::encrypt_out(&key, msg, &mut ct).unwrap_or_else(|e| { - panic!("fixed length: a {len}-byte one-shot must be accepted: {e:?}") - }); - let mut pt = vec![0u8; D::decrypt_out_len(n)]; - let m = D::decrypt_out(&key, &init, &ct[..n], &mut pt).unwrap(); - assert_eq!(&pt[..m], msg, "fixed length: {len}-byte one-shot round trip"); - let (mut enc, _) = E::do_encrypt_init(&key).unwrap(); + let mut ct = vec![0u8; encrypt_out_len(len)]; + assert!( + key.encrypt_out(rng, msg, &mut ct).is_err(), + "fixed length: a {len}-byte one-shot must be refused" + ); + let (mut enc, _) = key.create_encryptor(rng).unwrap(); let mut buf = vec![0u8; enc.do_encrypt_out_len(len)]; assert!( enc.do_encrypt_out(msg, &mut buf).is_err() || enc.do_encrypt_final().is_err(), @@ -122,21 +102,21 @@ impl TestFrameworkSymmetricCipher { ); continue; } - let mut ct = vec![0u8; E::encrypt_out_len(len)]; - let (init_data, ct_len) = E::encrypt_out(&key, msg, &mut ct).unwrap(); + let mut ct = vec![0u8; encrypt_out_len(len)]; + let (ct_len, init_data) = key.encrypt_out(rng, msg, &mut ct).unwrap(); assert_eq!(ct_len, ct.len(), "encrypt_out must write exactly encrypt_out_len bytes"); - let mut pt = vec![0u8; D::decrypt_out_len(ct_len)]; - let pt_len = D::decrypt_out(&key, &init_data, &ct[..ct_len], &mut pt).unwrap(); + let mut pt = vec![0u8; decrypt_out_len(ct_len)]; + let pt_len = key.decrypt_out(&init_data, &ct[..ct_len], &mut pt).unwrap(); assert!(pt_len <= pt.len(), "decrypt_out_len must bound the plaintext"); assert_eq!(&pt[..pt_len], msg, "one-shot round trip, len {len}"); // the std one-shots agree with the _out ones for the same init data - let (init_data2, ct2) = E::encrypt(&key, msg).unwrap(); + let (init_data2, ct2) = key.encrypt(rng, msg).unwrap(); assert_eq!(ct2.len(), ct_len, "encrypt must return exactly the bytes written"); - let pt2 = D::decrypt(&key, &init_data2, &ct2).unwrap(); + let pt2 = key.decrypt(&init_data2, &ct2).unwrap(); assert_eq!(pt2, msg, "std round trip, len {len}"); - let pt3 = D::decrypt(&key, &init_data, &ct[..ct_len]).unwrap(); + let pt3 = key.decrypt(&init_data, &ct[..ct_len]).unwrap(); assert_eq!(pt3, msg, "decrypt must agree with decrypt_out"); } @@ -147,7 +127,7 @@ impl TestFrameworkSymmetricCipher { [1, 2, 3, 7, FINAL_LEN.max(1), FINAL_LEN + 1, 2 * FINAL_LEN + 3, len.max(1)]; for chunk in chunkings { // encrypt in chunks, checking update_out_len is exact each time - let (mut enc, init_data) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, init_data) = key.create_encryptor(rng).unwrap(); let mut ct = Vec::new(); for piece in msg.chunks(chunk) { let expect = enc.do_encrypt_out_len(piece.len()); @@ -165,13 +145,13 @@ impl TestFrameworkSymmetricCipher { ct.extend_from_slice(&last[..last_len]); assert_eq!( ct.len(), - E::encrypt_out_len(len), + encrypt_out_len(len), "streaming total must match encrypt_out_len" ); // one-shot decrypt of the streamed ciphertext - let mut pt = vec![0u8; D::decrypt_out_len(ct.len())]; - let m = D::decrypt_out(&key, &init_data, &ct, &mut pt).unwrap(); + let mut pt = vec![0u8; decrypt_out_len(ct.len())]; + let m = key.decrypt_out(&init_data, &ct, &mut pt).unwrap(); assert_eq!( &pt[..m], msg, @@ -180,7 +160,7 @@ impl TestFrameworkSymmetricCipher { // decrypt in the same chunks, via do_decrypt_final and via do_decrypt_final_out for use_out in [false, true] { - let mut dec = D::do_decrypt_init(&key, &init_data).unwrap(); + let mut dec = key.create_decryptor(&init_data); let mut rec = Vec::new(); for piece in ct.chunks(chunk) { let expect = dec.do_decrypt_out_len(piece.len()); @@ -205,10 +185,9 @@ impl TestFrameworkSymmetricCipher { } // a fixed message length is enforced on both sides: one byte more is refused at the - // update, consuming nothing, and one byte fewer is refused at the final. Which variant - // each refusal carries is the implementor's to pin; here only that it refuses. + // update, consuming nothing, and one byte fewer is refused at the final. if let Some(fixed) = self.fixed_message_len { - let (mut enc, init_data) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, init_data) = key.create_encryptor(rng).unwrap(); let mut ct = vec![0u8; enc.do_encrypt_out_len(fixed)]; let n = enc.do_encrypt_out(msg, &mut ct).unwrap(); ct.truncate(n); @@ -217,22 +196,21 @@ impl TestFrameworkSymmetricCipher { enc.do_encrypt_out(&DUMMY_SEED[..1], &mut more).is_err(), "fixed length: one byte more must be refused at the update" ); - // ...and the refusal consumed nothing: the final still completes the message. let (last, last_len) = enc.do_encrypt_final().unwrap(); ct.extend_from_slice(&last[..last_len]); - let mut pt = vec![0u8; D::decrypt_out_len(ct.len())]; - let m = D::decrypt_out(&key, &init_data, &ct, &mut pt).unwrap(); + let mut pt = vec![0u8; decrypt_out_len(ct.len())]; + let m = key.decrypt_out(&init_data, &ct, &mut pt).unwrap(); assert_eq!(&pt[..m], msg, "fixed length: a refused update must not disturb the state"); if fixed > 0 { - let (mut enc, _) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = key.create_encryptor(rng).unwrap(); let mut buf = vec![0u8; enc.do_encrypt_out_len(fixed - 1)]; enc.do_encrypt_out(&msg[..fixed - 1], &mut buf).unwrap(); assert!( enc.do_encrypt_final().is_err(), "fixed length: one byte fewer must be refused at the final (encrypt)" ); - let mut dec = D::do_decrypt_init(&key, &init_data).unwrap(); + let mut dec = key.create_decryptor(&init_data); let mut buf = vec![0u8; dec.do_decrypt_out_len(fixed - 1)]; dec.do_decrypt_out(&ct[..fixed - 1], &mut buf).unwrap(); assert!( @@ -240,7 +218,7 @@ impl TestFrameworkSymmetricCipher { "fixed length: one byte fewer must be refused at the final (decrypt)" ); } - let mut dec = D::do_decrypt_init(&key, &init_data).unwrap(); + let mut dec = key.create_decryptor(&init_data); let mut rec = vec![0u8; dec.do_decrypt_out_len(ct.len())]; let n = dec.do_decrypt_out(&ct, &mut rec).unwrap(); rec.truncate(n); @@ -257,40 +235,41 @@ impl TestFrameworkSymmetricCipher { ); } - // The RNG-taking constructor is only exercised for a cipher that has init data to - // generate. Its contract requires an implementation with `INIT_DATA_LEN == 0` (ECB) to - // panic instead, so driving it here would fail that implementor for conforming. if INIT_DATA_LEN > 0 { // a driven RNG reproduces its init data, and determines the ciphertext let seed: [u8; INIT_DATA_LEN] = core::array::from_fn(|i| DUMMY_SEED[100 + i]); - let (mut enc, init_data) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(seed)).unwrap(); + let (mut enc, init_data) = key.create_encryptor(&mut FixedSeedRNG::new(seed)).unwrap(); assert_eq!(init_data, seed, "a fixed RNG must yield its stream as the init data"); let mut streamed = vec![0u8; enc.do_encrypt_out_len(len)]; let n = enc.do_encrypt_out(msg, &mut streamed).unwrap(); streamed.truncate(n); let (last, last_len) = enc.do_encrypt_final().unwrap(); streamed.extend_from_slice(&last[..last_len]); - let mut one_shot = vec![0u8; E::encrypt_out_len(len)]; - let (init_data2, n2) = - E::encrypt_rng_out(&key, &mut FixedSeedRNG::new(seed), msg, &mut one_shot).unwrap(); + let mut one_shot = vec![0u8; encrypt_out_len(len)]; + let (n2, init_data2) = + key.encrypt_out(&mut FixedSeedRNG::new(seed), msg, &mut one_shot).unwrap(); assert_eq!(init_data2, seed); assert_eq!( &one_shot[..n2], &streamed[..], "same key and init data must give the same ciphertext" ); + + // ...and two draws from a real RNG differ + let (_, a) = key.create_encryptor(rng).unwrap(); + let (_, b) = key.create_encryptor(rng).unwrap(); + assert_ne!(a, b, "two encryptions must not share init data"); } // corrupting the ciphertext does not give back the plaintext (or fails to decrypt) - let mut ct = vec![0u8; E::encrypt_out_len(len)]; - let (init_data, ct_len) = E::encrypt_out(&key, msg, &mut ct).unwrap(); + let mut ct = vec![0u8; encrypt_out_len(len)]; + let (ct_len, init_data) = key.encrypt_out(rng, msg, &mut ct).unwrap(); assert!(ct_len > 0, "the test message is non-empty, so its ciphertext must be"); for flip in [0usize, ct_len / 2, ct_len - 1] { let mut bad = ct[..ct_len].to_vec(); bad[flip] ^= 0x80; - let mut pt = vec![0u8; D::decrypt_out_len(ct_len)]; - match D::decrypt_out(&key, &init_data, &bad, &mut pt) { + let mut pt = vec![0u8; decrypt_out_len(ct_len)]; + match key.decrypt_out(&init_data, &bad, &mut pt) { Ok(m) => { assert_ne!(&pt[..m], msg, "corrupted byte {flip} decrypted to the plaintext") } @@ -302,29 +281,28 @@ impl TestFrameworkSymmetricCipher { } // too-short output buffers are refused with the required length, before any work is done - let need = E::encrypt_out_len(len); + let need = encrypt_out_len(len); let mut short = vec![0u8; need - 1]; - match E::encrypt_out(&key, msg, &mut short) { + match key.encrypt_out(rng, msg, &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("encrypt_out into a short buffer: {other:?}"), } - let need = D::decrypt_out_len(ct_len); + let need = decrypt_out_len(ct_len); if need > 0 { let mut short = vec![0u8; need - 1]; - match D::decrypt_out(&key, &init_data, &ct[..ct_len], &mut short) { + match key.decrypt_out(&init_data, &ct[..ct_len], &mut short) { Err(SymmetricCipherError::OutputBufferTooSmall(n)) => assert_eq!(n, need), other => panic!("decrypt_out into a short buffer: {other:?}"), } } - // ...and ones with room to spare are accepted: without this each `<` guard can be flipped - // to `>` and the short-buffer probes above still "pass". - let mut roomy = vec![0u8; E::encrypt_out_len(len) + 3]; - let (_, n) = E::encrypt_out(&key, msg, &mut roomy).unwrap(); - assert_eq!(n, E::encrypt_out_len(len), "encrypt_out into a roomy buffer"); + // ...and ones with room to spare are accepted + let mut roomy = vec![0u8; encrypt_out_len(len) + 3]; + let (n, _) = key.encrypt_out(rng, msg, &mut roomy).unwrap(); + assert_eq!(n, encrypt_out_len(len), "encrypt_out into a roomy buffer"); let mut roomy = vec![0u8; need + 3]; - let n = D::decrypt_out(&key, &init_data, &ct[..ct_len], &mut roomy).unwrap(); + let n = key.decrypt_out(&init_data, &ct[..ct_len], &mut roomy).unwrap(); assert_eq!(&roomy[..n], msg, "decrypt_out into a roomy buffer"); - let (mut enc, _) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = key.create_encryptor(rng).unwrap(); let need = enc.do_encrypt_out_len(len); if need > 0 { let mut short = vec![0u8; need - 1]; @@ -335,11 +313,8 @@ impl TestFrameworkSymmetricCipher { } // Output-buffer contract, for every method that writes into a caller's buffer: a buffer - // larger than needed is accepted; the returned count is the number of bytes *that call* - // wrote, not a running total over the stream; and every byte past it is zeroed, whatever - // the buffer held on the way in. Each buffer is pre-filled with a non-zero sentinel, so a - // byte left as the caller had it shows up. The `_final_out` buffers are `[u8; FINAL_LEN]` - // by type and so cannot be oversized; for them only the zeroed tail is checked. + // larger than needed is accepted, the returned count is what that call wrote, and every + // byte past it is zeroed. const SENTINEL: u8 = 0xA5; const EXTRA: usize = 7; let assert_tail_zeroed = |buf: &[u8], n: usize, what: &str| { @@ -354,26 +329,14 @@ impl TestFrameworkSymmetricCipher { ); }; - // the one-shots - let need = E::encrypt_out_len(len); + let need = encrypt_out_len(len); let mut ct = vec![SENTINEL; need + EXTRA]; - let (init_data, ct_len) = E::encrypt_out(&key, msg, &mut ct).unwrap(); + let (ct_len, init_data) = key.encrypt_out(rng, msg, &mut ct).unwrap(); assert_eq!(ct_len, need, "encrypt_out into an oversized buffer must write encrypt_out_len"); assert_tail_zeroed(&ct, ct_len, "encrypt_out"); ct.truncate(ct_len); - if INIT_DATA_LEN > 0 { - let seed: [u8; INIT_DATA_LEN] = core::array::from_fn(|i| DUMMY_SEED[100 + i]); - let mut buf = vec![SENTINEL; need + EXTRA]; - let (_, n) = - E::encrypt_rng_out(&key, &mut FixedSeedRNG::new(seed), msg, &mut buf).unwrap(); - assert_eq!( - n, need, - "encrypt_rng_out into an oversized buffer must write encrypt_out_len" - ); - assert_tail_zeroed(&buf, n, "encrypt_rng_out"); - } - let mut pt = vec![SENTINEL; D::decrypt_out_len(ct_len) + EXTRA]; - let n = D::decrypt_out(&key, &init_data, &ct, &mut pt).unwrap(); + let mut pt = vec![SENTINEL; decrypt_out_len(ct_len) + EXTRA]; + let n = key.decrypt_out(&init_data, &ct, &mut pt).unwrap(); assert_eq!(&pt[..n], msg, "decrypt_out into an oversized buffer"); assert_tail_zeroed(&pt, n, "decrypt_out"); @@ -384,7 +347,7 @@ impl TestFrameworkSymmetricCipher { // length, which a running total would overshoot. let cuts = |total: usize| [0, 1.min(total), (FINAL_LEN + 2).min(total), total]; let c = cuts(len); - let (mut enc, init_data) = E::do_encrypt_init(&key).unwrap(); + let (mut enc, init_data) = key.create_encryptor(rng).unwrap(); let mut streamed = Vec::new(); for w in c.windows(2) { let piece = &msg[w[0]..w[1]]; @@ -401,12 +364,12 @@ impl TestFrameworkSymmetricCipher { streamed.extend_from_slice(&last[..last_len]); assert_eq!( streamed.len(), - E::encrypt_out_len(len), + encrypt_out_len(len), "the per-call counts must sum to encrypt_out_len, not overshoot it as running totals" ); let c = cuts(streamed.len()); - let mut dec = D::do_decrypt_init(&key, &init_data).unwrap(); + let mut dec = key.create_decryptor(&init_data); let mut rec = Vec::new(); for w in c.windows(2) { let piece = &streamed[w[0]..w[1]]; @@ -425,59 +388,6 @@ impl TestFrameworkSymmetricCipher { rec, msg, "the per-call counts must sum to the plaintext, not overshoot it as running totals" ); - - // error case: KeyMaterial of the wrong type - let mac_key = - KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) - .unwrap(); - match E::do_encrypt_init(&mac_key) { - 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) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("A key that is not a SymmetricCipherKey should have been rejected"), - }; - - // error case: security strengths too weak, and strong enough - let mut key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - let security_strengths = [ - SecurityStrength::None, - SecurityStrength::_112bit, - SecurityStrength::_128bit, - SecurityStrength::_192bit, - SecurityStrength::_256bit, - ]; - for ss in security_strengths.iter() { - // Skip the strengths a KEY_LEN-byte key cannot carry; see `TestFrameworkElectronicCodeBook`. - if ss > &SecurityStrength::from_bytes(KEY_LEN) { - continue; - } - do_hazardous_operations(&mut key, |key| key.set_security_strength(*ss)).unwrap(); - - match E::do_encrypt_init(&key) { - Ok(_) => assert!( - ss >= &E::MAX_SECURITY_STRENGTH, - "should have required a key at least as strong as the algorithm" - ), - Err(SymmetricCipherError::KeyMaterialError(_)) => assert!( - ss < &E::MAX_SECURITY_STRENGTH, - "should not have rejected a key strong enough for the algorithm" - ), - _ => panic!("Unexpected error"), - }; - match D::do_decrypt_init(&key, &init_data) { - Ok(_) => assert!(ss >= &D::MAX_SECURITY_STRENGTH), - Err(SymmetricCipherError::KeyMaterialError(_)) => { - assert!(ss < &D::MAX_SECURITY_STRENGTH) - } - _ => panic!("Unexpected error"), - }; - } } } @@ -492,43 +402,39 @@ impl TestFrameworkStreamCipher { Self {} } - /// Test the contract of a [`StreamCipherEncryptor`] / [`StreamCipherDecryptor`] pair: every - /// chunking of the streaming API agrees with the one-shot and round-trips through the other - /// direction, the RNG-taking constructors reproduce their init data, and the key-type and - /// security-strength policy is enforced. This gives good baseline test coverage, but is not - /// exhaustive; algorithm-specific test vectors belong in the implementing crate. - pub fn test< + /// The stream-cipher contract over a [`SymmetricEncryptionKey`] / + /// [`SymmetricDecryptionKey`] pair whose workers are stream ciphers, with `rng` supplying + /// every init data draw. The key-policy checks are `TestFrameworkFromKeyMaterial`'s. + pub fn test_key< + 'k, const KEY_LEN: usize, const INIT_DATA_LEN: usize, - E: StreamCipherEncryptor + Algorithm, - D: StreamCipherDecryptor + Algorithm, + K: SymmetricEncryptionKey + + SymmetricDecryptionKey, >( &self, - ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - + key: &'k K, + rng: &mut dyn RNG, + ) where + K::Encryptor<'k>: StreamCipherEncryptor, + K::Decryptor<'k>: StreamCipherDecryptor, + { // one-shot, in place: must round-trip, and report every byte as written. let mut buf = *DUMMY_SEED; - let (n, iv) = E::encrypt_inplace(&key, &mut buf).unwrap(); + let (n, iv) = key.encrypt_inplace(rng, &mut buf).unwrap(); assert_eq!(n, buf.len(), "encrypt must report the number of bytes written"); let reference_ct = buf; assert_ne!(&reference_ct[..], &DUMMY_SEED[..], "encryption must change the data"); - let n = D::decrypt_inplace(&key, &iv, &mut buf).unwrap(); + let n = key.decrypt_inplace(&iv, &mut buf).unwrap(); assert_eq!(n, buf.len(), "decrypt must report the number of bytes written"); assert_eq!(&buf[..], &DUMMY_SEED[..]); // the streaming API under the same init data must give the one-shot's answer whatever - // the chunking, including chunks that are not a multiple of any internal keystream block - // and empty chunks; and encrypting in one chunking must decrypt in any other. + // the chunking, and encrypting in one chunking must decrypt in any other. let chunkings: &[usize] = &[1, 3, 7, 16, 63, 64, 65, 250, DUMMY_SEED.len()]; for &enc_chunk in chunkings { let mut buf = *DUMMY_SEED; - let (mut encryptor, iv2) = E::do_encrypt_init(&key).unwrap(); - // stream through the encryptor, with an empty chunk thrown in at the start and end + let (mut encryptor, iv2) = key.create_encryptor(rng).unwrap(); encryptor.do_encrypt_inplace(&mut []).unwrap(); for chunk in buf.chunks_mut(enc_chunk) { encryptor.do_encrypt_inplace(chunk).unwrap(); @@ -538,7 +444,7 @@ impl TestFrameworkStreamCipher { for &dec_chunk in chunkings { let mut buf = ct; - let mut decryptor = D::do_decrypt_init(&key, &iv2).unwrap(); + let mut decryptor = key.create_decryptor(&iv2); decryptor.do_decrypt_inplace(&mut []).unwrap(); for chunk in buf.chunks_mut(dec_chunk) { decryptor.do_decrypt_inplace(chunk).unwrap(); @@ -553,119 +459,62 @@ impl TestFrameworkStreamCipher { // and the one-shot decrypt agrees with every streaming encryption let mut buf = ct; - D::decrypt_inplace(&key, &iv2, &mut buf).unwrap(); + key.decrypt_inplace(&iv2, &mut buf).unwrap(); assert_eq!(&buf[..], &DUMMY_SEED[..]); } // the streaming decryptor must agree with the one-shot encryptor under its init data let mut buf = reference_ct; - let mut streamed = D::do_decrypt_init(&key, &iv).unwrap(); + let mut streamed = key.create_decryptor(&iv); for chunk in buf.chunks_mut(5) { streamed.do_decrypt_inplace(chunk).unwrap(); } assert_eq!(&buf[..], &DUMMY_SEED[..]); - // The RNG-taking constructor is only exercised for a cipher that has init data to - // generate. Its contract requires an implementation with `INIT_DATA_LEN == 0` (ECB) to - // panic instead, so driving it here would fail that implementor for conforming. - if INIT_DATA_LEN > 0 { - // the RNG-taking one-shot must give the streaming API's answer for the same RNG stream, - // and the same init data. - let pinned = [0xA5u8; INIT_DATA_LEN]; - let mut expected = *DUMMY_SEED; - let (mut streamed, iv_streamed) = - E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::new(pinned)).unwrap(); - streamed.do_encrypt_inplace(&mut expected).unwrap(); - let mut buf = *DUMMY_SEED; - let (n, iv) = - E::encrypt_rng_inplace(&key, &mut FixedSeedRNG::new(pinned), &mut buf).unwrap(); - assert_eq!(n, buf.len(), "encrypt_rng must report the number of bytes written"); - assert_eq!(iv, iv_streamed); - assert_eq!(&buf[..], &expected[..]); - // ...and a driven RNG determines the ciphertext: the same RNG stream again gives the same - // init data and ciphertext, so the ciphertext is a function of (key, init data) alone. - let mut buf2 = *DUMMY_SEED; - let (_, iv_again) = - E::encrypt_rng_inplace(&key, &mut FixedSeedRNG::new(pinned), &mut buf2).unwrap(); - assert_eq!(iv, iv_again); - assert_eq!(&buf[..], &buf2[..]); - } - - // test that the init data is random (ie not the same on two runs). A cipher with no init - // data at all (INIT_DATA_LEN == 0) has nothing to compare: two empty arrays are always equal. - if INIT_DATA_LEN > 0 { - let (_encryptor, iv1) = E::do_encrypt_init(&key).unwrap(); - let (_encryptor, iv2) = E::do_encrypt_init(&key).unwrap(); - assert_ne!(iv1, iv2); - // and different init data under the same key gives different ciphertext - let mut a = *DUMMY_SEED; - let mut b = *DUMMY_SEED; - let (_, iv_a) = E::encrypt_inplace(&key, &mut a).unwrap(); - let (_, iv_b) = E::encrypt_inplace(&key, &mut b).unwrap(); - assert_ne!(iv_a, iv_b); - assert_ne!(&a[..], &b[..]); - } - - // error case: KeyMaterial of wrong type, for both directions - let mac_key = - KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) - .unwrap(); - match E::do_encrypt_init(&mac_key) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("Unexpected error"), - }; - match D::do_decrypt_init(&mac_key, &[0u8; INIT_DATA_LEN]) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("Unexpected error"), - }; - - // error case: security strengths too weak and too strong - let mut key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - let security_strengths = [ - SecurityStrength::None, - SecurityStrength::_112bit, - SecurityStrength::_128bit, - SecurityStrength::_192bit, - SecurityStrength::_256bit, - ]; - for ss in security_strengths.iter() { - // `set_security_strength` enforces its key-length guard even inside a - // do_hazardous_operations() closure -- a KEY_LEN-byte key cannot be tagged at a - // strength above `from_bytes(KEY_LEN)` -- so skip the strengths this key cannot carry - // rather than unwrapping an error. (A 16-byte key can reach 128-bit and no higher.) - // Do NOT "fix" this by relaxing that guard in `KeyMaterial`: core's - // `test_hazardous_ops_error_handling` requires it to stay enforced. - if ss > &SecurityStrength::from_bytes(KEY_LEN) { - continue; - } - - // 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(); - - let check = |r: Result<(), SymmetricCipherError>, max: &SecurityStrength| match r { - Ok(_) => { - if ss >= max { /* good */ - } else { - panic!("Should have been a strong enough key"); - } - } - Err(SymmetricCipherError::KeyMaterialError(_)) => { - if ss < max { /* good */ - } else { - panic!("Should not have accepted a key weaker than algorithm"); - } - } - _ => 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(|_| ()), - &D::MAX_SECURITY_STRENGTH, + // the separate-output methods of the symmetric traits agree with the in-place ones + let out_len = + as SymmetricCipherEncryptor>::encrypt_out_len( + DUMMY_SEED.len(), ); - } + let mut out = vec![0u8; out_len]; + let (n, iv3) = key.encrypt_out(rng, DUMMY_SEED, &mut out).unwrap(); + assert_eq!(n, DUMMY_SEED.len(), "a stream cipher never changes the length"); + let mut back = out.clone(); + key.decrypt_inplace(&iv3, &mut back).unwrap(); + assert_eq!(&back[..], &DUMMY_SEED[..]); + let pt_len = + as SymmetricCipherDecryptor>::decrypt_out_len(n); + let mut pt = vec![0u8; pt_len]; + let m = key.decrypt_out(&iv3, &out[..n], &mut pt).unwrap(); + assert_eq!(&pt[..m], &DUMMY_SEED[..]); + + // the RNG-driven one-shot must give the streaming API's answer for the same RNG stream, + // and the same init data; a driven RNG determines the ciphertext. + let pinned = [0xA5u8; INIT_DATA_LEN]; + let mut expected = *DUMMY_SEED; + let (mut streamed, iv_streamed) = + key.create_encryptor(&mut FixedSeedRNG::new(pinned)).unwrap(); + streamed.do_encrypt_inplace(&mut expected).unwrap(); + let mut buf = *DUMMY_SEED; + let (n, iv) = key.encrypt_inplace(&mut FixedSeedRNG::new(pinned), &mut buf).unwrap(); + assert_eq!(n, buf.len(), "encrypt_inplace must report the number of bytes written"); + assert_eq!(iv, iv_streamed); + assert_eq!(&buf[..], &expected[..]); + let mut buf2 = *DUMMY_SEED; + let (_, iv_again) = key.encrypt_inplace(&mut FixedSeedRNG::new(pinned), &mut buf2).unwrap(); + assert_eq!(iv, iv_again); + assert_eq!(&buf[..], &buf2[..]); + + // the init data is random: not the same on two runs, and different init data under the + // same key gives different ciphertext + let (_encryptor, iv1) = key.create_encryptor(rng).unwrap(); + let (_encryptor, iv2) = key.create_encryptor(rng).unwrap(); + assert_ne!(iv1, iv2); + let mut a = *DUMMY_SEED; + let mut b = *DUMMY_SEED; + let (_, iv_a) = key.encrypt_inplace(rng, &mut a).unwrap(); + let (_, iv_b) = key.encrypt_inplace(rng, &mut b).unwrap(); + assert_ne!(iv_a, iv_b); + assert_ne!(&a[..], &b[..]); } } diff --git a/crypto/core/src/hazmat/ecb_key.rs b/crypto/core/src/hazmat/ecb_key.rs new file mode 100644 index 000000000..060f40a0d --- /dev/null +++ b/crypto/core/src/hazmat/ecb_key.rs @@ -0,0 +1,59 @@ +//! The ECB role pair: what a key bound to ECB can produce. Here rather than in +//! [`crate::symmetric_key`] because ECB is not a confidentiality mode for data, so a key for it +//! is reached through `hazmat` as the mode itself is. + +use crate::errors::SymmetricCipherError; +use crate::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; + +/// A key that can begin an ECB encryption: the permutation applied block by block, with no init +/// data and nothing to draw, so construction takes nothing and cannot fail. +/// +/// # 🚨 Security Considerations 🚨 +/// Equal plaintext blocks give equal ciphertext blocks; see [`crate::hazmat`] for the supported +/// uses. +pub trait EcbEncryptionKey { + /// The streaming encryptor this key produces, borrowing the key for `'k`. + type Encryptor<'k>: BlockCipherEncryptor<0, BLOCK_LEN> + where + Self: 'k; + + /// Begins an encryption. + fn create_encryptor<'k>(&'k self) -> Self::Encryptor<'k>; + + /// One-shot: encrypts `data`, which must be a whole number of blocks, in place, and returns + /// the number of bytes written. + /// + /// # Errors + /// [`SymmetricCipherError::DataNotBlockAligned`] if `data.len()` is not a multiple of + /// `BLOCK_LEN`; ECB itself refuses nothing. + fn encrypt_inplace(&self, data: &mut [u8]) -> Result { + let (blocks, []) = data.as_chunks_mut::() else { + return Err(SymmetricCipherError::DataNotBlockAligned); + }; + self.create_encryptor().do_encrypt_blocks_inplace(blocks) + } +} + +/// A key that can begin an ECB decryption; see [`EcbEncryptionKey`]. +pub trait EcbDecryptionKey { + /// The streaming decryptor this key produces, borrowing the key for `'k`. + type Decryptor<'k>: BlockCipherDecryptor<0, BLOCK_LEN> + where + Self: 'k; + + /// Begins a decryption. + fn create_decryptor<'k>(&'k self) -> Self::Decryptor<'k>; + + /// One-shot: decrypts `data`, which must be a whole number of blocks, in place, and returns + /// the number of bytes written. + /// + /// # Errors + /// [`SymmetricCipherError::DataNotBlockAligned`] if `data.len()` is not a multiple of + /// `BLOCK_LEN`; ECB itself refuses nothing. + fn decrypt_inplace(&self, data: &mut [u8]) -> Result { + let (blocks, []) = data.as_chunks_mut::() else { + return Err(SymmetricCipherError::DataNotBlockAligned); + }; + self.create_decryptor().do_decrypt_blocks_inplace(blocks) + } +} diff --git a/crypto/core/src/hazmat/electronic_code_book.rs b/crypto/core/src/hazmat/electronic_code_book.rs index 0bcea7b64..e64384772 100644 --- a/crypto/core/src/hazmat/electronic_code_book.rs +++ b/crypto/core/src/hazmat/electronic_code_book.rs @@ -1,4 +1,5 @@ -//! The [`ElectronicCodeBook`] trait: a keyed block permutation. +//! The [`BlockPermutation`] trait, a keyed block permutation, and [`ElectronicCodeBook`], which +//! adds its construction from key material. use crate::errors::SymmetricCipherError; use crate::key_material::KeyMaterial; @@ -9,7 +10,9 @@ use crate::traits::Algorithm; use crate::key_material::KeyType; // end of imports needed for docs -/// A keyed block permutation: the `CIPH_K` / `CIPH^-1_K` of NIST SP 800-38A Sec 5.1. +/// A keyed block permutation: the `CIPH_K` / `CIPH^-1_K` of NIST SP 800-38A Sec 5.1, already +/// keyed. A key object builds it through [`ElectronicCodeBook::new`]; the modes in +/// `bouncycastle_cipher::modes` bound on this trait alone. /// /// # 🚨 Security Considerations 🚨 /// A permutation applied to data block by block is ECB: equal plaintext blocks give equal @@ -23,19 +26,9 @@ use crate::key_material::KeyType; /// # Why the block methods are infallible /// /// 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 -{ - /// Expands the key. - /// - /// # Errors - /// 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 new(key: &KeyMaterial) -> Result; - +/// is nothing a caller can get wrong once [`ElectronicCodeBook::new`] has returned; that +/// constructor is the one fallible step, and only because of the key. +pub trait BlockPermutation: Algorithm + Sized { /// The forward cipher function, in place. fn encrypt_block(&self, block: &mut [u8; BLOCK_LEN]); @@ -47,11 +40,11 @@ pub trait ElectronicCodeBook: /// Required, with no default, so that every implementor decides for itself how to run a pair. /// A bit-sliced engine whose natural unit is a pair (see `bouncycastle-aes`) runs both blocks /// in one pass for barely more than the cost of one; an engine with no unit wider than a block - /// makes two [`ElectronicCodeBook::encrypt_block`] calls. A default of two single-block calls + /// makes two [`BlockPermutation::encrypt_block`] calls. A default of two single-block calls /// would be right only for the second kind, and silently wrong -- twice the work, with nothing /// failing -- for a wider engine that forgot to override it. /// - /// Must be indistinguishable from two [`ElectronicCodeBook::encrypt_block`] calls, including + /// Must be indistinguishable from two [`BlockPermutation::encrypt_block`] calls, including /// the order of the two results. `TestFrameworkElectronicCodeBook` pins that. /// /// Modes whose structure is parallel -- CBC decryption, CFB decryption, CTR -- should prefer @@ -60,18 +53,18 @@ pub trait ElectronicCodeBook: fn encrypt_2blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]); /// The inverse cipher function on two *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_2blocks`]. + /// See [`BlockPermutation::encrypt_2blocks`]. fn decrypt_2blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]); /// The forward cipher function on four *independent* blocks, in place. /// - /// Required for the same reason as [`ElectronicCodeBook::encrypt_2blocks`]. An engine whose + /// Required for the same reason as [`BlockPermutation::encrypt_2blocks`]. An engine whose /// natural unit is a pair runs the four as two pair calls; a bit-sliced engine whose S-box /// circuit substitutes four blocks per pass runs them as one full pass rather than two /// half-empty pair calls. Four is the unit because it is the widest any engine in this library /// fills: AES fills a pair, and the `u16`- and `u32`-plane engines (SM4, Camellia, ARIA) fill /// four. - /// Must be indistinguishable from four [`ElectronicCodeBook::encrypt_block`] calls, including + /// Must be indistinguishable from four [`BlockPermutation::encrypt_block`] calls, including /// the order of the four results. `TestFrameworkElectronicCodeBook` pins that. /// /// Modes with parallel structure chunk their data into fours first, then pairs, then single @@ -79,6 +72,20 @@ pub trait ElectronicCodeBook: fn encrypt_4blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 4]); /// The inverse cipher function on four *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_4blocks`]. + /// See [`BlockPermutation::encrypt_4blocks`]. fn decrypt_4blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 4]); } + +/// A [`BlockPermutation`] that can be built from `KEY_LEN` bytes of key material: what a key +/// object does once, when it is made. +pub trait ElectronicCodeBook: + BlockPermutation +{ + /// Expands the key. + /// + /// # Errors + /// 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 new(key: &KeyMaterial) -> Result; +} diff --git a/crypto/core/src/hazmat/key_stream.rs b/crypto/core/src/hazmat/key_stream.rs index 3c4e5f448..a3b06927e 100644 --- a/crypto/core/src/hazmat/key_stream.rs +++ b/crypto/core/src/hazmat/key_stream.rs @@ -1,8 +1,6 @@ //! A keyed keystream generator: the raw primitive under a stream cipher, as //! [`ElectronicCodeBook`] is the raw primitive under a block cipher mode. -use crate::errors::SymmetricCipherError; -use crate::key_material::KeyMaterial; use crate::traits::AlgorithmInfo; // Imports needed for docs @@ -17,41 +15,27 @@ use crate::traits::{BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEnc /// A keyed keystream generator: the raw primitive under a stream cipher, as /// [`ElectronicCodeBook`] is the raw primitive under a block cipher mode. /// -/// It is constructed from a key and init data and XORs successive keystream blocks into whatever -/// it is handed. It has no direction and no init-data policy: generating the nonce, buffering a -/// partly-used block between calls, and refusing a call that would run past the end of the -/// keystream all belong to `bouncycastle_cipher::stream::StreamCipher`, which turns any -/// `KeyStream` into a [`StreamCipherEncryptor`] / [`StreamCipherDecryptor`] pair. +/// It is positioned by its init data over an already-keyed permutation it borrows, and XORs +/// successive keystream blocks into whatever it is handed. It has no direction and no init-data +/// policy: generating the nonce, buffering a partly-used block between calls, and refusing a call +/// that would run past the end of the keystream all belong to +/// `bouncycastle_cipher::stream::StreamCipher`, which turns any `KeyStream` into a +/// [`StreamCipherEncryptor`] / [`StreamCipherDecryptor`] pair. /// /// Only a keystream that is independent of the data fits: CTR does, CFB does not, since its next /// keystream block is the encryption of the last ciphertext block. /// /// # 🚨 Security Considerations 🚨 -/// [`KeyStream::new`] takes the init data from the caller, so nothing stops a caller reusing a -/// nonce under a key -- which repeats the keystream and reveals the XOR of the two plaintexts -- -/// and nothing stops it running past [`KeyStream::remaining_blocks`]. `StreamCipher` generates -/// the init data and enforces the limit; use it. See the [module docs](crate::hazmat) for the -/// supported uses of the raw trait. +/// A keystream is started at init data the caller chose, so nothing here stops a caller reusing +/// a nonce under a key -- which repeats the keystream and reveals the XOR of the two plaintexts +/// -- and nothing stops it running past [`KeyStream::remaining_blocks`]. A key object's +/// `create_encryptor` draws the init data and `StreamCipher` enforces the limit; use those. See +/// the [module docs](crate::hazmat) for the supported uses of the raw trait. /// -/// 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: - AlgorithmInfo -{ - /// Expands the key and positions the keystream at its first block for `init_data`. - /// - /// # Errors - /// Rejects a key whose [`KeyType`] is not [`KeyType::SymmetricCipherKey`], and one whose - /// security strength is below [`Algorithm::MAX_SECURITY_STRENGTH`](crate::traits::Algorithm::MAX_SECURITY_STRENGTH), both as a - /// [`SymmetricCipherError::KeyMaterialError`]. - fn new( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result - where - Self: Sized; - +/// Implementors borrow the key, which keeps its schedule 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: AlgorithmInfo { /// How many more keystream blocks this value can produce before its keystream would repeat. /// A keystream with no practical limit returns `u64::MAX`. fn remaining_blocks(&self) -> u64; diff --git a/crypto/core/src/hazmat/mod.rs b/crypto/core/src/hazmat/mod.rs index 4d254ca6e..ea55088bf 100644 --- a/crypto/core/src/hazmat/mod.rs +++ b/crypto/core/src/hazmat/mod.rs @@ -11,14 +11,18 @@ //! grep -rnE --include='*.rs' 'use .*::hazmat::' //! ``` +mod ecb_key; mod electronic_code_book; mod hazardous_operations; mod key_stream; mod mode; -pub use electronic_code_book::ElectronicCodeBook; +pub use ecb_key::{EcbDecryptionKey, EcbEncryptionKey}; +pub use electronic_code_book::{BlockPermutation, ElectronicCodeBook}; pub use hazardous_operations::do_hazardous_operations; pub use key_stream::KeyStream; pub use mode::{ - AeadDecryptMode, AeadEncryptMode, AeadPacketDecryptMode, AeadPacketEncryptMode, MacMode, Mode, + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, AeadDecryptMode, AeadEncryptMode, + BlockDecryptMode, BlockEncryptMode, EcbDecryptMode, EcbEncryptMode, MacMode, Mode, ModeKey, + NonceMacMode, SymmetricDecryptMode, SymmetricEncryptMode, }; diff --git a/crypto/core/src/hazmat/mode.rs b/crypto/core/src/hazmat/mode.rs index da4ded5f3..2d91084ef 100644 --- a/crypto/core/src/hazmat/mode.rs +++ b/crypto/core/src/hazmat/mode.rs @@ -6,15 +6,29 @@ //! There are two shapes of constructor. [`AeadEncryptMode`] / [`AeadDecryptMode`] are handed a //! nonce and nothing else, which is every mode that can take its message as it comes. A mode that //! must commit to the lengths before its first byte -- CCM, whose `B0` carries the payload length -//! and whose AAD is length-prefixed -- implements [`AeadPacketEncryptMode`] / -//! [`AeadPacketDecryptMode`] instead, whose constructors take the AAD and payload lengths and -//! whose workers then need no buffer for either. A MAC mode -- CMAC -- implements [`MacMode`], -//! whose worker takes no nonce and serves both roles. +//! and whose AAD is length-prefixed -- implements [`AeadDeclaredEncryptMode`] / +//! [`AeadDeclaredDecryptMode`] instead, whose constructors take the AAD and payload lengths and +//! whose workers then need no buffer for either. The unauthenticated modes have the same two +//! shapes with no tag: [`SymmetricEncryptMode`] / [`SymmetricDecryptMode`] for a mode that takes +//! data of any length (padded CBC, CFB, CFB8, CTR), [`BlockEncryptMode`] / [`BlockDecryptMode`] +//! for one that takes whole blocks (CBC with no padding layer), and [`EcbEncryptMode`] / +//! [`EcbDecryptMode`] for ECB, which has no init data and so takes no RNG. A MAC mode -- CMAC -- +//! implements [`MacMode`], whose worker takes no nonce and serves both roles; a nonce-based MAC +//! -- GMAC -- implements [`NonceMacMode`], whose generator and verifier are made as an AEAD +//! mode's workers are. +//! +//! Every worker borrows the key state it is made from, for the lifetime `'k` on the mode's +//! associated worker type: the schedule stays in the key and is read through the reference, so +//! making a worker copies nothing but the per-message state, and a worker cannot outlive its +//! key. Resuming a suspended worker takes the same reference, which a key object exposes through +//! [`ModeKey::state`]. use crate::errors::SymmetricCipherError; use crate::key_material::KeyType; -use crate::symmetric_key::{AeadDecryptWorker, AeadEncryptWorker}; -use crate::traits::{MAC, RNG}; +use crate::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, MAC, RNG, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; /// A mode's per-key state over the permutation `P`. /// @@ -24,7 +38,8 @@ use crate::traits::{MAC, RNG}; pub trait Mode

{ /// What the mode keeps per key: the permutation plus anything derived from it once. A mode /// with nothing to derive sets `type KeyState = P`. Sensitive, and held so that it is zeroized - /// on drop as the schedule it contains is. + /// on drop as the schedule it contains is. A state that implements `PartialEq`, which gives the + /// key object its equality, must compare in constant time for the same reason. type KeyState: Clone; /// The [`KeyType`] a key for this mode must carry: [`KeyType::SymmetricCipherKey`] for a @@ -35,9 +50,20 @@ pub trait Mode

{ fn key_state(perm: P) -> Self::KeyState; } -// `KEY_LEN` is on these traits only while the cipher traits still carry it: the permutation bound -// a mode impl needs, `P: ElectronicCodeBook`, must be tied to the impl's trait. It -// goes when the constructors leave the cipher traits. +/// A key object's mode state, borrowed: what a suspended worker is resumed over +/// (`SuspendableKeyed::Key` on a cipher mode's worker is the mode's `KeyState`). Hazmat because +/// the state is the bare keyed permutation, which the mode traits take directly; the safe route to +/// a worker is the key's `create_*` method. +pub trait ModeKey> { + /// The mode's per-key state this key holds. + fn state(&self) -> &M::KeyState; +} + +// `KEY_LEN` is on these traits as the tie between a key's length and its permutation's: a mode +// impl bounds `P: ElectronicCodeBook`, which only the permutation's own key length +// satisfies, so a key object's role impls, which take `KEY_LEN` from here, exist for that one +// length. The workers themselves never see it; `Mode

` and `MacMode

` bound on +// `BlockPermutation` alone. /// The encryption side of an AEAD mode over `P`: the mode's own constructor for its encryptor. pub trait AeadEncryptMode< @@ -48,23 +74,28 @@ pub trait AeadEncryptMode< const FINAL_LEN: usize, >: Mode

{ - /// The streaming encryptor this mode produces over `P`. - type Encryptor: AeadEncryptWorker; + /// The streaming encryptor this mode produces over `P`, borrowing the key state for `'k`. + type Encryptor<'k>: AEADCipherEncryptor + where + P: 'k; /// Begins an encryption from `state` under a nonce drawn from `rng`. /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - fn encrypt_init( - state: &Self::KeyState, + fn encrypt_init<'k>( + state: &'k Self::KeyState, rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError>; + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError>; /// Begins an encryption from `state` under a nonce the caller supplies. Infallible, as /// [`AeadDecryptMode::decrypt_init`] is: the state is keyed and the nonce length is in the /// type. The caller takes on the obligation [`encrypt_init`](Self::encrypt_init) discharges /// for it: the nonce must never repeat under this key. - fn encrypt_init_nonce(state: &Self::KeyState, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor; + fn encrypt_init_nonce<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + ) -> Self::Encryptor<'k>; /// Encrypts `buf` in place from `state` under `aad` and a nonce drawn from `rng`, returning /// the nonce and the detached tag. Provided by streaming `buf` through @@ -108,12 +139,14 @@ pub trait AeadDecryptMode< const FINAL_LEN: usize, >: Mode

{ - /// The streaming decryptor this mode produces over `P`. - type Decryptor: AeadDecryptWorker; + /// The streaming decryptor this mode produces over `P`, borrowing the key state for `'k`. + type Decryptor<'k>: AEADCipherDecryptor + where + P: 'k; /// Begins the decryption of a message encrypted under `nonce` from `state`. Infallible: the /// state is already keyed and the nonce length is in the type. - fn decrypt_init(state: &Self::KeyState, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor; + fn decrypt_init<'k>(state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k>; /// Decrypts `buf` in place from `state` under `nonce` and `aad`, and checks `tag`. On any /// error `buf` is left zeroized, since the plaintext was written into it before the tag could @@ -137,7 +170,7 @@ pub trait AeadDecryptMode< /// The encryption side of an AEAD mode over `P` whose worker must know the AAD and payload /// lengths before its first byte. The constructors take both; the worker then accepts exactly /// that much of each, in any chunking, and holds nothing back. -pub trait AeadPacketEncryptMode< +pub trait AeadDeclaredEncryptMode< P, const KEY_LEN: usize, const NONCE_LEN: usize, @@ -145,8 +178,10 @@ pub trait AeadPacketEncryptMode< const FINAL_LEN: usize, >: Mode

{ - /// The streaming encryptor this mode produces over `P`. - type Encryptor: AeadEncryptWorker; + /// The streaming encryptor this mode produces over `P`, borrowing the key state for `'k`. + type Encryptor<'k>: AEADCipherEncryptor + where + P: 'k; /// Begins an encryption of `payload_len` bytes under `aad_len` bytes of AAD, from `state` /// under a nonce drawn from `rng`. @@ -154,24 +189,24 @@ pub trait AeadPacketEncryptMode< /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails, or the mode's own refusal of /// a length it cannot encode. - fn encrypt_init( - state: &Self::KeyState, + fn encrypt_init<'k>( + state: &'k Self::KeyState, rng: &mut dyn RNG, aad_len: usize, payload_len: usize, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError>; + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError>; /// As [`encrypt_init`](Self::encrypt_init) under a nonce the caller supplies, who takes on /// the obligation that it never repeat under this key. /// /// # Errors /// The mode's own refusal of a length it cannot encode. - fn encrypt_init_nonce( - state: &Self::KeyState, + fn encrypt_init_nonce<'k>( + state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result; + ) -> Result, SymmetricCipherError>; /// Encrypts `buf` in place from `state` under `aad` and a nonce drawn from `rng`, returning /// the nonce and the detached tag. Provided by streaming `buf` through the encryptor a chunk @@ -208,8 +243,8 @@ pub trait AeadPacketEncryptMode< } /// The decryption side of an AEAD mode over `P` whose worker must know the AAD and payload -/// lengths before its first byte; see [`AeadPacketEncryptMode`]. -pub trait AeadPacketDecryptMode< +/// lengths before its first byte; see [`AeadDeclaredEncryptMode`]. +pub trait AeadDeclaredDecryptMode< P, const KEY_LEN: usize, const NONCE_LEN: usize, @@ -217,20 +252,22 @@ pub trait AeadPacketDecryptMode< const FINAL_LEN: usize, >: Mode

{ - /// The streaming decryptor this mode produces over `P`. - type Decryptor: AeadDecryptWorker; + /// The streaming decryptor this mode produces over `P`, borrowing the key state for `'k`. + type Decryptor<'k>: AEADCipherDecryptor + where + P: 'k; /// Begins the decryption of `payload_len` bytes of ciphertext under `aad_len` bytes of AAD, /// encrypted under `nonce`, from `state`. /// /// # Errors /// The mode's own refusal of a length it cannot encode. - fn decrypt_init( - state: &Self::KeyState, + fn decrypt_init<'k>( + state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result; + ) -> Result, SymmetricCipherError>; /// Decrypts `buf` in place from `state` under `nonce` and `aad`, and checks `tag`. On any /// error after construction `buf` is left zeroized, since the plaintext was written into it @@ -251,14 +288,169 @@ pub trait AeadPacketDecryptMode< } } +/// The encryption side of an unauthenticated mode over `P` that takes data of any length: the +/// mode's own constructor for its [`SymmetricCipherEncryptor`]. +pub trait SymmetricEncryptMode< + P, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// The streaming encryptor this mode produces over `P`, borrowing the key state for `'k`. + type Encryptor<'k>: SymmetricCipherEncryptor + where + P: 'k; + + /// Begins an encryption from `state` under init data drawn from `rng`. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the draw fails; nothing else can. + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError>; +} + +/// The decryption side of an unauthenticated mode over `P` that takes data of any length. +pub trait SymmetricDecryptMode< + P, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// The streaming decryptor this mode produces over `P`, borrowing the key state for `'k`. + type Decryptor<'k>: SymmetricCipherDecryptor + where + P: 'k; + + /// Begins the decryption of a message encrypted under `init_data` from `state`. Infallible: + /// the state is already keyed and the init data length is in the type. + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; INIT_DATA_LEN], + ) -> Self::Decryptor<'k>; +} + +/// The encryption side of a whole-block mode over `P`: the mode's own constructor for its +/// [`BlockCipherEncryptor`]. +pub trait BlockEncryptMode< + P, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, +>: Mode

+{ + /// The streaming encryptor this mode produces over `P`, borrowing the key state for `'k`. + type Encryptor<'k>: BlockCipherEncryptor + where + P: 'k; + + /// Begins an encryption from `state` under init data drawn from `rng`. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the draw fails; nothing else can. + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError>; +} + +/// The decryption side of a whole-block mode over `P`. +pub trait BlockDecryptMode< + P, + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, +>: Mode

+{ + /// The streaming decryptor this mode produces over `P`, borrowing the key state for `'k`. + type Decryptor<'k>: BlockCipherDecryptor + where + P: 'k; + + /// Begins the decryption of a message encrypted under `init_data` from `state`. Infallible. + fn decrypt_init<'k>( + state: &'k Self::KeyState, + init_data: &[u8; INIT_DATA_LEN], + ) -> Self::Decryptor<'k>; +} + +/// The encryption side of ECB over `P`: a whole-block mode with no init data, so its constructor +/// takes no RNG and cannot fail. Its own pair rather than [`BlockEncryptMode`] with +/// `INIT_DATA_LEN = 0`, so that nothing is handed an RNG it has no use for. +pub trait EcbEncryptMode: Mode

{ + /// The streaming encryptor this mode produces over `P`, borrowing the key state for `'k`. + type Encryptor<'k>: BlockCipherEncryptor<0, BLOCK_LEN> + where + P: 'k; + + /// Begins an encryption from `state`. + fn encrypt_init<'k>(state: &'k Self::KeyState) -> Self::Encryptor<'k>; +} + +/// The decryption side of ECB over `P`; see [`EcbEncryptMode`]. +pub trait EcbDecryptMode: Mode

{ + /// The streaming decryptor this mode produces over `P`, borrowing the key state for `'k`. + type Decryptor<'k>: BlockCipherDecryptor<0, BLOCK_LEN> + where + P: 'k; + + /// Begins a decryption from `state`. + fn decrypt_init<'k>(state: &'k Self::KeyState) -> Self::Decryptor<'k>; +} + /// A MAC mode over `P`: the mode's own constructor for its worker, which generates and verifies /// alike. The role traits [`crate::symmetric_key::MacGenerationKey`] and /// [`crate::symmetric_key::MacVerificationKey`] forward to it. pub trait MacMode

: Mode

{ - /// The streaming MAC worker this mode produces over `P`. - type Worker: MAC; + /// The streaming MAC worker this mode produces over `P`. It borrows the key state it was made + /// from for `'k`: the schedule is read through the reference, never copied into the worker, + /// and the worker cannot outlive the state. + type Worker<'k>: MAC + where + P: 'k; /// Begins a MAC over a new message from `state`. Infallible: the state is keyed and there is /// nothing to draw. - fn mac_init(state: &Self::KeyState) -> Self::Worker; + fn mac_init<'k>(state: &'k Self::KeyState) -> Self::Worker<'k>; +} + +/// A nonce-based MAC mode over `P` -- GMAC -- whose tag is bound to a nonce, so its two sides are +/// made as an AEAD mode's workers are: the generator under a nonce drawn from an RNG or supplied +/// by the caller, the verifier under the nonce the tag was made under. There is no role trait +/// for it in [`crate::symmetric_key`], GMAC being the only such MAC in the tree; a key type's +/// inherent generator/verifier pair forwards here. +pub trait NonceMacMode: Mode

{ + /// The streaming generator this mode produces over `P`, borrowing the key state for `'k`. + type Generator<'k> + where + P: 'k; + + /// The streaming verifier this mode produces over `P`, borrowing the key state for `'k`. + type Verifier<'k> + where + P: 'k; + + /// Begins a tag from `state` under a nonce drawn from `rng`, which is returned with the + /// generator. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. + fn generate_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> Result<(Self::Generator<'k>, [u8; NONCE_LEN]), SymmetricCipherError>; + + /// Begins a tag from `state` under a nonce the caller supplies, who takes on the obligation + /// that it never repeat under this key. + fn generate_init_nonce<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + ) -> Self::Generator<'k>; + + /// Begins the verification of a tag made under `nonce` from `state`. Infallible: the state + /// is keyed and the nonce length is in the type. + fn verify_init<'k>(state: &'k Self::KeyState, nonce: &[u8; NONCE_LEN]) -> Self::Verifier<'k>; } diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 7368ab2e6..ea41754e3 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -316,7 +316,12 @@ impl KeyMaterial { *key.key_len = KEY_LEN; key.key_type = KeyType::CryptographicRandom; - key.security_strength = rng.security_strength(); + // The RNG's strength, capped at what `KEY_LEN` bytes can carry: a 16-byte key drawn from + // a 256-bit DRBG is a 128-bit key, and `set_security_strength` would refuse the higher + // figure. + let by_len = SecurityStrength::from_bits(KEY_LEN * 8); + let by_rng = rng.security_strength(); + key.security_strength = if by_rng < by_len { by_rng } else { by_len }; Ok(key) } diff --git a/crypto/core/src/provided.rs b/crypto/core/src/provided.rs index dfb554631..bc4022e7e 100644 --- a/crypto/core/src/provided.rs +++ b/crypto/core/src/provided.rs @@ -1,5 +1,5 @@ //! The provided bodies of the role keys' and mode traits' one-shots, over a worker that has -//! already been constructed, so that the drawing, supplied-nonce and packet forms share them. +//! already been constructed, so that the drawing, supplied-nonce and declared-length forms share them. //! //! The `_out` bodies stream the message from one slice to another. The in-place bodies run a //! message in its own buffer through the same split-slice update methods: each chunk is copied out @@ -12,7 +12,9 @@ //! and no second buffer the size of the message. use crate::errors::SymmetricCipherError; -use crate::symmetric_key::{AeadDecryptWorker, AeadEncryptWorker}; +use crate::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; /// Bytes copied out per step. Four 16-byte blocks keeps a block-cipher mode on its wide path. const CHUNK: usize = 64; @@ -36,7 +38,7 @@ pub(crate) fn encrypt_inline< ciphertext: &mut [u8], ) -> Result where - E: AeadEncryptWorker, + E: AEADCipherEncryptor, { ciphertext.fill(0); let needed = E::encrypt_out_len(plaintext.len()); @@ -68,7 +70,7 @@ pub(crate) fn encrypt_detached< ciphertext: &mut [u8], ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> where - E: AeadEncryptWorker, + E: AEADCipherEncryptor, { ciphertext.fill(0); let needed = E::encrypt_detached_out_len(plaintext.len()); @@ -100,7 +102,7 @@ pub(crate) fn encrypt_detached_in_place< buf: &mut [u8], ) -> Result<[u8; TAG_LEN], SymmetricCipherError> where - E: AeadEncryptWorker, + E: AEADCipherEncryptor, { enc.do_update_aad(aad)?; let mut tmp = [0u8; CHUNK]; @@ -138,7 +140,7 @@ pub(crate) fn decrypt_inline< plaintext: &mut [u8], ) -> Result where - D: AeadDecryptWorker, + D: AEADCipherDecryptor, { plaintext.fill(0); let needed = D::decrypt_out_len(ciphertext.len()); @@ -180,7 +182,7 @@ pub(crate) fn decrypt_detached< plaintext: &mut [u8], ) -> Result where - D: AeadDecryptWorker, + D: AEADCipherDecryptor, { plaintext.fill(0); let needed = D::decrypt_detached_out_len(ciphertext.len()); @@ -220,7 +222,7 @@ pub(crate) fn decrypt_detached_in_place< tag: &[u8; TAG_LEN], ) -> Result<(), SymmetricCipherError> where - D: AeadDecryptWorker, + D: AEADCipherDecryptor, { let result = (|| { dec.do_update_aad(aad)?; @@ -254,3 +256,67 @@ pub(crate) fn inline_payload_len( ) -> Result { ciphertext_len.checked_sub(TAG_LEN).ok_or(SymmetricCipherError::DecryptionFailed) } + +/// Encrypts `plaintext` through `enc` into `ciphertext`, and returns the number of bytes written. +/// `ciphertext` is zeroized first and must hold `encrypt_out_len(plaintext.len())` bytes. The +/// unauthenticated counterpart of [`encrypt_inline`], for the symmetric role keys. +/// +/// # Errors +/// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, or any error of +/// the worker's streaming methods. +pub(crate) fn symmetric_encrypt_out( + mut enc: E, + plaintext: &[u8], + ciphertext: &mut [u8], +) -> Result +where + E: SymmetricCipherEncryptor, +{ + ciphertext.fill(0); + let needed = E::encrypt_out_len(plaintext.len()); + if ciphertext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + let written = enc.do_encrypt_out(plaintext, ciphertext)?; + let (last, last_len) = enc.do_encrypt_final()?; + // `encrypt_out_len` is exactly `written + last_len`, so this fits in `ciphertext[..needed]`. + ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); + Ok(written + last_len) +} + +/// Decrypts `ciphertext` through `dec` into `plaintext`, and returns the number of bytes +/// written. `plaintext` is zeroized first, must hold `decrypt_out_len(ciphertext.len())` bytes, +/// and is zeroized again before an error is returned. The unauthenticated counterpart of +/// [`decrypt_inline`]. +/// +/// # Errors +/// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, or any error of +/// the worker's streaming methods. +pub(crate) fn symmetric_decrypt_out( + mut dec: D, + ciphertext: &[u8], + plaintext: &mut [u8], +) -> Result +where + D: SymmetricCipherDecryptor, +{ + plaintext.fill(0); + let needed = D::decrypt_out_len(ciphertext.len()); + if plaintext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + let written = dec.do_decrypt_out(ciphertext, plaintext)?; + match dec.do_decrypt_final() { + Ok((last, data_len)) => { + // `decrypt_out_len` bounds `written + data_len`, so this fits in `plaintext[..needed]`. + plaintext[written..written + data_len].copy_from_slice(&last[..data_len]); + Ok(written + data_len) + } + Err(e) => { + // A padding failure says the stream was not what it claimed to be; this one-shot owns + // the whole message, so it does not leave the decrypted prefix in the caller's hands. + plaintext[..written].fill(0); + Err(e) + } + } +} diff --git a/crypto/core/src/symmetric_key.rs b/crypto/core/src/symmetric_key.rs index b97c74b3d..401b5b319 100644 --- a/crypto/core/src/symmetric_key.rs +++ b/crypto/core/src/symmetric_key.rs @@ -4,7 +4,8 @@ //! `HmacKey` -- and is validated once, when it is made. It holds whatever the construction //! keeps per key (an expanded schedule, HMAC's two pad states) and hands out one worker per //! message: an encryptor, a decryptor, a MAC generator or a MAC verifier. The worker traits in -//! [`crate::traits`] then describe only an object that already exists. +//! [`crate::traits`] then describe only an object that already exists. A worker borrows the key +//! it came from: the schedule is never copied into it, and it cannot outlive the key. //! //! Two kinds of trait live here: //! @@ -12,15 +13,20 @@ //! `KEY_LEN` bytes, [`FromAnyKeyMaterial`] for a construction that accepts any byte string. //! A key that lives in a hardware token implements neither; it has its own way in. //! * **Roles**, one trait per direction: [`AeadEncryptionKey`] / [`AeadDecryptionKey`], their -//! packet forms [`AeadPacketEncryptionKey`] / [`AeadPacketDecryptionKey`] for a mode that must -//! know the lengths up front, and [`MacGenerationKey`] / [`MacVerificationKey`]. A key type +//! declared-length forms [`AeadDeclaredEncryptionKey`] / [`AeadDeclaredDecryptionKey`] for a mode that must +//! know the lengths up front, [`SymmetricEncryptionKey`] / [`SymmetricDecryptionKey`] for the +//! unauthenticated modes that take data of any length (padded CBC, CFB, CFB8, CTR), +//! [`BlockEncryptionKey`] / [`BlockDecryptionKey`] for the whole-block modes, the ECB pair in +//! [`crate::hazmat`], and [`MacGenerationKey`] / [`MacVerificationKey`]. A key type //! implements the sides it is allowed to use, so a decrypt-only or verify-only key refuses the //! other side at compile time. The one-shots are provided methods over `create_*` and the -//! worker's streaming methods, which [`AeadEncryptWorker`] / [`AeadDecryptWorker`] describe. +//! worker's streaming methods, the cipher traits in [`crate::traits`]. //! //! Which role a cipher key carries is decided by its mode type through the mode traits in -//! [`crate::hazmat`] ([`Mode`], [`AeadEncryptMode`], [`AeadDecryptMode`] and their packet -//! forms); the permutation crate connects the two with a forwarding impl per role. +//! [`crate::hazmat`] ([`Mode`], [`AeadEncryptMode`], [`AeadDecryptMode`] and their declared-length, +//! symmetric, block and ECB forms); the permutation crate connects the two with a forwarding +//! impl per role. A mode implements exactly one pair, so `key.create_encryptor(..)` on a key +//! bound to it is never ambiguous. //! //! ```text //! let key = AES128Key::::generate(&mut rng)?; @@ -42,7 +48,11 @@ use crate::errors::{MACError, SymmetricCipherError}; use crate::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; #[cfg(feature = "std")] use crate::traits::AEADEncryptedTuple; -use crate::traits::{MAC, RNG}; +use crate::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, MAC, RNG, + StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, +}; /// Construction of a key object from exactly `KEY_LEN` bytes of key material. /// @@ -105,112 +115,10 @@ pub trait FromAnyKeyMaterial: Sized { fn generate(rng: &mut dyn RNG) -> Result; } -// The lengths the cipher traits carry are const generics on the role traits rather than associated -// constants, because stable Rust can neither pass an associated constant as a const-generic -// argument (the worker bound) nor size an array from one (the returned nonce). `KEY_LEN` is on -// the role traits only while the cipher traits still carry it; it goes when their constructors do. - -/// An AEAD encryptor that already exists: the streaming methods of the cipher traits without -/// their constructors, which is all a key's worker needs. A worker produced by a key implements -/// this; the cipher traits' own types implement it by forwarding. -pub trait AeadEncryptWorker: - Sized -{ - /// Absorbs associated data, which is authenticated but not encrypted, before any payload. - /// - /// # Errors - /// [`SymmetricCipherError::StateError`] if non-empty after the payload has begun, or the - /// worker's own refusal of more than it declared. - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError>; - - /// The most ciphertext bytes the next `do_encrypt_out` of `input_len` bytes can release. - fn do_encrypt_out_len(&self, input_len: usize) -> usize; - - /// Encrypts `plaintext` into `ciphertext`, returning the bytes written. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is shorter than - /// [`do_encrypt_out_len`](Self::do_encrypt_out_len) says, or the worker's own refusals. - fn do_encrypt_out( - &mut self, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result; - - /// Finishes with the tag inline: whatever was held back, then the tag, and how many bytes - /// of the array are valid. - /// - /// # Errors - /// The worker's own refusal of a message it was not given all of. - fn do_encrypt_final(self) -> Result<([u8; FINAL_LEN], usize), SymmetricCipherError>; - - /// Finishes with the tag detached: whatever was held back goes into `ciphertext`, and the - /// bytes written come back with the tag. - /// - /// # Errors - /// As [`do_encrypt_final`](Self::do_encrypt_final). - fn do_encrypt_final_detached_out( - self, - ciphertext: &mut [u8; FINAL_LEN], - ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError>; - - /// The ciphertext length, tag inline, for `plaintext_len` bytes. - fn encrypt_out_len(plaintext_len: usize) -> usize; - - /// The ciphertext length, tag detached, for `plaintext_len` bytes. - fn encrypt_detached_out_len(plaintext_len: usize) -> usize; -} - -/// An AEAD decryptor that already exists; see [`AeadEncryptWorker`]. -pub trait AeadDecryptWorker: - Sized -{ - /// Absorbs associated data before any ciphertext. - /// - /// # Errors - /// As [`AeadEncryptWorker::do_update_aad`]. - fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError>; - - /// The most plaintext bytes the next `do_decrypt_out` of `input_len` bytes can release. - fn do_decrypt_out_len(&self, input_len: usize) -> usize; - - /// Decrypts `ciphertext` into `plaintext`, returning the bytes written. What is written is - /// not yet authenticated: only the final says whether it may be trusted. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is shorter than - /// [`do_decrypt_out_len`](Self::do_decrypt_out_len) says, or the worker's own refusals. - fn do_decrypt_out( - &mut self, - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result; - - /// Finishes with the tag taken from the end of the ciphertext: whatever was held back, and - /// how many bytes of the array are valid. - /// - /// # Errors - /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, and - /// [`SymmetricCipherError::DecryptionFailed`] for a malformed ciphertext. - fn do_decrypt_final(self) -> Result<([u8; FINAL_LEN], usize), SymmetricCipherError>; - - /// Finishes against a detached `tag`: whatever was held back goes into `plaintext`, and the - /// bytes written come back. - /// - /// # Errors - /// As [`do_decrypt_final`](Self::do_decrypt_final). - fn do_decrypt_final_detached_out( - self, - tag: &[u8; TAG_LEN], - plaintext: &mut [u8; FINAL_LEN], - ) -> Result; - - /// The plaintext length for `ciphertext_len` bytes with the tag inline. - fn decrypt_out_len(ciphertext_len: usize) -> usize; - - /// The plaintext length for `ciphertext_len` bytes with the tag detached. - fn decrypt_detached_out_len(ciphertext_len: usize) -> usize; -} +// The lengths are const generics on the role traits rather than associated constants, because +// stable Rust can neither pass an associated constant as a const-generic argument (the worker +// bound) nor size an array from one (the returned nonce). `KEY_LEN` is here, and not on the +// cipher traits, because it is the key's: the worker never sees the material. /// A key that can begin an authenticated encryption: the encryption half of an AEAD key. /// @@ -231,8 +139,10 @@ pub trait AeadEncryptionKey< const FINAL_LEN: usize, > { - /// The streaming encryptor this key produces. - type Encryptor: AeadEncryptWorker; + /// The streaming encryptor this key produces, borrowing the key for `'k`. + type Encryptor<'k>: AEADCipherEncryptor + where + Self: 'k; /// Begins an encryption under a nonce drawn from `rng`. The nonce is returned alongside the /// encryptor and must reach the recipient, who passes it to @@ -240,14 +150,14 @@ pub trait AeadEncryptionKey< /// /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. - fn create_encryptor( - &self, + fn create_encryptor<'k>( + &'k self, rng: &mut dyn RNG, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError>; + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError>; /// Begins an encryption under a nonce the caller supplies. Infallible, as /// [`AeadDecryptionKey::create_decryptor`] is. - fn create_encryptor_nonce(&self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor; + fn create_encryptor_nonce<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Encryptor<'k>; /// One-shot with the tag inline: encrypts `plaintext` under `aad` into `ciphertext`, which /// needs `encrypt_out_len(plaintext.len())` bytes of the encryptor type, and returns the number @@ -280,7 +190,13 @@ pub trait AeadEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as SymmetricCipherEncryptor< + NONCE_LEN, + FINAL_LEN, + >>::encrypt_out_len(plaintext.len()) + ]; let (written, nonce) = self.encrypt_with_aad_out(rng, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); Ok((nonce, ciphertext)) @@ -315,7 +231,14 @@ pub trait AeadEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result, SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_detached_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as AEADCipherEncryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::encrypt_detached_out_len(plaintext.len()) + ]; let (written, nonce, tag) = self.encrypt_detached_out(rng, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); @@ -372,7 +295,13 @@ pub trait AeadEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result, SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as SymmetricCipherEncryptor< + NONCE_LEN, + FINAL_LEN, + >>::encrypt_out_len(plaintext.len()) + ]; let written = self.encrypt_with_aad_nonce_out(nonce, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); Ok(ciphertext) @@ -410,7 +339,14 @@ pub trait AeadEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_detached_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as AEADCipherEncryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::encrypt_detached_out_len(plaintext.len()) + ]; let (written, tag) = self.encrypt_detached_nonce_out(nonce, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); @@ -443,17 +379,20 @@ pub trait AeadDecryptionKey< const FINAL_LEN: usize, > { - /// The streaming decryptor this key produces. - type Decryptor: AeadDecryptWorker; + /// The streaming decryptor this key produces, borrowing the key for `'k`. + type Decryptor<'k>: AEADCipherDecryptor + where + Self: 'k; /// Begins the decryption of a message whose encryptor returned `nonce`. - fn create_decryptor(&self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor; + fn create_decryptor<'k>(&'k self, nonce: &[u8; NONCE_LEN]) -> Self::Decryptor<'k>; /// One-shot with the tag inline: decrypts `ciphertext` under `nonce` and `aad` into /// `plaintext`, which needs `decrypt_out_len(ciphertext.len())` bytes of the decryptor type, /// and returns the number of plaintext bytes written. The entire output buffer is zeroized - /// before the plaintext is written, and again before an error is returned, so a caller who - /// ignores the `Result` is left with zeros rather than unauthenticated plaintext. + /// before anything is written, and the plaintext released before a failed tag check is + /// zeroized again, so a caller who ignores the `Result` is left with zeros rather than + /// unauthenticated plaintext. /// /// # Errors /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, @@ -480,7 +419,13 @@ pub trait AeadDecryptionKey< aad: &[u8], ciphertext: &[u8], ) -> Result, SymmetricCipherError> { - let mut plaintext = vec![0u8; Self::Decryptor::decrypt_out_len(ciphertext.len())]; + let mut plaintext = vec![ + 0u8; + as SymmetricCipherDecryptor< + NONCE_LEN, + FINAL_LEN, + >>::decrypt_out_len(ciphertext.len()) + ]; let written = self.decrypt_with_aad_out(nonce, aad, ciphertext, &mut plaintext)?; plaintext.truncate(written); Ok(plaintext) @@ -521,7 +466,14 @@ pub trait AeadDecryptionKey< ciphertext: &[u8], tag: &[u8; TAG_LEN], ) -> Result, SymmetricCipherError> { - let mut plaintext = vec![0u8; Self::Decryptor::decrypt_detached_out_len(ciphertext.len())]; + let mut plaintext = vec![ + 0u8; + as AEADCipherDecryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::decrypt_detached_out_len(ciphertext.len()) + ]; let written = self.decrypt_detached_out(nonce, aad, ciphertext, tag, &mut plaintext)?; plaintext.truncate(written); Ok(plaintext) @@ -549,15 +501,17 @@ pub trait AeadDecryptionKey< /// its first byte: CCM. The constructors take both, and the worker then needs no buffer for /// either and holds nothing back. The one-shots have the lengths in hand, so they are provided /// exactly as [`AeadEncryptionKey`]'s are and behave the same. -pub trait AeadPacketEncryptionKey< +pub trait AeadDeclaredEncryptionKey< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, > { - /// The streaming encryptor this key produces. - type Encryptor: AeadEncryptWorker; + /// The streaming encryptor this key produces, borrowing the key for `'k`. + type Encryptor<'k>: AEADCipherEncryptor + where + Self: 'k; /// Begins an encryption of `payload_len` bytes under `aad_len` bytes of AAD and a nonce /// drawn from `rng`, which is returned. The worker then takes exactly those amounts. @@ -565,24 +519,24 @@ pub trait AeadPacketEncryptionKey< /// # Errors /// [`SymmetricCipherError::RNGError`] if the nonce draw fails, or the mode's refusal of a /// length it cannot encode. - fn create_encryptor( - &self, + fn create_encryptor<'k>( + &'k self, rng: &mut dyn RNG, aad_len: usize, payload_len: usize, - ) -> Result<(Self::Encryptor, [u8; NONCE_LEN]), SymmetricCipherError>; + ) -> Result<(Self::Encryptor<'k>, [u8; NONCE_LEN]), SymmetricCipherError>; /// As [`create_encryptor`](Self::create_encryptor) under a nonce the caller supplies, who /// takes on the obligation that it never repeat under this key. /// /// # Errors /// The mode's refusal of a length it cannot encode. - fn create_encryptor_nonce( - &self, + fn create_encryptor_nonce<'k>( + &'k self, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result; + ) -> Result, SymmetricCipherError>; /// As [`AeadEncryptionKey::encrypt_with_aad_out`]. /// @@ -612,7 +566,13 @@ pub trait AeadPacketEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as SymmetricCipherEncryptor< + NONCE_LEN, + FINAL_LEN, + >>::encrypt_out_len(plaintext.len()) + ]; let (written, nonce) = self.encrypt_with_aad_out(rng, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); Ok((nonce, ciphertext)) @@ -645,7 +605,14 @@ pub trait AeadPacketEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result, SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_detached_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as AEADCipherEncryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::encrypt_detached_out_len(plaintext.len()) + ]; let (written, nonce, tag) = self.encrypt_detached_out(rng, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); @@ -695,7 +662,13 @@ pub trait AeadPacketEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result, SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as SymmetricCipherEncryptor< + NONCE_LEN, + FINAL_LEN, + >>::encrypt_out_len(plaintext.len()) + ]; let written = self.encrypt_with_aad_nonce_out(nonce, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); Ok(ciphertext) @@ -727,7 +700,14 @@ pub trait AeadPacketEncryptionKey< aad: &[u8], plaintext: &[u8], ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError> { - let mut ciphertext = vec![0u8; Self::Encryptor::encrypt_detached_out_len(plaintext.len())]; + let mut ciphertext = vec![ + 0u8; + as AEADCipherEncryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::encrypt_detached_out_len(plaintext.len()) + ]; let (written, tag) = self.encrypt_detached_nonce_out(nonce, aad, plaintext, &mut ciphertext)?; ciphertext.truncate(written); @@ -751,29 +731,31 @@ pub trait AeadPacketEncryptionKey< } /// The decryption half of an AEAD key whose mode must know the lengths up front; see -/// [`AeadPacketEncryptionKey`]. For the inline layout the payload length is the ciphertext less +/// [`AeadDeclaredEncryptionKey`]. For the inline layout the payload length is the ciphertext less /// its tag, and a ciphertext too short for a tag is malformed. -pub trait AeadPacketDecryptionKey< +pub trait AeadDeclaredDecryptionKey< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, const FINAL_LEN: usize, > { - /// The streaming decryptor this key produces. - type Decryptor: AeadDecryptWorker; + /// The streaming decryptor this key produces, borrowing the key for `'k`. + type Decryptor<'k>: AEADCipherDecryptor + where + Self: 'k; /// Begins the decryption of `payload_len` bytes of ciphertext under `aad_len` bytes of AAD, /// encrypted under `nonce`. /// /// # Errors /// The mode's refusal of a length it cannot encode. - fn create_decryptor( - &self, + fn create_decryptor<'k>( + &'k self, nonce: &[u8; NONCE_LEN], aad_len: usize, payload_len: usize, - ) -> Result; + ) -> Result, SymmetricCipherError>; /// As [`AeadDecryptionKey::decrypt_with_aad_out`]. /// @@ -803,7 +785,13 @@ pub trait AeadPacketDecryptionKey< aad: &[u8], ciphertext: &[u8], ) -> Result, SymmetricCipherError> { - let mut plaintext = vec![0u8; Self::Decryptor::decrypt_out_len(ciphertext.len())]; + let mut plaintext = vec![ + 0u8; + as SymmetricCipherDecryptor< + NONCE_LEN, + FINAL_LEN, + >>::decrypt_out_len(ciphertext.len()) + ]; let written = self.decrypt_with_aad_out(nonce, aad, ciphertext, &mut plaintext)?; plaintext.truncate(written); Ok(plaintext) @@ -838,7 +826,14 @@ pub trait AeadPacketDecryptionKey< ciphertext: &[u8], tag: &[u8; TAG_LEN], ) -> Result, SymmetricCipherError> { - let mut plaintext = vec![0u8; Self::Decryptor::decrypt_detached_out_len(ciphertext.len())]; + let mut plaintext = vec![ + 0u8; + as AEADCipherDecryptor< + NONCE_LEN, + TAG_LEN, + FINAL_LEN, + >>::decrypt_detached_out_len(ciphertext.len()) + ]; let written = self.decrypt_detached_out(nonce, aad, ciphertext, tag, &mut plaintext)?; plaintext.truncate(written); Ok(plaintext) @@ -862,21 +857,272 @@ pub trait AeadPacketDecryptionKey< } } +/// A key that can begin an unauthenticated encryption of data of any length: the encryption +/// half of a key for padded CBC, CFB, CFB8 or CTR. The encryptor is the mode's ordinary worker, a +/// [`SymmetricCipherEncryptor`]; this trait adds where it comes from, and the one-shots are +/// provided over [`create_encryptor`](Self::create_encryptor) and the worker's streaming methods. +/// +/// The init data (an IV or nonce) is drawn from the RNG the key is given and returned; there is +/// no form that takes one, since these modes need it unpredictable (SP 800-38A Appendix C). +pub trait SymmetricEncryptionKey< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// The streaming encryptor this key produces, borrowing the key for `'k`. + type Encryptor<'k>: SymmetricCipherEncryptor + where + Self: 'k; + + /// Begins an encryption under init data drawn from `rng`, which is returned alongside the + /// encryptor and must reach the recipient, who passes it to + /// [`SymmetricDecryptionKey::create_decryptor`]. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the draw fails; nothing else can. + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + + /// One-shot: encrypts `plaintext` into `ciphertext`, which needs `encrypt_out_len` bytes of + /// the encryptor type, and returns the number of bytes written with the generated init data. + /// The entire output buffer is zeroized before the ciphertext is written. + /// + /// # Errors + /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, or any error + /// of [`create_encryptor`](Self::create_encryptor) and the encryptor's streaming methods. + fn encrypt_out( + &self, + rng: &mut dyn RNG, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + let (enc, init_data) = self.create_encryptor(rng)?; + let written = crate::provided::symmetric_encrypt_out(enc, plaintext, ciphertext)?; + Ok((written, init_data)) + } + + /// As [`encrypt_out`](Self::encrypt_out), allocating the ciphertext. + /// + /// # Errors + /// As [`encrypt_out`](Self::encrypt_out). + #[cfg(feature = "std")] + fn encrypt( + &self, + rng: &mut dyn RNG, + plaintext: &[u8], + ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError> { + let mut ciphertext = vec![ + 0u8; + as SymmetricCipherEncryptor< + INIT_DATA_LEN, + FINAL_LEN, + >>::encrypt_out_len(plaintext.len()) + ]; + let (written, init_data) = self.encrypt_out(rng, plaintext, &mut ciphertext)?; + ciphertext.truncate(written); + Ok((init_data, ciphertext)) + } + + /// One-shot in place, for a mode whose encryptor is a stream cipher: encrypts `data` in its + /// own buffer under init data drawn from `rng`, and returns the number of bytes written with + /// the init data. + /// + /// # Errors + /// As [`create_encryptor`](Self::create_encryptor), or + /// [`StreamCipherEncryptor::do_encrypt_inplace`]'s. + fn encrypt_inplace<'k>( + &'k self, + rng: &mut dyn RNG, + data: &mut [u8], + ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> + where + Self::Encryptor<'k>: StreamCipherEncryptor, + { + let (mut enc, init_data) = self.create_encryptor(rng)?; + let written = StreamCipherEncryptor::do_encrypt_inplace(&mut enc, data)?; + Ok((written, init_data)) + } +} + +/// A key that can begin an unauthenticated decryption of data of any length; see +/// [`SymmetricEncryptionKey`]. Construction is infallible: the key is already validated and the +/// init data length is in the type. +pub trait SymmetricDecryptionKey< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// The streaming decryptor this key produces, borrowing the key for `'k`. + type Decryptor<'k>: SymmetricCipherDecryptor + where + Self: 'k; + + /// Begins the decryption of a message whose encryptor returned `init_data`. + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k>; + + /// One-shot: decrypts `ciphertext` under `init_data` into `plaintext`, which needs + /// `decrypt_out_len(ciphertext.len())` bytes of the decryptor type, and returns the number + /// of plaintext bytes written. The entire output buffer is zeroized before anything is + /// written, and the plaintext released before a failed final is zeroized again. + /// + /// # Errors + /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, or any error + /// of the decryptor's streaming methods: a malformed ciphertext or bad padding. + fn decrypt_out( + &self, + init_data: &[u8; INIT_DATA_LEN], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result { + crate::provided::symmetric_decrypt_out( + self.create_decryptor(init_data), + ciphertext, + plaintext, + ) + } + + /// As [`decrypt_out`](Self::decrypt_out), allocating the plaintext. + /// + /// # Errors + /// As [`decrypt_out`](Self::decrypt_out). + #[cfg(feature = "std")] + fn decrypt( + &self, + init_data: &[u8; INIT_DATA_LEN], + ciphertext: &[u8], + ) -> Result, SymmetricCipherError> { + let mut plaintext = vec![ + 0u8; + as SymmetricCipherDecryptor< + INIT_DATA_LEN, + FINAL_LEN, + >>::decrypt_out_len(ciphertext.len()) + ]; + let written = self.decrypt_out(init_data, ciphertext, &mut plaintext)?; + plaintext.truncate(written); + Ok(plaintext) + } + + /// One-shot in place, for a mode whose decryptor is a stream cipher: decrypts `data` in its + /// own buffer under `init_data`, and returns the number of bytes written. + /// + /// # Errors + /// [`StreamCipherDecryptor::do_decrypt_inplace`]'s. + fn decrypt_inplace<'k>( + &'k self, + init_data: &[u8; INIT_DATA_LEN], + data: &mut [u8], + ) -> Result + where + Self::Decryptor<'k>: StreamCipherDecryptor, + { + let mut dec = self.create_decryptor(init_data); + StreamCipherDecryptor::do_decrypt_inplace(&mut dec, data) + } +} + +/// A key that can begin a whole-block encryption: the encryption half of a key for CBC without +/// a padding layer. The encryptor is a [`BlockCipherEncryptor`], whose buffers are +/// block-aligned arrays checked at compile time; data of arbitrary length goes through a padded +/// mode and [`SymmetricEncryptionKey`] instead. +pub trait BlockEncryptionKey< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, +> +{ + /// The streaming encryptor this key produces, borrowing the key for `'k`. + type Encryptor<'k>: BlockCipherEncryptor + where + Self: 'k; + + /// Begins an encryption under init data drawn from `rng`, which is returned alongside the + /// encryptor and must reach the recipient, who passes it to + /// [`BlockDecryptionKey::create_decryptor`]. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the draw fails; nothing else can. + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result<(Self::Encryptor<'k>, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + + /// One-shot: encrypts `data`, which must be a whole number of blocks, in place under init + /// data drawn from `rng`, and returns the number of bytes written with the init data. + /// + /// # Errors + /// [`SymmetricCipherError::DataNotBlockAligned`] if `data.len()` is not a multiple of + /// `BLOCK_LEN`; otherwise as [`create_encryptor`](Self::create_encryptor), or the encryptor's + /// own refusals. + fn encrypt_inplace( + &self, + rng: &mut dyn RNG, + data: &mut [u8], + ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + let (blocks, []) = data.as_chunks_mut::() else { + return Err(SymmetricCipherError::DataNotBlockAligned); + }; + let (mut enc, init_data) = self.create_encryptor(rng)?; + let written = enc.do_encrypt_blocks_inplace(blocks)?; + Ok((written, init_data)) + } +} + +/// A key that can begin a whole-block decryption; see [`BlockEncryptionKey`]. Construction is +/// infallible: the key is already validated and the init data length is in the type. +pub trait BlockDecryptionKey< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, +> +{ + /// The streaming decryptor this key produces, borrowing the key for `'k`. + type Decryptor<'k>: BlockCipherDecryptor + where + Self: 'k; + + /// Begins the decryption of a message whose encryptor returned `init_data`. + fn create_decryptor<'k>(&'k self, init_data: &[u8; INIT_DATA_LEN]) -> Self::Decryptor<'k>; + + /// One-shot: decrypts `data`, which must be a whole number of blocks, in place under + /// `init_data`, and returns the number of bytes written. + /// + /// # Errors + /// [`SymmetricCipherError::DataNotBlockAligned`] if `data.len()` is not a multiple of + /// `BLOCK_LEN`; otherwise the decryptor's own refusals. + fn decrypt_inplace( + &self, + init_data: &[u8; INIT_DATA_LEN], + data: &mut [u8], + ) -> Result { + let (blocks, []) = data.as_chunks_mut::() else { + return Err(SymmetricCipherError::DataNotBlockAligned); + }; + self.create_decryptor(init_data).do_decrypt_blocks_inplace(blocks) + } +} + // The MAC worker is bounded on `MAC` as a whole for now: splitting its finals into `MACGenerator` // and `MACVerifier` while `MAC` still carries them would put the same method names on two traits, // which is ambiguous at every call site that has both in scope (`do_update` is on `MAC`, `do_final` -// would be on both). The split happens when `MAC` loses its constructors and finals. +// would be on both). The split waits on `MAC` losing its finals; its constructors are already gone. -/// A key that can generate MACs. Each call to [`create_generator`](Self::create_generator) clones -/// the key's per-key state into a fresh worker, so any number of generators can be in flight from +/// A key that can generate MACs. Each call to [`create_generator`](Self::create_generator) starts +/// a fresh worker over the key's per-key state, so any number of generators can be in flight from /// one key and finish in any order; the key is never changed by use. pub trait MacGenerationKey { - /// The streaming worker this key produces for generation. - type Generator: MAC; + /// The streaming worker this key produces for generation, borrowing the key for `'k`. + type Generator<'k>: MAC + where + Self: 'k; /// Begins a MAC over a new message. Infallible: there is nothing to draw and nothing left to /// check. - fn create_generator(&self) -> Self::Generator; + fn create_generator<'k>(&'k self) -> Self::Generator<'k>; /// One-shot: the MAC of `data` into `out`, which is zeroized first; returns the number of /// bytes written. @@ -902,11 +1148,13 @@ pub trait MacGenerationKey { /// A key that can verify MACs; see [`MacGenerationKey`] for the lifetime of the workers. A key /// may implement this and not [`MacGenerationKey`], which is what a verify-only token key is. pub trait MacVerificationKey { - /// The streaming worker this key produces for verification. - type Verifier: MAC; + /// The streaming worker this key produces for verification, borrowing the key for `'k`. + type Verifier<'k>: MAC + where + Self: 'k; /// Begins the verification of a new message. Infallible. - fn create_verifier(&self) -> Self::Verifier; + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k>; /// One-shot: whether `mac` is the MAC of `data`, compared in constant time. fn verify(&self, data: &[u8], mac: &[u8]) -> bool { diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 08bdd20c8..fd4d965c4 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -15,7 +15,7 @@ use crate::key_material::KeyMaterial; use crate::key_material::KeyType; // end of imports needed for docs -/// What the allocating one-shot [`AEADCipherEncryptor::encrypt_detached`] hands back: +/// What an AEAD key's allocating one-shot with a detached tag hands back: /// `(nonce, ciphertext, tag)` #[cfg(feature = "std")] pub type AEADEncryptedTuple = @@ -44,16 +44,10 @@ pub type AEADEncryptedTuple = /// A streaming caller must therefore treat everything `do_update_out` produces as untrusted until /// the final call succeeds, and scrub it if it does not. /// -/// The one-shots -- [`decrypt_detached_out`](Self::decrypt_detached_out), -/// [`decrypt_with_aad_out`](Self::decrypt_with_aad_out) and [`SymmetricCipherDecryptor::decrypt_out`] -- have -/// no such caveat: each owns the whole message, so it zeroizes the buffer itself before returning -/// the error. -pub trait AEADCipherDecryptor< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, ->: SymmetricCipherDecryptor +/// The key's one-shots have no such caveat: each owns the whole message, so it zeroizes the +/// buffer itself before returning the error. +pub trait AEADCipherDecryptor: + 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 @@ -93,8 +87,7 @@ pub trait AEADCipherDecryptor< /// As [`do_decrypt_final_detached_out`](Self::do_decrypt_final_detached_out). /// /// A provided method that takes `self` by value, so not callable on a trait object; the - /// required `_out` form is. The same holds for the other by-value conveniences, and for the - /// constructors and one-shots, which have no receiver at all. + /// required `_out` form is. The same holds for the other three `final` conveniences. fn do_decrypt_final_detached( self, tag: &[u8; TAG_LEN], @@ -108,7 +101,7 @@ pub trait AEADCipherDecryptor< } /// An upper bound on the plaintext recovered from `ciphertext_len` bytes of ciphertext with - /// the tag detached, i.e. the buffer [`decrypt_detached_out`](Self::decrypt_detached_out) + /// the tag detached, i.e. the buffer the key's `decrypt_detached_out` /// requires. The default returns `ciphertext_len` itself, which is exact for every conformant /// AEAD: unlike a padding scheme, an AEAD never expands or shrinks the data it is given, only /// adds the separate `tag`. @@ -120,146 +113,6 @@ pub trait AEADCipherDecryptor< { ciphertext_len } - - /// One-shot with the tag detached: decrypts `ciphertext` into `plaintext`, which needs - /// [`decrypt_detached_out_len`](Self::decrypt_detached_out_len) bytes, under `nonce` - /// and `aad`, and checks `tag`. Returns the number of plaintext bytes written. The entire - /// output buffer is zeroized before the plaintext is written, so any bytes past that count - /// will be 0. - /// - /// Unlike the streaming methods this releases nothing unauthenticated: on failure `plaintext` - /// is zeroized before the error is returned, so a caller who ignores the `Result` is left with - /// zeros rather than attacker-chosen plaintext. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, checked - /// 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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; TAG_LEN], - plaintext: &mut [u8], - ) -> Result - where - Self: Sized, - { - plaintext.fill(0); - let needed = Self::decrypt_detached_out_len(ciphertext.len()); - if plaintext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let mut dec = Self::do_decrypt_init(key, nonce)?; - dec.do_update_aad(aad)?; - let written = dec.do_decrypt_out(ciphertext, plaintext)?; - let mut final_buf = [0u8; FINAL_LEN]; - match dec.do_decrypt_final_detached_out(tag, &mut final_buf) { - Ok(final_len) => { - // Everything held back comes out of `do_decrypt_final_detached_out`, so `written - // + final_len` is the ciphertext length, which `decrypt_detached_out_len` bounds. - plaintext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); - Ok(written + final_len) - } - Err(e) => { - // As in the trait docs: what `do_update_out` already released is unauthenticated, - // and this one-shot owns the whole message, so it does not leave that in the - // caller's hands. A plain `fill` rather than a volatile write because `core` is - // `#![forbid(unsafe_code)]`; the store is to the caller's own buffer, which the - // caller may read after this returns, so it is not a dead store the optimizer is - // entitled to drop. - plaintext[..written].fill(0); - Err(e) - } - } - } - - /// One-shot over the inline `ciphertext || tag` layout with associated data: the trailing - /// `TAG_LEN` bytes of `ciphertext` are the tag. This is [`SymmetricCipherDecryptor::decrypt_out`] - /// with an `aad`, and needs the same - /// [`decrypt_out_len`](SymmetricCipherDecryptor::decrypt_out_len) bytes of - /// `plaintext`. Returns the number of plaintext bytes written. The entire output buffer is - /// zeroized before the plaintext is written, so any bytes past that count will be 0. As with - /// every AEAD one-shot, `plaintext` is zeroized when the tag does not verify. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, checked - /// before any work is done; [`SymmetricCipherError::DecryptionFailed`] if `ciphertext` is - /// 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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result - where - Self: Sized, - { - plaintext.fill(0); - let needed = Self::decrypt_out_len(ciphertext.len()); - if plaintext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let mut dec = Self::do_decrypt_init(key, nonce)?; - dec.do_update_aad(aad)?; - let written = dec.do_decrypt_out(ciphertext, plaintext)?; - match dec.do_decrypt_final() { - Ok((last, data_len)) => { - // `decrypt_out_len` bounds `written + data_len`, so this fits in - // `plaintext[..needed]`. - plaintext[written..written + data_len].copy_from_slice(&last[..data_len]); - Ok(written + data_len) - } - Err(e) => { - // As in `decrypt_detached_out`. - plaintext[..written].fill(0); - Err(e) - } - } - } - - #[cfg(feature = "std")] - /// One-shot, allocating, with the tag detached: as - /// [`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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; TAG_LEN], - ) -> Result, SymmetricCipherError> - where - Self: Sized, - { - let mut plaintext = vec![0u8; Self::decrypt_detached_out_len(ciphertext.len())]; - let written = Self::decrypt_detached_out(key, nonce, aad, ciphertext, tag, &mut plaintext)?; - plaintext.truncate(written); - Ok(plaintext) - } - - #[cfg(feature = "std")] - /// One-shot, allocating, over the inline `ciphertext || tag` layout with associated data: as - /// [`decrypt_with_aad_out`](Self::decrypt_with_aad_out), returning the plaintext as a - /// `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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - ciphertext: &[u8], - ) -> Result, SymmetricCipherError> - where - Self: Sized, - { - let mut plaintext = vec![0u8; Self::decrypt_out_len(ciphertext.len())]; - let written = Self::decrypt_with_aad_out(key, nonce, aad, ciphertext, &mut plaintext)?; - plaintext.truncate(written); - Ok(plaintext) - } } /// The encryption half of an AEAD cipher's streaming API. This extends @@ -285,7 +138,7 @@ pub trait AEADCipherDecryptor< /// be protected against tampering. Every AEAD construction absorbs that AAD *before* the plaintext. /// This leads to a stateful API flow: /// -/// * [`do_encrypt_init`](SymmetricCipherEncryptor::do_encrypt_init) constructs the instance. +/// * The key's `create_encryptor` constructs the instance under a nonce. /// * [`do_update_aad`](Self::do_update_aad) may be called any number of times, including zero if /// there is no AAD. /// * The first [`do_encrypt_out`](SymmetricCipherEncryptor::do_encrypt_out) switches to encrypting, @@ -294,14 +147,14 @@ pub trait AEADCipherDecryptor< /// /// (An empty `aad` slice is a no-op and is accepted at any point.) /// -/// # The nonce is generated by default +/// # The nonce is drawn by default /// -/// [`do_encrypt_init`](SymmetricCipherEncryptor::do_encrypt_init) and the one-shots provided over -/// it draw the nonce themselves and return it for transmission alongside the ciphertext, so a -/// caller cannot repeat one by mistake. A protocol that manages its own nonces -- a counter, or -/// the construction its specification prescribes -- uses -/// [`do_encrypt_init_nonce`](Self::do_encrypt_init_nonce) and the `_nonce` one-shots instead, -/// and then carries the obligation itself: **a nonce must never repeat under one key.** For every +/// The key's `create_encryptor` and the one-shots provided over it draw the nonce from the RNG +/// they are given and return it for transmission alongside the ciphertext, so a caller cannot +/// repeat one by mistake. A protocol that manages its own nonces -- a counter, or the +/// construction its specification prescribes -- uses the key's `create_encryptor_nonce` and the +/// `_nonce` one-shots instead, and then carries the obligation itself: **a nonce must never +/// repeat under one key.** For every /// AEAD here a repeat exposes the XOR of the two plaintexts, and for GCM the authentication key /// as well. /// @@ -317,12 +170,8 @@ pub trait AEADCipherDecryptor< /// to see how much output will be produced for the given amount of input. Doing it this way has /// 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< - const KEY_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const FINAL_LEN: usize, ->: SymmetricCipherEncryptor +pub trait AEADCipherEncryptor: + 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 @@ -368,7 +217,7 @@ pub trait AEADCipherEncryptor< } /// The exact ciphertext length for a `plaintext_len`-byte plaintext with the tag detached, i.e. - /// the buffer [`encrypt_detached_out`](Self::encrypt_detached_out) requires and the number of + /// the buffer the key's `encrypt_detached_out` requires and the number of /// bytes it writes (the tag is returned separately, not counted here). The default returns /// `plaintext_len` itself, which holds for every conformant AEAD: unlike a padding scheme, an /// AEAD never expands or shrinks the data it is given. @@ -380,283 +229,6 @@ pub trait AEADCipherEncryptor< { plaintext_len } - - /// One-shot with the tag detached: encrypts `plaintext` into `ciphertext`, which needs - /// [`encrypt_detached_out_len`](Self::encrypt_detached_out_len) bytes, authenticating `aad` - /// along with it under a fresh nonce. Returns the generated nonce, the number of bytes - /// written, and the tag. The entire output buffer is zeroized before the ciphertext is - /// written, so any bytes past that count will be 0. - /// - /// Provided as `do_encrypt_init`, one `do_update_aad`, one `do_update_out` and - /// `do_encrypt_final_detached_out`. - /// - /// # Errors - /// [`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, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_detached_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, nonce) = Self::do_encrypt_init(key)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let mut final_buf = [0u8; FINAL_LEN]; - let (final_len, tag) = enc.do_encrypt_final_detached_out(&mut final_buf)?; - // Implementors that hold plaintext back must override `encrypt_detached_out_len` if - // `written + final_len` can exceed the plaintext length, so this fits in - // `ciphertext[..needed]`. - ciphertext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); - Ok((nonce, written + final_len, tag)) - } - - #[cfg(feature = "std")] - /// One-shot, allocating, with the tag detached: as - /// [`encrypt_detached_out`](Self::encrypt_detached_out), returning the ciphertext as a - /// `Vec`. Only available with the `std` feature. - fn encrypt_detached( - key: &KeyMaterial, - aad: &[u8], - plaintext: &[u8], - ) -> Result, SymmetricCipherError> - where - Self: Sized, - { - let mut ciphertext = vec![0u8; Self::encrypt_detached_out_len(plaintext.len())]; - let (nonce, written, tag) = - Self::encrypt_detached_out(key, aad, plaintext, &mut ciphertext)?; - ciphertext.truncate(written); - Ok((nonce, ciphertext, tag)) - } - - /// As [`encrypt_detached_out`](Self::encrypt_detached_out), but sources randomness from the - /// provided RNG. - fn encrypt_detached_rng_out( - key: &KeyMaterial, - rng: &mut dyn RNG, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_detached_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, nonce) = Self::do_encrypt_init_rng(key, rng)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let mut final_buf = [0u8; FINAL_LEN]; - let (final_len, tag) = enc.do_encrypt_final_detached_out(&mut final_buf)?; - // As in `encrypt_detached_out`. - ciphertext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); - Ok((nonce, written + final_len, tag)) - } - - /// One-shot into the inline `ciphertext || tag` layout with associated data: this is - /// [`SymmetricCipherEncryptor::encrypt_out`] with an `aad`, and needs the same - /// [`encrypt_out_len`](SymmetricCipherEncryptor::encrypt_out_len) bytes of `ciphertext`. - /// Returns the generated nonce and the total number of bytes written, tag included. The - /// entire output buffer is zeroized before the ciphertext is written, so any bytes past that - /// count will be 0. - /// - /// # Errors - /// [`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, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, nonce) = Self::do_encrypt_init(key)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let (last, last_len) = enc.do_encrypt_final()?; - // `encrypt_out_len` is exactly `written + last_len`, so this fits in `ciphertext[..needed]`. - ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); - Ok((nonce, written + last_len)) - } - - #[cfg(feature = "std")] - /// One-shot, allocating, into the inline `ciphertext || tag` layout with associated data: as - /// [`encrypt_with_aad_out`](Self::encrypt_with_aad_out), returning the ciphertext, tag - /// included, as a `Vec`. This is [`SymmetricCipherEncryptor::encrypt`] with an `aad`. Only - /// available with the `std` feature. - fn encrypt_with_aad( - key: &KeyMaterial, - aad: &[u8], - plaintext: &[u8], - ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> - where - Self: Sized, - { - let mut ciphertext = vec![0u8; Self::encrypt_out_len(plaintext.len())]; - let (nonce, written) = Self::encrypt_with_aad_out(key, aad, plaintext, &mut ciphertext)?; - ciphertext.truncate(written); - Ok((nonce, ciphertext)) - } - - /// 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, - rng: &mut dyn RNG, - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, nonce) = Self::do_encrypt_init_rng(key, rng)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let (last, last_len) = enc.do_encrypt_final()?; - // As in `encrypt_with_aad_out`. - ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); - Ok((nonce, written + last_len)) - } - - /// Begins a streaming encryption under a nonce the caller supplies, for a protocol whose - /// nonce is a counter or another value it manages itself; see the trait docs. The caller - /// takes on the obligation the generating constructors discharge for it: the nonce must never - /// repeat under this key. - /// - /// # Errors - /// As [`do_encrypt_init`](SymmetricCipherEncryptor::do_encrypt_init). - fn do_encrypt_init_nonce( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - ) -> Result - where - Self: Sized; - - /// As [`encrypt_detached_out`](Self::encrypt_detached_out), under a nonce the caller supplies - /// (see [`do_encrypt_init_nonce`](Self::do_encrypt_init_nonce)). Returns the number of bytes - /// written and the tag; the nonce is the caller's, so it is not returned. - /// - /// # Errors - /// As [`encrypt_detached_out`](Self::encrypt_detached_out). - fn encrypt_detached_nonce_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_detached_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let mut enc = Self::do_encrypt_init_nonce(key, nonce)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let mut final_buf = [0u8; FINAL_LEN]; - let (final_len, tag) = enc.do_encrypt_final_detached_out(&mut final_buf)?; - // As in `encrypt_detached_out`. - ciphertext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); - Ok((written + final_len, tag)) - } - - #[cfg(feature = "std")] - /// One-shot, allocating, with the tag detached, under a nonce the caller supplies: as - /// [`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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError> - where - Self: Sized, - { - let mut ciphertext = vec![0u8; Self::encrypt_detached_out_len(plaintext.len())]; - let (written, tag) = - Self::encrypt_detached_nonce_out(key, nonce, aad, plaintext, &mut ciphertext)?; - ciphertext.truncate(written); - Ok((ciphertext, tag)) - } - - /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out), under a nonce the caller supplies - /// (see [`do_encrypt_init_nonce`](Self::do_encrypt_init_nonce)). Returns the number of bytes - /// written, tag included. - /// - /// # Errors - /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). - fn encrypt_with_aad_nonce_out( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let mut enc = Self::do_encrypt_init_nonce(key, nonce)?; - enc.do_update_aad(aad)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let (last, last_len) = enc.do_encrypt_final()?; - // As in `encrypt_with_aad_out`. - ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); - Ok(written + last_len) - } - - #[cfg(feature = "std")] - /// One-shot, allocating, into the inline `ciphertext || tag` layout under a nonce the caller - /// 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, - nonce: &[u8; NONCE_LEN], - aad: &[u8], - plaintext: &[u8], - ) -> Result, SymmetricCipherError> - where - Self: Sized, - { - let mut ciphertext = vec![0u8; Self::encrypt_out_len(plaintext.len())]; - let written = - Self::encrypt_with_aad_nonce_out(key, nonce, aad, plaintext, &mut ciphertext)?; - ciphertext.truncate(written); - Ok(ciphertext) - } } /// Metadata about a cryptographic algorithm. @@ -699,19 +271,9 @@ 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< - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, ->: AlgorithmInfo +pub trait BlockCipherDecryptor: + AlgorithmInfo { - /// Begins a streaming decryption flow from the init data returned by [`BlockCipherEncryptor::do_encrypt_init`]. - fn do_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result - where - Self: Sized; /// The implementor hook: decrypts consecutive whole blocks in place. See /// [`BlockCipherEncryptor::do_encrypt_blocks_inplace`]. Returns the number of bytes written, which is /// always `blocks.len() * BLOCK_LEN` since a block cipher mode never changes the length of its @@ -721,26 +283,6 @@ pub trait BlockCipherDecryptor< &mut self, blocks: &mut [[u8; BLOCK_LEN]], ) -> Result; - - /// One-shot: decrypts `data` in place from the given init data. Returns the number of bytes - /// written; see [`Self::do_decrypt_blocks_inplace`]. - /// - /// # Errors - /// [`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, - init_data: &[u8; INIT_DATA_LEN], - data: &mut [u8], - ) -> Result - where - Self: Sized, - { - let (blocks, []) = data.as_chunks_mut::() else { - return Err(SymmetricCipherError::DataNotBlockAligned); - }; - Self::do_decrypt_init(key, init_data)?.do_decrypt_blocks_inplace(blocks) - } } /// The encryption half of a block cipher's API. @@ -783,35 +325,9 @@ pub trait BlockCipherDecryptor< /// use, so a mode like CBC never returns `Err` from the streaming methods. The `Result` is for modes with a per-initialization /// 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< - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const BLOCK_LEN: usize, ->: AlgorithmInfo +pub trait BlockCipherEncryptor: + AlgorithmInfo { - /// 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> - where - Self: Sized; - /// As [`BlockCipherEncryptor::do_encrypt_init`], but sources randomness from the provided RNG. - /// - /// # Panics - /// An implementation that generates no init data -- `INIT_DATA_LEN == 0`, as in ECB -- must - /// panic here rather than ignore `rng` and succeed. There is no randomness for it to consume, - /// so a caller reaching for this constructor has mistaken the cipher for a randomized one, and - /// quietly returning a deterministic encryptor would leave that mistake undetected. This is a - /// programmer error, not bad input, so it is a panic rather than a - /// [`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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized; /// The implementor hook: encrypts consecutive whole blocks in place. A sequence of calls is /// equivalent to one call over the concatenation. /// @@ -828,49 +344,6 @@ pub trait BlockCipherEncryptor< &mut self, blocks: &mut [[u8; BLOCK_LEN]], ) -> Result; - - /// One-shot: encrypts `data` in place under a fresh init, and returns the number of bytes - /// written (see [`Self::do_encrypt_blocks_inplace`]) alongside the generated init data. - /// - /// # Errors - /// [`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, - data: &mut [u8], - ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized, - { - let (blocks, []) = data.as_chunks_mut::() else { - return Err(SymmetricCipherError::DataNotBlockAligned); - }; - let (mut enc, init_data) = Self::do_encrypt_init(key)?; - let written = enc.do_encrypt_blocks_inplace(blocks)?; - Ok((written, init_data)) - } - /// As [`BlockCipherEncryptor::encrypt_inplace`], but sources randomness from the provided RNG. - /// A misaligned `data` is refused before `rng` is drawn from. - /// - /// # Panics - /// Provided over [`do_encrypt_init_rng`](Self::do_encrypt_init_rng), so it panics in exactly - /// 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, - rng: &mut dyn RNG, - data: &mut [u8], - ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized, - { - let (blocks, []) = data.as_chunks_mut::() else { - return Err(SymmetricCipherError::DataNotBlockAligned); - }; - let (mut enc, init_data) = Self::do_encrypt_init_rng(key, rng)?; - let written = enc.do_encrypt_blocks_inplace(blocks)?; - Ok((written, init_data)) - } } /// A hash function is a cryptographic primitive that takes an input of any length and produces a fixed-size output. @@ -1204,8 +677,8 @@ pub trait KEMPublicKey: /// This trait provides one-shot functions [`MAC::mac`], [`MAC::mac_out`], and [`MAC::verify`]. /// It also provides streaming functions [`MAC::do_update`], [`MAC::do_final`], [`MAC::do_final_out`], /// and [`MAC::do_verify_final`]. -/// The workflow is that a MAC object is initialized with a key with [`MAC::new`] -- or [`MAC::new_allow_weak_key`] if you -/// need to disable the library's safety mechanism to prevent the use of weak keys -- then data is +/// The workflow is that a MAC object is obtained from a key object (`HmacKey`, `KMAC128Key`, +/// `AES128Key` and the like, through `create_generator` / `create_verifier`), then data is /// processed into one or more calls to [`MAC::do_update`], /// after that the object can either create a MAC with [`MAC::do_final`] or [`MAC::do_final_out`] (which are final functions, and so consume the object), /// or the object can be used to verify a MAC. @@ -1215,41 +688,11 @@ pub trait KEMPublicKey: /// use constant-time comparison to avoid cryptographic timing attacks whereby an attacker could learn /// the bytes of the MAC value under some conditions. Therefore, it is highly recommended to use the provided verification functions. /// -/// Note that the MAC key is not represented in this trait because it is provided to the MAC algorithm -/// as part of its new functions. +/// Note that the MAC key is not represented in this trait because it belongs to the key object +/// that makes the worker. /// /// MACs do not implement Default because they do not have a sensible no-args constructor. pub trait MAC: AlgorithmInfo { - /// Create a new MAC instance with the given key. - /// - /// This is a common constructor whether creating or verifying a MAC value. - /// - /// Key / Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero, - /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt. - /// - /// # Note about the security strength of the provided key: - /// If you initialize the MAC with a key that is tagged at a lower [`SecurityStrength`] than the - /// underlying hash function then [`MAC::new`] will fail with the following error: - /// ```text - /// MACError::KeyMaterialError(KeyMaterialError::SecurityStrength("HMAC::init(): provided key has a lower security strength than the instantiated HMAC") - /// ``` - /// There are situations in which it is completely reasonable and secure to provide low-entropy - /// (and sometimes all-zero) keys / salts; for these cases we have provided [`MAC::new_allow_weak_key`]. - fn new(key: &impl KeyMaterialTrait) -> Result - where - Self: Sized; - - /// Create a new HMAC instance with the given key. - /// - /// This constructor completely ignores the [`SecurityStrength`] tag on the input key and will "just work". - /// This should be used if you really do need to use a weak key, such as an all-zero salt, - /// but use of this constructor is discouraged and you should really be asking yourself why you need it; - /// in most cases it indicates that your key is not long enough to support the security level of this - /// HMAC instance, or the key was derived using algorithms at a lower security level, etc. - fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result - where - Self: Sized; - /// The size of the output in bytes. fn output_len(&self) -> usize; @@ -1449,8 +892,8 @@ pub trait SignatureVerifier: AlgorithmInfo + Sized { /// 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: + 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 @@ -1458,19 +901,6 @@ pub trait StreamCipherDecryptor Result; - - /// One-shot: decrypts `data` in place from the given init data. Returns the number of bytes - /// written; see [`Self::do_decrypt_inplace`]. - fn decrypt_inplace( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - data: &mut [u8], - ) -> Result - where - Self: Sized, - { - Self::do_decrypt_init(key, init_data)?.do_decrypt_inplace(data) - } } /// The encryption half of a stream cipher's streaming API: the in-place view of a @@ -1498,8 +928,8 @@ pub trait StreamCipherDecryptor: - SymmetricCipherEncryptor +pub trait StreamCipherEncryptor: + 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 @@ -1518,45 +948,6 @@ pub trait StreamCipherEncryptor Result; - - /// One-shot: encrypts `data` in place under a fresh init, and returns the number of bytes - /// written (see [`Self::do_encrypt_inplace`]) alongside the generated init data. - /// - /// # Errors - /// Whatever [`SymmetricCipherEncryptor::do_encrypt_init`] or [`Self::do_encrypt_inplace`] returns. - fn encrypt_inplace( - key: &KeyMaterial, - data: &mut [u8], - ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized, - { - let (mut enc, init_data) = Self::do_encrypt_init(key)?; - let written = StreamCipherEncryptor::do_encrypt_inplace(&mut enc, data)?; - Ok((written, init_data)) - } - /// As [`StreamCipherEncryptor::encrypt_inplace`], but sources randomness from the provided - /// RNG. - /// - /// # Panics - /// Provided over [`SymmetricCipherEncryptor::do_encrypt_init_rng`], so it panics in exactly - /// the cases that does: an implementation with `INIT_DATA_LEN == 0`, which has no randomness - /// to consume. See that method for why. - /// - /// # Errors - /// Whatever [`SymmetricCipherEncryptor::do_encrypt_init_rng`] or [`Self::do_encrypt_inplace`] returns. - fn encrypt_rng_inplace( - key: &KeyMaterial, - rng: &mut dyn RNG, - data: &mut [u8], - ) -> Result<(usize, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized, - { - let (mut enc, init_data) = Self::do_encrypt_init_rng(key, rng)?; - let written = StreamCipherEncryptor::do_encrypt_inplace(&mut enc, data)?; - Ok((written, init_data)) - } } /// Allows a stateful object to suspend its operation by serializing its state into a byte array @@ -1598,8 +989,10 @@ pub trait Suspendable: Sized { /// which require a private key in order to resume successfully. /// For security reasons, the private key is not included in the serialized state /// and must be provided separately as part of the deserialization process. -pub trait SuspendableKeyed: Sized { - /// The type of key that must be re-supplied to resume this object. +pub trait SuspendableKeyed<'k, const SERIALIZED_STATE_LEN: usize>: Sized { + /// The type of key that must be re-supplied to resume this object. A worker that borrows its + /// key for `'k` names the key's type (the mode's key state, for a cipher mode) and is resumed + /// over the reference it is given; one that owns a copy of its key ignores the lifetime. type Key: ?Sized; /// Suspend operation by serializing out the state of the object. @@ -1618,7 +1011,7 @@ pub trait SuspendableKeyed: Sized { /// deserializer should reject serialized states from that version or older. fn from_suspended( state: [u8; SERIALIZED_STATE_LEN], - key: &Self::Key, + key: &'k Self::Key, ) -> Result; } @@ -1633,31 +1026,13 @@ pub trait SuspendableKeyed: Sized { /// [`do_decrypt_final`](Self::do_decrypt_final) returns the buffer *and* how much of it is data, /// and the one-shot length helper is an upper bound rather than an exact count. /// -/// The one-shot [`decrypt_out`](Self::decrypt_out) is provided over the streaming methods, as is -/// the allocating [`decrypt`](Self::decrypt) behind the `std` feature. An implementor writes only -/// [`do_decrypt_init`](Self::do_decrypt_init), [`update_out_len`](Self::do_decrypt_out_len), -/// [`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< - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const FINAL_LEN: usize, ->: AlgorithmInfo +/// The one-shots are the key's, provided over these streaming methods. An implementor writes +/// only [`update_out_len`](Self::do_decrypt_out_len), [`do_update_out`](Self::do_decrypt_out), +/// [`do_decrypt_final`](Self::do_decrypt_final) and [`decrypt_out_len`](Self::decrypt_out_len); +/// the constructor is the key's, and never part of the trait. +pub trait SymmetricCipherDecryptor: + AlgorithmInfo { - /// Begins a streaming decryption from the init data returned by - /// [`SymmetricCipherEncryptor::do_encrypt_init`]. - /// - /// # Errors - /// 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_decrypt_init( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result - where - Self: Sized; - /// The exact number of bytes the next [`do_update_out`](Self::do_decrypt_out) will write if /// given `input_len` more bytes of ciphertext, so a caller can size the `plaintext` buffer for /// that call before making it. @@ -1753,80 +1128,17 @@ pub trait SymmetricCipherDecryptor< } /// An upper bound on the plaintext recovered from `ciphertext_len` bytes of ciphertext, i.e. - /// the buffer [`decrypt_out`](Self::decrypt_out) requires. Exact for ciphers with no padding; + /// the buffer the key's `decrypt_out` requires. Exact for ciphers with no padding; /// for a padding scheme the exact length is only known after decryption. /// /// Not callable on a trait object: the length is a property of the type. fn decrypt_out_len(ciphertext_len: usize) -> usize where Self: Sized; - - /// One-shot: decrypts `ciphertext` into `plaintext`, which needs - /// [`decrypt_out_len`](Self::decrypt_out_len) bytes. Returns the number of plaintext - /// bytes written. The entire output buffer is zeroized before the plaintext is written, so any - /// bytes past that count will be 0. - /// - /// Provided as `do_decrypt_init`, one `do_update_out` and `do_decrypt_final`. If - /// `do_decrypt_final` fails -- a bad tag, bad padding -- the plaintext already written is - /// zeroized before the error is returned, so a caller who ignores the `Result` is not left - /// holding unauthenticated data. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, checked - /// before any work is done; otherwise whatever the streaming methods return. - fn decrypt_out( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result - where - Self: Sized, - { - plaintext.fill(0); - let needed = Self::decrypt_out_len(ciphertext.len()); - if plaintext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let mut dec = Self::do_decrypt_init(key, init_data)?; - let written = dec.do_decrypt_out(ciphertext, plaintext)?; - match dec.do_decrypt_final() { - Ok((last, data_len)) => { - // `decrypt_out_len` bounds `written + data_len`, so this fits in - // `plaintext[..needed]`. - plaintext[written..written + data_len].copy_from_slice(&last[..data_len]); - Ok(written + data_len) - } - Err(e) => { - // An AEAD reaches this one-shot through its `SymmetricCipherDecryptor` side, and - // what `do_update_out` released is unauthenticated; see - // `AEADCipherDecryptor::decrypt_detached_out` for why a plain `fill` is enough. - plaintext[..written].fill(0); - Err(e) - } - } - } - - /// One-shot, allocating: as [`decrypt_out`](Self::decrypt_out), returning the plaintext as a - /// `Vec` of exactly the recovered length. Only available with the `std` feature. - #[cfg(feature = "std")] - fn decrypt( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ciphertext: &[u8], - ) -> Result, SymmetricCipherError> - where - Self: Sized, - { - let mut plaintext = vec![0u8; Self::decrypt_out_len(ciphertext.len())]; - let written = Self::decrypt_out(key, init_data, ciphertext, &mut plaintext)?; - plaintext.truncate(written); - Ok(plaintext) - } } /// The encryption half of a symmetric cipher's arbitrary-length API: streaming `do_update_out` / -/// `do_encrypt_final`, plus one-shots provided over them. +/// `do_encrypt_final`. The one-shots, and the constructor, are the key's. /// /// This is the layer a caller with *data* uses, as opposed to the block-aligned /// [`BlockCipherEncryptor`] a mode implements. Its shape is that of the padding adapters in @@ -1839,51 +1151,16 @@ pub trait SymmetricCipherDecryptor< /// length is answered exactly, before the fact, by [`update_out_len`](Self::do_encrypt_out_len) and /// [`encrypt_out_len`](Self::encrypt_out_len), so a caller can size buffers without guessing. /// -/// Init data (an IV or nonce) is generated by the constructor and returned, never supplied, for -/// the same reason as in [`BlockCipherEncryptor`]. Everything is `no_std`-friendly except the -/// allocating [`encrypt`](Self::encrypt), which sits behind the `std` feature. -/// -/// The one-shots [`encrypt_out`](Self::encrypt_out) and [`encrypt_rng_out`](Self::encrypt_rng_out) -/// are provided over the streaming methods. An implementor writes only the two `_init` -/// 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< - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - const FINAL_LEN: usize, ->: AlgorithmInfo +/// Init data (an IV or nonce) is drawn by the key's `create_encryptor` and returned, never +/// supplied, for the same reason as in [`BlockCipherEncryptor`]. Everything is `no_std`-friendly +/// except the allocating [`do_encrypt`](Self::do_encrypt), which sits behind the `std` feature. +/// +/// An implementor writes only [`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: + AlgorithmInfo { - /// Begins a streaming encryption, returning the encryptor and the generated init data (IV or - /// nonce), which the recipient needs for [`SymmetricCipherDecryptor::do_decrypt_init`]. Sources - /// randomness from the library's default OS-backed RNG. - /// - /// # Errors - /// 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> - where - Self: Sized; - - /// As [`do_encrypt_init`](Self::do_encrypt_init), but sources randomness from the provided RNG. - /// - /// # Panics - /// An implementation that generates no init data -- `INIT_DATA_LEN == 0`, as in ECB -- must - /// panic here rather than ignore `rng` and succeed. There is no randomness for it to consume, - /// so a caller reaching for this constructor has mistaken the cipher for a randomized one, and - /// quietly returning a deterministic encryptor would leave that mistake undetected. This is a - /// programmer error, not bad input, so it is a panic rather than a - /// [`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, - rng: &mut dyn RNG, - ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> - where - Self: Sized; - /// The exact number of bytes the next [`do_update_out`](Self::do_encrypt_out) will write if /// given `input_len` more bytes of plaintext, so a caller can size the `ciphertext` buffer for /// that call before making it. @@ -1972,91 +1249,13 @@ pub trait SymmetricCipherEncryptor< } /// The exact ciphertext length for a `plaintext_len`-byte plaintext that the cipher accepts, - /// i.e. the buffer [`encrypt_out`](Self::encrypt_out) requires and the number of bytes it - /// writes. (A length the cipher rejects -- unaligned data under a scheme that adds no padding -- + /// i.e. the buffer the key's `encrypt_out` requires and the number of bytes it writes. (A length the cipher rejects -- unaligned data under a scheme that adds no padding -- /// fails in [`do_encrypt_final`](Self::do_encrypt_final) instead.) /// /// Not callable on a trait object: the length is a property of the type. fn encrypt_out_len(plaintext_len: usize) -> usize where Self: Sized; - - /// One-shot: encrypts `plaintext` into `ciphertext`, which needs - /// [`encrypt_out_len`](Self::encrypt_out_len) bytes. Returns the generated init data and the - /// number of bytes written. The entire output buffer is zeroized before the ciphertext is - /// written, so any bytes past that count will be 0. - /// - /// Provided as `do_encrypt_init`, one `do_update_out` and `do_encrypt_final`. - /// - /// # Errors - /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, checked - /// before any work is done; otherwise whatever the streaming methods return. - fn encrypt_out( - key: &KeyMaterial, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, init_data) = Self::do_encrypt_init(key)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let (last, last_len) = enc.do_encrypt_final()?; - // `encrypt_out_len` is exactly `written + last_len`, so this fits in `ciphertext[..needed]`. - // .copy_from_slice is a bit of a code smell for a function meant to work in-place, - // but `ciphertext` is not required to be block-aligned, so it may not be large enough - // to hand to `do_encrypt_final_out()`. - ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); - Ok((init_data, written + last_len)) - } - - /// As [`encrypt_out`](Self::encrypt_out), but sources randomness from the provided RNG. - /// - /// # Panics - /// Provided over [`do_encrypt_init_rng`](Self::do_encrypt_init_rng), so it panics in exactly - /// 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, - rng: &mut dyn RNG, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError> - where - Self: Sized, - { - ciphertext.fill(0); - let needed = Self::encrypt_out_len(plaintext.len()); - if ciphertext.len() < needed { - return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); - } - let (mut enc, init_data) = Self::do_encrypt_init_rng(key, rng)?; - let written = enc.do_encrypt_out(plaintext, ciphertext)?; - let (last, last_len) = enc.do_encrypt_final()?; - ciphertext[written..written + last_len].copy_from_slice(&last[..last_len]); - Ok((init_data, written + last_len)) - } - - #[cfg(feature = "std")] - /// 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, - plaintext: &[u8], - ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError> - where - Self: Sized, - { - let mut ciphertext = vec![0u8; Self::encrypt_out_len(plaintext.len())]; - let (init_data, written) = Self::encrypt_out(key, plaintext, &mut ciphertext)?; - ciphertext.truncate(written); - Ok((init_data, ciphertext)) - } } /// The squeezing phase of an [`XOF`]: a value that produces output and can no longer take input, diff --git a/crypto/core/tests/aead_buffering_toy_tests.rs b/crypto/core/tests/aead_buffering_toy_tests.rs index 9180a2612..27aa50e2f 100644 --- a/crypto/core/tests/aead_buffering_toy_tests.rs +++ b/crypto/core/tests/aead_buffering_toy_tests.rs @@ -15,8 +15,8 @@ /// Pins that a *genuinely buffering* `AEADCipherEncryptor` / `AEADCipherDecryptor` pair's /// `update_out_len` is honoured through every chunking, against a toy built to hold back up to /// three bytes at a time before releasing them -- more than the tag the decryptor has to hold -/// back anyway -- the property `TestFrameworkAEADCipher::test_encryptor_decryptor` cannot pin on its own, since -/// a caller-supplied `E`/`D` might hold back nothing but the tag (Ascon-AEAD128 holds back +/// back anyway -- the property `TestFrameworkAEADCipher::test_key` cannot pin on its own, since +/// a key's workers might hold back nothing but the tag (Ascon-AEAD128 holds back /// nothing else). Modelled on the toy permutations `crypto/cipher/tests/modes/common/mod.rs` uses for /// the equivalent block-cipher property. /// @@ -27,15 +27,13 @@ #[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::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, RNG, SymmetricCipherDecryptor, + AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; const HOLD_BACK: usize = 3; - const KEY_LEN: usize = 4; const NONCE_LEN: usize = 4; const TAG_LEN: usize = 1; // What either side's final call can produce: the encryptor's held-back bytes plus the tag @@ -113,18 +111,7 @@ 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> { - Ok((Self(Buffered::new(HOLD_BACK)), [0u8; NONCE_LEN])) - } - fn do_encrypt_init_rng( - key: &KeyMaterial, - _rng: &mut dyn RNG, - ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { - Self::do_encrypt_init(key) - } + impl SymmetricCipherEncryptor for Enc { fn do_encrypt_out_len(&self, input_len: usize) -> usize { self.0.update_out_len(input_len) } @@ -147,16 +134,10 @@ 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, - _nonce: &[u8; NONCE_LEN], - ) -> Result { - Ok(Self::do_encrypt_init(key)?.0) - } fn do_encrypt_final_detached_out( mut self, ciphertext: &mut [u8; FINAL_LEN], @@ -168,13 +149,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { } } - impl SymmetricCipherDecryptor for Dec { - fn do_decrypt_init( - _key: &KeyMaterial, - _nonce: &[u8; NONCE_LEN], - ) -> Result { - Ok(Self(Buffered::new(FINAL_LEN))) - } + impl SymmetricCipherDecryptor for Dec { fn do_decrypt_out_len(&self, input_len: usize) -> usize { self.0.update_out_len(input_len) } @@ -203,7 +178,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(()) } @@ -222,21 +197,30 @@ 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 + // The toy takes no key: these stand in for a key's `create_encryptor` / `create_decryptor`. + let fresh_enc = || Enc(Buffered::new(HOLD_BACK)); + let fresh_dec = || Dec(Buffered::new(FINAL_LEN)); + + // The bytes every 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(); for len in 0..=(3 * FINAL_LEN + 5) { let msg = &seed[..len]; + // The detached one-shot, streamed by hand: everything in one update, then the final. let mut ct = vec![0u8; len]; - let (nonce, ct_len, tag) = Enc::encrypt_detached_out(&key, b"", msg, &mut ct).unwrap(); + let (ct_len, tag) = { + let mut enc = fresh_enc(); + let written = enc.do_encrypt_out(msg, &mut ct).unwrap(); + let mut final_buf = [0u8; FINAL_LEN]; + let (final_len, tag) = enc.do_encrypt_final_detached_out(&mut final_buf).unwrap(); + ct[written..written + final_len].copy_from_slice(&final_buf[..final_len]); + (written + final_len, tag) + }; assert_eq!(ct_len, len, "the toy never expands the data, only the finalizer flushes"); for chunk in [1usize, 2, 3, HOLD_BACK, FINAL_LEN, FINAL_LEN + 1, len.max(1)] { - let (mut enc, _) = Enc::do_encrypt_init(&key).unwrap(); + let mut enc = fresh_enc(); let mut chunked = Vec::new(); for piece in msg.chunks(chunk) { let expect = enc.do_encrypt_out_len(piece.len()); @@ -257,7 +241,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { // detached: the decryptor releases what it held back as a possible tag in // `do_decrypt_final_detached_out`, alongside what it held back of its own accord - let mut dec = Dec::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = fresh_dec(); let mut pt = Vec::new(); for piece in ct.chunks(chunk) { let expect = dec.do_decrypt_out_len(piece.len()); @@ -274,7 +258,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { // inline: the same stream with the tag on the end, chunked the same way let mut inline = ct.clone(); inline.extend_from_slice(&tag); - let mut dec = Dec::do_decrypt_init(&key, &nonce).unwrap(); + let mut dec = fresh_dec(); let mut pt = Vec::new(); for piece in inline.chunks(chunk) { let expect = dec.do_decrypt_out_len(piece.len()); @@ -291,7 +275,7 @@ fn a_buffering_pair_is_handled_by_every_default_method() { // The inline `ciphertext || tag` layout, which is where a buffering cipher makes // `do_encrypt_final` do two things at once: flush the held-back bytes and then append the // tag after them. - let (mut enc, nonce) = Enc::do_encrypt_init(&key).unwrap(); + let mut enc = fresh_enc(); let mut inline = vec![0u8; enc.do_encrypt_out_len(len)]; let written = enc.do_encrypt_out(msg, &mut inline).unwrap(); assert!(written < len || len == 0, "len {len}: the toy must be holding something back"); @@ -303,47 +287,12 @@ fn a_buffering_pair_is_handled_by_every_default_method() { "len {len}: inline layout is the message plus a tag" ); - let mut one = vec![0u8; Enc::encrypt_out_len(len)]; - let (one_nonce, one_len) = Enc::encrypt_with_aad_out(&key, b"", msg, &mut one).unwrap(); - assert_eq!(&one[..one_len], &inline[..], "len {len}: one-shot must agree"); - assert_eq!(one_nonce, nonce); - // Exactly the buffer it asks for: that is what makes the `+ data_len` arithmetic in - // the one-shot observable, since with a generous buffer any arithmetic there would do. - let mut back = vec![0u8; Dec::decrypt_out_len(one_len)]; - let back_len = - Dec::decrypt_with_aad_out(&key, &one_nonce, b"", &one[..one_len], &mut back).unwrap(); - assert_eq!(&back[..back_len], msg, "len {len}: inline one-shot round trip"); - - // Every other one-shot over the toy too: its final calls flush real data, which is - // what makes the `written + final_len` arithmetic in each of them observable. - let mut ct_rng = vec![0u8; len]; - let (_, n_rng, tag_rng) = Enc::encrypt_detached_rng_out( - &key, - &mut bouncycastle_rng::DefaultRNG::default(), - b"", - msg, - &mut ct_rng, - ) - .unwrap(); - assert_eq!(&ct_rng[..n_rng], &ct[..], "len {len}: encrypt_detached_rng_out"); - assert_eq!(tag_rng, tag, "len {len}: encrypt_detached_rng_out tag"); - let mut back = vec![0u8; len]; - let back_len = Dec::decrypt_detached_out(&key, &nonce, b"", &ct, &tag, &mut back).unwrap(); - assert_eq!(&back[..back_len], msg, "len {len}: decrypt_detached_out"); - let mut plain = vec![0u8; Enc::encrypt_out_len(len)]; - let (plain_nonce, plain_len) = Enc::encrypt_out(&key, msg, &mut plain).unwrap(); - assert_eq!(&plain[..plain_len], &inline[..], "len {len}: encrypt_out"); - let mut back = vec![0u8; Dec::decrypt_out_len(plain_len)]; - let back_len = - Dec::decrypt_out(&key, &plain_nonce, &plain[..plain_len], &mut back).unwrap(); - assert_eq!(&back[..back_len], msg, "len {len}: decrypt_out"); - // For any length past the hold-back window, at least one prefix of the input must be // held back rather than released immediately -- the property this whole test exists // to pin. (For `len < HOLD_BACK` nothing is ever releasable until `do_encrypt_final`, which // is also correct but does not exercise `do_update_out` returning less than it was given.) if len > HOLD_BACK { - let (mut enc, _) = Enc::do_encrypt_init(&key).unwrap(); + let mut enc = fresh_enc(); let first = &msg[..1]; let mut buf = vec![0u8; enc.do_encrypt_out_len(first.len())]; let n = enc.do_encrypt_out(first, &mut buf).unwrap(); diff --git a/crypto/core/tests/dyn_compatible_tests.rs b/crypto/core/tests/dyn_compatible_tests.rs index 8b9aaf270..58e3228ff 100644 --- a/crypto/core/tests/dyn_compatible_tests.rs +++ b/crypto/core/tests/dyn_compatible_tests.rs @@ -10,23 +10,15 @@ use bouncycastle_core::traits::{ }; fn symmetric( - _: &mut dyn SymmetricCipherEncryptor<16, 16, 16>, - _: &mut dyn SymmetricCipherDecryptor<16, 16, 16>, -) { -} -fn aead( - _: &mut dyn AEADCipherEncryptor<16, 12, 16, 16>, - _: &mut dyn AEADCipherDecryptor<16, 12, 16, 16>, -) { -} -fn stream(_: &mut dyn StreamCipherEncryptor<16, 16>, _: &mut dyn StreamCipherDecryptor<16, 16>) {} -fn block( - _: &mut dyn BlockCipherEncryptor<16, 16, 16>, - _: &mut dyn BlockCipherDecryptor<16, 16, 16>, + _: &mut dyn SymmetricCipherEncryptor<16, 16>, + _: &mut dyn SymmetricCipherDecryptor<16, 16>, ) { } +fn aead(_: &mut dyn AEADCipherEncryptor<12, 16, 16>, _: &mut dyn AEADCipherDecryptor<12, 16, 16>) {} +fn stream(_: &mut dyn StreamCipherEncryptor<16>, _: &mut dyn StreamCipherDecryptor<16>) {} +fn block(_: &mut dyn BlockCipherEncryptor<16, 16>, _: &mut dyn BlockCipherDecryptor<16, 16>) {} fn mac(_: &mut dyn MAC) {} -fn key_stream(_: &mut dyn KeyStream<16, 12, 16>) {} +fn key_stream(_: &mut dyn KeyStream<12, 16>) {} fn hash(_: &mut dyn Hash) {} /// A XOF names its squeezer type, so the object type is `dyn XOF`. fn xof(_: &mut dyn XOF, _: &mut dyn XOFSqueezer) {} diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index bca2f318d..dea104962 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -7,6 +7,7 @@ mod test_key_material { KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::RNG; const DUMMY_KEY: &[u8; 64] = 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\ @@ -214,6 +215,24 @@ mod test_key_material { assert_eq!(key.key_type(), KeyType::CryptographicRandom); } + /// The material carries the RNG's strength, capped at what the key length can carry: the + /// default DRBG is 256-bit, so 32 bytes keep that and 16 bytes are a 128-bit key. + #[test] + fn from_rng_caps_the_strength_at_the_key_length() { + use bouncycastle_rng as rng; + + let mut drbg = rng::DefaultRNG::default(); + let rng_strength = drbg.security_strength(); + assert!(rng_strength >= SecurityStrength::_256bit, "the default DRBG is 256-bit"); + + let key = KeyMaterial256::from_rng(&mut drbg).unwrap(); + assert_eq!(key.security_strength(), rng_strength, "32 bytes carry the RNG's strength"); + let key = KeyMaterial128::from_rng(&mut drbg).unwrap(); + assert_eq!(key.security_strength(), SecurityStrength::_128bit, "16 bytes cap it"); + let key = KeyMaterial512::from_rng(&mut drbg).unwrap(); + assert_eq!(key.security_strength(), rng_strength, "64 bytes keep the RNG's strength"); + } + #[test] fn zeroize() { let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); diff --git a/crypto/factory/Cargo.toml b/crypto/factory/Cargo.toml index c97657960..7350cb154 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] bouncycastle-ascon.workspace = true bouncycastle-core.workspace = true +bouncycastle-hmac.workspace = true bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-sm3.workspace = true diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index 6fa2bb8e6..b6df9e8b5 100644 --- a/crypto/factory/src/mac_factory.rs +++ b/crypto/factory/src/mac_factory.rs @@ -1,7 +1,9 @@ -//! MAC factory for creating instances of algorithms that implement the [`MAC`] trait. +//! MAC factory for creating MAC keys by algorithm name. //! //! As with all Factory objects, this implements constructions from strings and defaults, and -//! returns a [`MACFactory`] object which itself implements the [`MAC`] trait as a pass-through to the underlying algorithm. +//! returns a [`MACFactory`] object: a key, which implements [`MacGenerationKey`] and +//! [`MacVerificationKey`] as a pass-through to the underlying key type, and whose workers, +//! [`MACFactoryWorker`], implement [`MAC`] the same way. //! See [`MAC`] for the full message-authentication API, key handling, streaming workflow, and verification guidance. //! //! Example usage: @@ -9,7 +11,7 @@ //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; //! use bouncycastle_hex as hex; //! use bouncycastle_factory::AlgorithmFactory; //! use bouncycastle_factory::mac_factory::MACFactory; @@ -26,8 +28,7 @@ //! // Generate the MAC value //! let mac_value: Vec = hmac.mac(data); //! -//! // Verify the MAC value -//! let hmac = MACFactory::new(bouncycastle_sha3::hmac::HMAC_SHA3_256_NAME, &key).unwrap(); +//! // Verify the MAC value; the key is used for any number of messages //! if hmac.verify(data, &mac_value,) { //! println!("MAC verified successfully!") //! } else { @@ -75,7 +76,9 @@ use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; use bouncycastle_core::errors::MACError; use bouncycastle_core::key_material::KeyMaterialTrait; use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{AlgorithmInfo, MAC}; +use bouncycastle_hmac::{HMAC, HmacKey}; use bouncycastle_sha2 as sha2; use bouncycastle_sha2::hmac::{ HMAC_SHA224_NAME, HMAC_SHA256_NAME, HMAC_SHA384_NAME, HMAC_SHA512_224_NAME, @@ -85,7 +88,9 @@ use bouncycastle_sha3 as sha3; use bouncycastle_sha3::hmac::{ HMAC_SHA3_224_NAME, HMAC_SHA3_256_NAME, HMAC_SHA3_384_NAME, HMAC_SHA3_512_NAME, }; -use bouncycastle_sha3::kmac::{KMAC128, KMAC128_NAME, KMAC256, KMAC256_NAME}; +use bouncycastle_sha3::kmac::{ + KMAC128, KMAC128_NAME, KMAC128Key, KMAC256, KMAC256_NAME, KMAC256Key, +}; use bouncycastle_sm3 as sm3; use bouncycastle_sm3::hmac::HMAC_SM3_NAME; @@ -97,42 +102,76 @@ pub const DEFAULT_128BIT_MAC_NAME: &str = HMAC_SHA256_NAME; /// pub const DEFAULT_256BIT_MAC_NAME: &str = HMAC_SHA256_NAME; +/// A MAC key selected by name: the key object of whichever algorithm was named, which hands out +/// [`MACFactoryWorker`]s through [`MacGenerationKey`] and [`MacVerificationKey`]. +/// MACFactory deviates from the usual AlgorithmFactory trait because MAC keys do not have a no-arg +/// constructor; instead they have a constructor that takes a [`KeyMaterialTrait`] and can return +/// an error. #[allow(non_camel_case_types)] - -/// Wrapper object for all algorithms that impl [`MAC`]. -/// MACFactory deviates from the usual AlgorithmFactory trait because MAC objects do not have a no-arg constructor; -/// instead they have a constructor that takes a [`KeyMaterialTrait`] and can return an error. #[non_exhaustive] pub enum MACFactory { /// KMAC128 with no customization string and a 32-byte tag (NIST SP 800-185 Sec 4). /// For a customization string or a different output length, construct - /// `bouncycastle_sha3::KMAC128` directly -- the factory selects by name alone and has no - /// channel for those parameters. - KMAC128(KMAC128), + /// `bouncycastle_sha3::kmac::KMAC128Key` directly -- the factory selects by name alone and has + /// no channel for those parameters. + KMAC128(KMAC128Key), /// KMAC256 with no customization string and a 64-byte tag. See [`MACFactory::KMAC128`]. + KMAC256(KMAC256Key), + /// + HMAC_SHA224(HmacKey), + /// + HMAC_SHA256(HmacKey), + /// + HMAC_SHA384(HmacKey), + /// + HMAC_SHA512(HmacKey), + /// + HMAC_SHA512_224(HmacKey), + /// + HMAC_SHA512_256(HmacKey), + /// + HMAC_SHA3_224(HmacKey), + /// + HMAC_SHA3_256(HmacKey), + /// + HMAC_SHA3_384(HmacKey), + /// + HMAC_SHA3_512(HmacKey), + /// + HMAC_SM3(HmacKey), +} + +/// The worker a [`MACFactory`] key hands out: the named algorithm's own worker, borrowing the key +/// for `'k`, behind one [`MAC`] impl. +#[allow(non_camel_case_types)] +#[non_exhaustive] +pub enum MACFactoryWorker<'k> { + /// + KMAC128(KMAC128), + /// KMAC256(KMAC256), /// - HMAC_SHA224(sha2::hmac::HMAC_SHA224), + HMAC_SHA224(HMAC<'k, sha2::SHA224>), /// - HMAC_SHA256(sha2::hmac::HMAC_SHA256), + HMAC_SHA256(HMAC<'k, sha2::SHA256>), /// - HMAC_SHA384(sha2::hmac::HMAC_SHA384), + HMAC_SHA384(HMAC<'k, sha2::SHA384>), /// - HMAC_SHA512(sha2::hmac::HMAC_SHA512), + HMAC_SHA512(HMAC<'k, sha2::SHA512>), /// - HMAC_SHA512_224(sha2::hmac::HMAC_SHA512_224), + HMAC_SHA512_224(HMAC<'k, sha2::SHA512_224>), /// - HMAC_SHA512_256(sha2::hmac::HMAC_SHA512_256), + HMAC_SHA512_256(HMAC<'k, sha2::SHA512_256>), /// - HMAC_SHA3_224(sha3::hmac::HMAC_SHA3_224), + HMAC_SHA3_224(HMAC<'k, sha3::SHA3_224>), /// - HMAC_SHA3_256(sha3::hmac::HMAC_SHA3_256), + HMAC_SHA3_256(HMAC<'k, sha3::SHA3_256>), /// - HMAC_SHA3_384(sha3::hmac::HMAC_SHA3_384), + HMAC_SHA3_384(HMAC<'k, sha3::SHA3_384>), /// - HMAC_SHA3_512(sha3::hmac::HMAC_SHA3_512), + HMAC_SHA3_512(HMAC<'k, sha3::SHA3_512>), /// - HMAC_SM3(sm3::hmac::HMAC_SM3), + HMAC_SM3(HMAC<'k, sm3::SM3>), } impl MACFactory { @@ -154,42 +193,69 @@ impl MACFactory { DEFAULT => Self::default(key), DEFAULT_128_BIT => Self::default_128_bit(key), DEFAULT_256_BIT => Self::default_256_bit(key), - KMAC128_NAME => Ok(Self::KMAC128(KMAC128::new(key)?)), - KMAC256_NAME => Ok(Self::KMAC256(KMAC256::new(key)?)), - HMAC_SHA224_NAME => Ok(Self::HMAC_SHA224(sha2::hmac::HMAC_SHA224::new(key)?)), - HMAC_SHA256_NAME => Ok(Self::HMAC_SHA256(sha2::hmac::HMAC_SHA256::new(key)?)), - HMAC_SHA384_NAME => Ok(Self::HMAC_SHA384(sha2::hmac::HMAC_SHA384::new(key)?)), - HMAC_SHA512_NAME => Ok(Self::HMAC_SHA512(sha2::hmac::HMAC_SHA512::new(key)?)), - HMAC_SHA512_224_NAME => { - Ok(Self::HMAC_SHA512_224(sha2::hmac::HMAC_SHA512_224::new(key)?)) - } - HMAC_SHA512_256_NAME => { - Ok(Self::HMAC_SHA512_256(sha2::hmac::HMAC_SHA512_256::new(key)?)) - } - HMAC_SHA3_224_NAME => Ok(Self::HMAC_SHA3_224(sha3::hmac::HMAC_SHA3_224::new(key)?)), - HMAC_SHA3_256_NAME => Ok(Self::HMAC_SHA3_256(sha3::hmac::HMAC_SHA3_256::new(key)?)), - HMAC_SHA3_384_NAME => Ok(Self::HMAC_SHA3_384(sha3::hmac::HMAC_SHA3_384::new(key)?)), - HMAC_SHA3_512_NAME => Ok(Self::HMAC_SHA3_512(sha3::hmac::HMAC_SHA3_512::new(key)?)), - HMAC_SM3_NAME => Ok(Self::HMAC_SM3(sm3::hmac::HMAC_SM3::new(key)?)), + KMAC128_NAME => Ok(Self::KMAC128(KMAC128Key::from_key_material(key)?)), + KMAC256_NAME => Ok(Self::KMAC256(KMAC256Key::from_key_material(key)?)), + HMAC_SHA224_NAME => Ok(Self::HMAC_SHA224(HmacKey::from_key_material(key)?)), + HMAC_SHA256_NAME => Ok(Self::HMAC_SHA256(HmacKey::from_key_material(key)?)), + HMAC_SHA384_NAME => Ok(Self::HMAC_SHA384(HmacKey::from_key_material(key)?)), + HMAC_SHA512_NAME => Ok(Self::HMAC_SHA512(HmacKey::from_key_material(key)?)), + HMAC_SHA512_224_NAME => Ok(Self::HMAC_SHA512_224(HmacKey::from_key_material(key)?)), + HMAC_SHA512_256_NAME => Ok(Self::HMAC_SHA512_256(HmacKey::from_key_material(key)?)), + HMAC_SHA3_224_NAME => Ok(Self::HMAC_SHA3_224(HmacKey::from_key_material(key)?)), + HMAC_SHA3_256_NAME => Ok(Self::HMAC_SHA3_256(HmacKey::from_key_material(key)?)), + HMAC_SHA3_384_NAME => Ok(Self::HMAC_SHA3_384(HmacKey::from_key_material(key)?)), + HMAC_SHA3_512_NAME => Ok(Self::HMAC_SHA3_512(HmacKey::from_key_material(key)?)), + HMAC_SM3_NAME => Ok(Self::HMAC_SM3(HmacKey::from_key_material(key)?)), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known MAC", alg_name ))), } } + + /// The worker for one message, from whichever key this is. + fn worker(&self) -> MACFactoryWorker<'_> { + match self { + Self::KMAC128(k) => MACFactoryWorker::KMAC128(k.create_generator()), + Self::KMAC256(k) => MACFactoryWorker::KMAC256(k.create_generator()), + Self::HMAC_SHA224(k) => MACFactoryWorker::HMAC_SHA224(k.create_generator()), + Self::HMAC_SHA256(k) => MACFactoryWorker::HMAC_SHA256(k.create_generator()), + Self::HMAC_SHA384(k) => MACFactoryWorker::HMAC_SHA384(k.create_generator()), + Self::HMAC_SHA512(k) => MACFactoryWorker::HMAC_SHA512(k.create_generator()), + Self::HMAC_SHA512_224(k) => MACFactoryWorker::HMAC_SHA512_224(k.create_generator()), + Self::HMAC_SHA512_256(k) => MACFactoryWorker::HMAC_SHA512_256(k.create_generator()), + Self::HMAC_SHA3_224(k) => MACFactoryWorker::HMAC_SHA3_224(k.create_generator()), + Self::HMAC_SHA3_256(k) => MACFactoryWorker::HMAC_SHA3_256(k.create_generator()), + Self::HMAC_SHA3_384(k) => MACFactoryWorker::HMAC_SHA3_384(k.create_generator()), + Self::HMAC_SHA3_512(k) => MACFactoryWorker::HMAC_SHA3_512(k.create_generator()), + Self::HMAC_SM3(k) => MACFactoryWorker::HMAC_SM3(k.create_generator()), + } + } } -impl MAC for MACFactory { - /// This is a dummy function, required by the [`MAC`] trait. DO NOT call it, it does not do anything. - fn new(_key: &impl KeyMaterialTrait) -> Result { - unimplemented!() +impl MacGenerationKey for MACFactory { + type Generator<'k> + = MACFactoryWorker<'k> + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + self.worker() } +} - /// This is a dummy function, required by the [`MAC`] trait. DO NOT call it, it does not do anything. - fn new_allow_weak_key(_key: &impl KeyMaterialTrait) -> Result { - unimplemented!() +impl MacVerificationKey for MACFactory { + type Verifier<'k> + = MACFactoryWorker<'k> + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.worker() } +} +impl MAC for MACFactoryWorker<'_> { fn output_len(&self) -> usize { match self { Self::KMAC128(h) => h.output_len(), @@ -228,7 +294,6 @@ impl MAC for MACFactory { fn mac_out(self, data: &[u8], out: &mut [u8]) -> Result { out.fill(0); - match self { Self::KMAC128(h) => h.mac_out(data, out), Self::KMAC256(h) => h.mac_out(data, out), @@ -300,23 +365,22 @@ impl MAC for MACFactory { } } - fn do_final_out(self, mut out: &mut [u8]) -> Result { + fn do_final_out(self, out: &mut [u8]) -> Result { out.fill(0); - match self { - Self::KMAC128(h) => h.do_final_out(&mut out), - Self::KMAC256(h) => h.do_final_out(&mut out), - Self::HMAC_SHA224(h) => h.do_final_out(&mut out), - Self::HMAC_SHA256(h) => h.do_final_out(&mut out), - Self::HMAC_SHA384(h) => h.do_final_out(&mut out), - Self::HMAC_SHA512(h) => h.do_final_out(&mut out), - Self::HMAC_SHA512_224(h) => h.do_final_out(&mut out), - Self::HMAC_SHA512_256(h) => h.do_final_out(&mut out), - Self::HMAC_SHA3_224(h) => h.do_final_out(&mut out), - Self::HMAC_SHA3_256(h) => h.do_final_out(&mut out), - Self::HMAC_SHA3_384(h) => h.do_final_out(&mut out), - Self::HMAC_SHA3_512(h) => h.do_final_out(&mut out), - Self::HMAC_SM3(h) => h.do_final_out(&mut out), + Self::KMAC128(h) => h.do_final_out(out), + Self::KMAC256(h) => h.do_final_out(out), + Self::HMAC_SHA224(h) => h.do_final_out(out), + Self::HMAC_SHA256(h) => h.do_final_out(out), + Self::HMAC_SHA384(h) => h.do_final_out(out), + Self::HMAC_SHA512(h) => h.do_final_out(out), + Self::HMAC_SHA512_224(h) => h.do_final_out(out), + Self::HMAC_SHA512_256(h) => h.do_final_out(out), + Self::HMAC_SHA3_224(h) => h.do_final_out(out), + Self::HMAC_SHA3_256(h) => h.do_final_out(out), + Self::HMAC_SHA3_384(h) => h.do_final_out(out), + Self::HMAC_SHA3_512(h) => h.do_final_out(out), + Self::HMAC_SM3(h) => h.do_final_out(out), } } @@ -339,7 +403,7 @@ impl MAC for MACFactory { } } -impl AlgorithmInfo for MACFactory { +impl AlgorithmInfo for MACFactoryWorker<'_> { fn alg_name(&self) -> &'static str { match self { Self::KMAC128(h) => h.alg_name(), diff --git a/crypto/factory/tests/mac_factory_tests.rs b/crypto/factory/tests/mac_factory_tests.rs index 84c791117..76d1d9c34 100644 --- a/crypto/factory/tests/mac_factory_tests.rs +++ b/crypto/factory/tests/mac_factory_tests.rs @@ -2,6 +2,7 @@ mod hash_factory_tests { use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{AlgorithmInfo, MAC}; use bouncycastle_factory::mac_factory::MACFactory; use bouncycastle_hex as hex; @@ -32,6 +33,7 @@ mod hash_factory_tests { ]; for (name, strength) in table { let worker = MACFactory::new(name, &key).unwrap(); + let worker = worker.create_generator(); assert_eq!(worker.alg_name(), name); assert_eq!(worker.max_security_strength(), strength, "{name}"); } @@ -63,7 +65,7 @@ mod hash_factory_tests { let msg = hex::decode("2579f5df03e0fccde2b515944d88dc81ca3b4a20517cdc54170559f0d2f889e2f543eacf8a84b34563d0139351ea9a77399d274c5c6c1b0f488063b7255f9df648667fe800151ef288a68d6c8c24d57abd7e4f70eed149752beae4a9763cebf03c").unwrap(); let expected = hex::decode("6e927067f724d4fedc96b310c5115979e8dde8a4").unwrap(); let hmac = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); - assert_eq!(hmac.output_len(), 28); + assert_eq!(hmac.create_generator().output_len(), 28); assert_eq!(&hmac.mac(&msg)[..20], &expected[..]); let hmac = MACFactory::new(bouncycastle_sha2::hmac::HMAC_SHA512_224_NAME, &key).unwrap(); @@ -78,7 +80,7 @@ mod hash_factory_tests { let msg = hex::decode("").unwrap(); let expected = hex::decode("7857d4737760e127f1533185c6ad183ac4e10bd9").unwrap(); let hmac = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); - assert_eq!(hmac.output_len(), 32); + assert_eq!(hmac.create_generator().output_len(), 32); assert_eq!(&hmac.mac(&msg)[..20], &expected[..]); let hmac = MACFactory::new(bouncycastle_sha2::hmac::HMAC_SHA512_256_NAME, &key).unwrap(); @@ -98,7 +100,8 @@ mod hash_factory_tests { &hex::decode("6e927067f724d4fedc96b310c5115979e8dde8a4").unwrap()[..] ); - let mut hmac = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); for chunk in msg.chunks(7) { hmac.do_update(chunk); } @@ -112,7 +115,8 @@ mod hash_factory_tests { assert_eq!(out, full); let mut out = vec![0xffu8; 28]; - let mut hmac = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert_eq!(hmac.do_final_out(&mut out).unwrap(), 28); assert_eq!(out, full); @@ -121,10 +125,12 @@ mod hash_factory_tests { wrong[0] ^= 1; assert!(MACFactory::new("HMAC-SHA512/224", &key).unwrap().verify(&msg, &full)); assert!(!MACFactory::new("HMAC-SHA512/224", &key).unwrap().verify(&msg, &wrong)); - let mut hmac = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert!(hmac.do_verify_final(&full)); - let mut hmac = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/224", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert!(!hmac.do_verify_final(&wrong)); @@ -142,7 +148,8 @@ mod hash_factory_tests { &hex::decode("7857d4737760e127f1533185c6ad183ac4e10bd9").unwrap()[..] ); - let mut hmac = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); for chunk in msg.chunks(7) { hmac.do_update(chunk); } @@ -156,7 +163,8 @@ mod hash_factory_tests { assert_eq!(out, full); let mut out = vec![0xffu8; 32]; - let mut hmac = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert_eq!(hmac.do_final_out(&mut out).unwrap(), 32); assert_eq!(out, full); @@ -165,10 +173,12 @@ mod hash_factory_tests { wrong[0] ^= 1; assert!(MACFactory::new("HMAC-SHA512/256", &key).unwrap().verify(&msg, &full)); assert!(!MACFactory::new("HMAC-SHA512/256", &key).unwrap().verify(&msg, &wrong)); - let mut hmac = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert!(hmac.do_verify_final(&full)); - let mut hmac = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let hmac_key = MACFactory::new("HMAC-SHA512/256", &key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(&msg); assert!(!hmac.do_verify_final(&wrong)); @@ -186,7 +196,7 @@ mod hash_factory_tests { .unwrap(); for name in ["HMAC-SM3", bouncycastle_sm3::hmac::HMAC_SM3_NAME] { let hmac = MACFactory::new(name, &key).unwrap(); - assert_eq!(hmac.output_len(), 32); + assert_eq!(hmac.create_generator().output_len(), 32); assert!( hmac.verify( b"Hi There", diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index b8809f497..bed49ab0e 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -113,9 +113,10 @@ use bouncycastle_core::key_material::{ use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; use bouncycastle_core::traits::{Algorithm, KDF, MAC, Suspendable, SuspendableKeyed}; -use bouncycastle_hmac::{HMAC, HMACParams, HmacKey, LARGEST_HASHER_BLOCK_LEN}; +use bouncycastle_hmac::hazmat as hmac_hazmat; +use bouncycastle_hmac::{HMACParams, HmacKey, LARGEST_HASHER_BLOCK_LEN}; use bouncycastle_utils::secret::Secret; -use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, bounded_usize, check_lib_ver}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; // Imports needed only for docs @@ -159,14 +160,22 @@ pub const MAX_HMAC_OUTPUT_LEN: usize = 64; /// `SuspendableKeyed<{HASH_STATE_LEN + 14}>` over `H: Suspendable`. #[derive(Clone)] pub struct HKDF { - // Optional because an HMAC cannot be constructed until a key is provided - // to initialize it with. + // Optional because the extract-phase HMAC cannot be keyed until the salt is provided. // None must correspond to a state of Uninitialized. - hmac: Option>, + hmac: Option>, entropy: HkdfEntropyTracker, state: HkdfStates, } +/// The extract phase's HMAC, held as its two halves: the salt key and the inner hash absorbing +/// the IKM. An `HMAC` worker borrows its key, and a struct cannot borrow from itself, so the +/// phase is run through `bouncycastle_hmac::hazmat`'s `inner_state` and `finish_out` instead. +#[derive(Clone)] +struct HkdfExtract { + key: HmacKey, + inner: H, +} + // Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data. #[derive(Clone, Debug, PartialOrd, PartialEq)] @@ -217,7 +226,8 @@ impl HkdfEntropyTracker { /// Returns the amount of entropy credited. fn credit_entropy(&mut self, key: &impl KeyMaterialTrait) -> usize { let additional_entropy = if key.is_full_entropy() { key.key_len() } else { 0 }; - self.entropy += additional_entropy; + // Saturating: the counter is bookkeeping, and a resumed state may carry any value. + self.entropy = self.entropy.saturating_add(additional_entropy); self.security_strength = max(&self.security_strength, &key.security_strength()).clone(); self.security_strength = min(&self.security_strength, &SecurityStrength::from_bytes(H::OUTPUT_LEN / 2)).clone(); @@ -562,7 +572,9 @@ impl // The salt is often empty or short, so HMAC's key-strength check is skipped; the entropy // tracker below accounts for it. let salt_key = Self::salt_as_mac_key(salt)?; - self.hmac = Some(HmacKey::::from_key_material_allow_weak(&salt_key)?.create_generator()); + let key = HmacKey::::from_key_material_allow_weak(&salt_key)?; + let inner = hmac_hazmat::inner_state(&key); + self.hmac = Some(HkdfExtract { key, inner }); let additional_entropy = self.entropy.credit_entropy(salt); self.state = HkdfStates::Initialized; @@ -595,9 +607,7 @@ impl debug_assert!(self.hmac.is_some()); let additional_entropy = self.entropy.credit_entropy(ikm); - let hmac_ref: &mut HMAC = self.hmac.as_mut().unwrap(); - hmac_ref.do_update(ikm.ref_to_bytes()); - // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes()); + self.hmac.as_mut().unwrap().inner.do_update(ikm.ref_to_bytes()); Ok(additional_entropy) } @@ -619,7 +629,7 @@ impl }; self.state = HkdfStates::TakingAdditionalInfo; - self.hmac.as_mut().unwrap().do_update(ikm_chunk); + self.hmac.as_mut().unwrap().inner.do_update(ikm_chunk); Ok(0) } @@ -648,11 +658,9 @@ impl let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self. let mut bytes_written = 0; + let HkdfExtract { key, inner } = self.hmac.unwrap(); do_hazardous_operations(prk, |okm| { - bytes_written = self - .hmac - .unwrap() - .do_final_out(&mut okm.ref_to_bytes_mut()?) + bytes_written = hmac_hazmat::finish_out(&key, inner, okm.ref_to_bytes_mut()?) .map_err(|_| KeyMaterialError::GenericError("HMAC do_final_out failed"))?; okm.set_key_len(bytes_written)?; okm.set_key_type(output_key_type)?; @@ -794,8 +802,8 @@ impl KD /// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping /// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`, /// which is the relationship `HKDF_STATE_LEN == HASH_STATE_LEN + 14` asserted below. -impl SuspendableKeyed - for HKDF +impl<'k, H, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize> + SuspendableKeyed<'k, HKDF_STATE_LEN> for HKDF where H: HMACParams + Suspendable, { @@ -815,7 +823,7 @@ where // The present flag, then (when present) the inner salt-keyed HMAC blob right after it. if let Some(hmac) = self.hmac { state[3] = 1; // present flag - state[4..4 + HASH_STATE_LEN].copy_from_slice(&hmac.suspend()); + state[4..4 + HASH_STATE_LEN].copy_from_slice(&hmac.inner.suspend()); } // else None: // the presence flag = 0 @@ -832,7 +840,7 @@ where fn from_suspended( state: [u8; HKDF_STATE_LEN], - salt: &Self::Key, + salt: &'k Self::Key, ) -> Result { debug_assert_eq!(HKDF_STATE_LEN, HASH_STATE_LEN + 14); @@ -843,10 +851,11 @@ where let hmac = match state[3] { 0 => None, // infallible: the sub-slice is exactly HASH_STATE_LEN bytes by const construction. - 1 => Some(HMAC::::from_suspended( - state[4..4 + HASH_STATE_LEN].try_into().unwrap(), - salt, - )?), + 1 => Some(HkdfExtract { + key: HmacKey::::from_key_material_allow_weak(salt) + .map_err(|_| SuspendableError::InvalidData)?, + inner: H::from_suspended(state[4..4 + HASH_STATE_LEN].try_into().unwrap())?, + }), _ => return Err(SuspendableError::InvalidData), }; @@ -860,10 +869,12 @@ where return Err(SuspendableError::InvalidData); } - // infallible: the sub-slice is exactly 8 bytes by const construction. - let entropy = - u64::from_le_bytes(state[5 + HASH_STATE_LEN..13 + HASH_STATE_LEN].try_into().unwrap()) - as usize; + // infallible: the sub-slice is exactly 8 bytes by const construction. The counter is + // refused rather than truncated where `usize` is narrower than the encoding. + let entropy = bounded_usize( + u64::from_le_bytes(state[5 + HASH_STATE_LEN..13 + HASH_STATE_LEN].try_into().unwrap()), + usize::MAX, + )?; let security_strength = SecurityStrength::try_from(state[13 + HASH_STATE_LEN])?; Ok(HKDF { diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 7878d84e0..7fa4bb569 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -876,13 +876,14 @@ mod hkdf_tests { // A helper that exercises the full round-trip for one HKDF variant. A concrete `&KeyMaterial128` // works for `do_extract_init` (which wants a `Sized` `&impl KeyMaterialTrait`) and coerces to // `&dyn KeyMaterialTrait` for the serialization APIs. - fn round_trip( - salt: &KeyMaterial128, + fn round_trip<'k, const HASH_LEN: usize, const LEN: usize, H>( + salt: &'k KeyMaterial128, part1: &[u8], part2: &[u8], ) where H: HMACParams, - HKDF: Clone + SuspendableKeyed, + HKDF: + Clone + SuspendableKeyed<'k, LEN, Key = dyn KeyMaterialTrait + 'static>, { let hkdf = HKDF::::new(); @@ -960,6 +961,30 @@ mod hkdf_tests { )); } + /// The entropy counter is bookkeeping a resumed state may set to anything: the next credit + /// saturates rather than overflowing. + #[test] + fn a_resumed_entropy_counter_saturates() { + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_sha2::hkdf::SUSPENDED_HKDF_SHA256_STATE_LEN; + + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); + let full_entropy = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[16..32], KeyType::CryptographicRandom) + .unwrap(); + let mut hkdf = HKDF_SHA256::new(); + hkdf.do_extract_init(&salt).unwrap(); + let mut state = hkdf.suspend(); + // Layout: [version(3) | present flag | hmac blob | state | entropy(8) | strength]. + let entropy_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 9; + state[entropy_idx..entropy_idx + 8].copy_from_slice(&u64::MAX.to_le_bytes()); + + let mut resumed = HKDF_SHA256::from_suspended(state, &salt).unwrap(); + assert_eq!(resumed.get_entropy(), usize::MAX); + assert_eq!(resumed.do_extract_update_key(&full_entropy).unwrap(), 16); + assert_eq!(resumed.get_entropy(), usize::MAX); + } + /// RFC 5869 Sec 2.3: "OKM = first L octets of T", for any L up to 255 * HashLen, so a shorter /// output is a prefix of a longer one. Every L from 0 to 200 covers each value of L mod HashLen /// for both hashes: the last block used to be produced as an HMAC truncated to the remaining diff --git a/crypto/hmac/src/hazmat.rs b/crypto/hmac/src/hazmat.rs new file mode 100644 index 000000000..b58d3fa22 --- /dev/null +++ b/crypto/hmac/src/hazmat.rs @@ -0,0 +1,38 @@ +//! The two halves of an HMAC computation, for a construction that cannot hold an [`HMAC`] +//! worker: HKDF's extract phase owns its salt key and so cannot also hold a worker that borrows +//! it. See [`bouncycastle_core::hazmat`] for what the path means. +//! +//! # 🚨 Security Considerations 🚨 +//! +//! [`inner_state`] is `H(K XOR ipad)` with nothing absorbed, and [`finish_out`] finishes any +//! `HASH` value through the key's outer pad. Nothing checks that the hash handed to `finish_out` +//! came from `inner_state` of the same key, or that it was fed exactly the message: a caller that +//! composes the two wrongly computes something that is not HMAC over that message. Use the +//! [`HMAC`] worker from [`HmacKey::create_generator`] unless the worker's borrow is what stands +//! in the way. +//! +//! [`HMAC`]: crate::HMAC +//! [`HmacKey::create_generator`]: bouncycastle_core::symmetric_key::MacGenerationKey::create_generator + +use crate::{HMACParams, HmacKey}; +use bouncycastle_core::errors::MACError; + +/// `H` after absorbing `K XOR ipad`, ready for the text: the start of RFC 2104 Section 2 step +/// (4). What a worker's updates feed; [`finish_out`] is its other half. +pub fn inner_state(key: &HmacKey) -> HASH { + key.inner_state() +} + +/// RFC 2104 Section 2 steps (4), (6) and (7) over an inner hash that has absorbed the text: +/// finishes it, feeds the result to the key's outer pad state, and finishes that into `out`, +/// which is zeroized first. Returns the number of bytes written. +/// +/// # Errors +/// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_FIPS_DIGEST_LEN`](crate::MIN_FIPS_DIGEST_LEN). +pub fn finish_out( + key: &HmacKey, + inner: HASH, + out: &mut [u8], +) -> Result { + key.finish_out(inner, out) +} diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 942e20d01..9c7638859 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -3,10 +3,10 @@ //! //! This is a utility crate and is not intended to be used directly. It provides [`HmacKey`] -- the //! key object, which holds the two precomputed pad states RFC 2104 Section 4 describes and hands -//! out workers -- and [`HMAC`] -- the worker, one per message. Both are generic over any struct -//! that implements [`Hash`] and [`HMACParams`], the extension point through which a hash declares -//! the metadata from which the HMAC instance is built. The library provides the following concrete -//! instantiations of HMAC: +//! out workers -- and [`HMAC`] -- the worker, one per message, which borrows the key it came from. +//! Both are generic over any struct that implements [`Hash`] and [`HMACParams`], the extension +//! point through which a hash declares the metadata from which the HMAC instance is built. The +//! library provides the following concrete instantiations of HMAC: //! //! | Hash family | Instantiations | //! |-------------|------------------------------------------------------------------------------| @@ -42,10 +42,6 @@ //! assert!(key.verify(b"part onepart two", &tag[..n])); // one-shot, same path underneath //! ``` //! -//! [`MAC::new`] and [`MAC::new_allow_weak_key`] on the worker build the same key internally and -//! take one worker from it, so they cost the pad computation per message; a key that will -//! authenticate more than one message is better held as an [`HmacKey`]. -//! //! # Instantiating HMAC over a Hash //! //! HMAC works with any hash: [`HMAC`](HMAC) needs only [`Hash`]. What HMAC cannot @@ -65,15 +61,15 @@ //! 3. Publish a type alias for the instantiation, `HMAC`. //! 4. Optionally publish the suspended-state length as a constant. [`SuspendableKeyed`] is //! implemented automatically for any hash that implements [`Suspendable`], and HMAC's suspended -//! state is exactly the inner hash's -- the key is deliberately excluded -- so the constant is -//! just an alias for the hash's own. +//! state is exactly the inner hash's -- the key is deliberately excluded, and resume takes the +//! [`HmacKey`] again -- so the constant is just an alias for the hash's own. //! //! ## Worked example //! //! As an example, the `bouncycastle-sha2` crate follows exactly the recipe above; its entry for SHA-256 reduces to: //! //! ```rust,ignore -//! pub type HMAC_SHA256 = HMAC; +//! pub type HMAC_SHA256<'k> = HMAC<'k, SHA256>; //! //! impl HMACParams for SHA256 { //! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; @@ -96,23 +92,25 @@ //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * [`HMACParams::HMAC_MAX_SECURITY_STRENGTH`] is a claim that [`MAC::new`] enforces against the -//! key's tagged strength, and that [`HMAC::keygen_from_rng`] enforces against the RNG's. Declaring -//! a strength the underlying hash cannot support does not make the construction stronger, it just -//! makes the check wrong. NIST SP 800-107-r1 Section 5.3.4 gives the ceiling: the effective -//! strength is `min(strength of K, 2C)` for an internal chaining value of `C` bits. -//! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols -//! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be -//! used to silence an error from [`MAC::new`]. +//! * [`HMACParams::HMAC_MAX_SECURITY_STRENGTH`] is a claim that +//! [`FromAnyKeyMaterial::from_key_material`] enforces against the key's tagged strength, and +//! that [`HMAC::keygen_from_rng`] enforces against the RNG's. Declaring a strength the +//! underlying hash cannot support does not make the construction stronger, it just makes the +//! check wrong. NIST SP 800-107-r1 Section 5.3.4 gives the ceiling: the effective strength is +//! `min(strength of K, 2C)` for an internal chaining value of `C` bits. +//! * [`FromAnyKeyMaterial::from_key_material_allow_weak`] deliberately skips the key-strength +//! check. It exists for protocols that call for a weak or all-zero key -- an all-zero HKDF salt, +//! for example -- and should not be used to silence an error from +//! [`FromAnyKeyMaterial::from_key_material`]. //! * Verification via [`MAC::verify`] / [`MAC::do_verify_final`] uses a constant-time comparison. //! Recomputing the MAC and comparing it with `==` leaks how many leading bytes matched. //! * [`MIN_FIPS_DIGEST_LEN`] (4 bytes) is the shortest truncation this crate will produce, per //! FIPS 140-2 IG A.8 / NIST SP 800-107-r1 Section 5.3.3. It is a floor, not a recommendation: //! RFC 2104 Section 5 recommends that the output length "be not less than half the length of the //! hash output ... and not less than 80 bits". -//! * The key is deliberately excluded from the suspended state and must be re-supplied on resume. -//! Resuming with the wrong key cannot be detected and silently produces a wrong MAC, computed with -//! different keys in the inner and outer pad. +//! * The key is deliberately excluded from the suspended state and the [`HmacKey`] must be +//! re-supplied on resume. Resuming with the wrong key cannot be detected and silently produces a +//! wrong MAC, computed with different keys in the inner and outer pad. //! * The two pad states are key-equivalent, as RFC 2104 Section 4 says ("the stored intermediate //! values need to be treated and protected the same as secret keys"). [`HmacKey`] and the worker //! hold them as hash states, which the hash crates zeroize on drop; neither has a `Debug` that @@ -124,6 +122,8 @@ #![forbid(unsafe_code)] #![forbid(missing_docs)] +pub mod hazmat; + use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, SuspendableError}; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; @@ -175,12 +175,12 @@ pub trait HMACParams: HashOneShot + HashAlgParams + Clone { const HMAC_OID_DER: &'static [u8]; } -impl Algorithm for HMAC { +impl Algorithm for HMAC<'_, HASH> { const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::HMAC_MAX_SECURITY_STRENGTH; } -impl AlgorithmOID for HMAC { +impl AlgorithmOID for HMAC<'_, HASH> { const OID: &'static [u32] = HASH::HMAC_OID; const OID_DER: &'static [u8] = HASH::HMAC_OID_DER; } @@ -203,10 +203,10 @@ const LARGEST_HASHER_OUTPUT_LEN: usize = 64; /// An HMAC key: the two intermediate hash states of RFC 2104 Section 4, `H` after absorbing /// `K XOR ipad` and after absorbing `K XOR opad`, computed once from key material of any length. /// -/// Every worker the key hands out starts from clones of the two states, so the per-message cost -/// of the key is a clone and no hashing. The key holds no key bytes: a caller who needs the -/// material again keeps the `KeyMaterial`. Construction is [`FromAnyKeyMaterial`]; the workers -/// come from [`MacGenerationKey`] and [`MacVerificationKey`]. +/// Every worker the key hands out starts from a clone of the inner state and borrows the key for +/// the outer one, so the per-message cost of the key is one clone and no hashing. The key holds +/// no key bytes: a caller who needs the material again keeps the `KeyMaterial`. Construction is +/// [`FromAnyKeyMaterial`]; the workers come from [`MacGenerationKey`] and [`MacVerificationKey`]. pub struct HmacKey { /// `H` after absorbing `K XOR ipad`: the start of RFC 2104 Section 2 step (4). inner: HASH, @@ -299,7 +299,8 @@ impl FromAnyKeyMaterial for HmacKey { } /// Draws a key of the hash's output length (RFC 2104 Section 3's recommended minimum) from - /// `rng`, tagged [`KeyType::MACKey`] at the RNG's strength, and builds the key from it. + /// `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped at what that length can + /// carry, and builds the key from it. /// /// # Errors /// [`MACError::KeyMaterialError`] if the RNG fails, or with @@ -312,48 +313,87 @@ impl FromAnyKeyMaterial for HmacKey { .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; key.set_key_len(output_len)?; key.set_key_type(KeyType::MACKey)?; - key.set_security_strength(rng.security_strength()) + // The RNG's strength, capped at what the length carries, as KeyMaterial::from_rng + // does: a 28-byte HMAC-SHA224 key from a 256-bit DRBG is a 192-bit key. + let by_len = SecurityStrength::from_bits(output_len * 8); + let by_rng = rng.security_strength(); + key.set_security_strength(if by_rng < by_len { by_rng } else { by_len }) })?; Self::from_key_material(&key) } } +impl HmacKey { + /// `H` after absorbing `K XOR ipad`, ready for the text: the start of RFC 2104 Section 2 + /// step (4). What a worker's updates feed; `finish_out` is its other half. The pair is + /// reachable outside the crate only through [`hazmat`]. + pub(crate) fn inner_state(&self) -> HASH { + self.inner.clone() + } + + /// RFC 2104 Section 2 steps (4), (6) and (7) over an inner hash that has absorbed the text: + /// finishes it, feeds the result to the outer pad state, and finishes that into `out`, which + /// is zeroized first. Returns the number of bytes written. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_FIPS_DIGEST_LEN`]. + pub(crate) fn finish_out(&self, inner: HASH, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_FIPS_DIGEST_LEN { + return Err(MACError::InvalidLength( + "HMAC truncation too short for FIPS 140-2 guidelines", + )); + } + let mut ihash = vec![0u8; inner.output_len()]; + inner.do_final_out(&mut ihash); // step (4) + let mut outer = self.outer.clone(); + outer.do_update(&ihash); // step (6) + Ok(outer.do_final_out(out)) // step (7) + } +} + impl MacGenerationKey for HmacKey { - type Generator = HMAC; + type Generator<'k> + = HMAC<'k, HASH> + where + Self: 'k; - fn create_generator(&self) -> Self::Generator { - HMAC { hasher: self.inner.clone(), outer: self.outer.clone() } + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + HMAC { hasher: self.inner.clone(), key: self } } } impl MacVerificationKey for HmacKey { - type Verifier = HMAC; + type Verifier<'k> + = HMAC<'k, HASH> + where + Self: 'k; - fn create_verifier(&self) -> Self::Verifier { + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { self.create_generator() } } /*** Worker ***/ -/// The HMAC worker for one message: the inner hash, absorbing the message, and the outer pad state -/// it is finished through. Obtained from an [`HmacKey`], or built with its own key for one message -/// by [`MAC::new`]. +/// The HMAC worker for one message: the inner hash, absorbing the message, and the key it was made +/// from, borrowed for `'k`, whose outer pad state it is finished through. Obtained from an +/// [`HmacKey`]. #[derive(Clone)] -pub struct HMAC { +pub struct HMAC<'k, HASH: HMACParams> { /// RFC 2104 Section 2 steps (3)-(4): `H(K XOR ipad, text)` in progress. hasher: HASH, - /// `H` after absorbing `K XOR opad`, for step (7). - outer: HASH, + /// The key, for `H` after absorbing `K XOR opad` at step (7). + key: &'k HmacKey, } -impl Debug for HMAC { +impl Debug for HMAC<'_, HASH> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "HMAC-{} instance", HASH::ALG_NAME,) } } -impl Display for HMAC { +impl Display for HMAC<'_, HASH> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "HMAC-{} instance", HASH::ALG_NAME,) } @@ -375,40 +415,14 @@ const OPAD_BYTE: u8 = 0x5C; /// = 32 bits / 8 = 4 bytes; pub const MIN_FIPS_DIGEST_LEN: usize = 4; -impl HMAC { - /// RFC 2104 Section 2 steps (4), (6) and (7): finish the inner hash, feed its result to the - /// outer pad state, and finish that into `out`. +impl HMAC<'_, HASH> { + /// RFC 2104 Section 2 steps (4), (6) and (7), through the key: [`HmacKey::finish_out`]. fn do_final_internal_out(self, out: &mut [u8]) -> Result { - if out.len() < MIN_FIPS_DIGEST_LEN { - return Err(MACError::InvalidLength( - "HMAC truncation too short for FIPS 140-2 guidelines", - )); - } - out.fill(0); - - let Self { hasher, mut outer } = self; - // The inner digest is key-derived state, so it is held in a Secret and wiped on drop. - let output_len = hasher.output_len(); - assert!( - output_len <= LARGEST_HASHER_OUTPUT_LEN, - "Fatal error: hash output length exceeds HMAC's inner-digest scratch" - ); - let mut ihash: Secret<[u8; LARGEST_HASHER_OUTPUT_LEN]> = Secret::new(); - hasher.do_final_out(&mut ihash[..output_len]); // step (4) - outer.do_update(&ihash[..output_len]); // step (6) - Ok(outer.do_final_out(out)) // step (7) + self.key.finish_out(self.hasher, out) } } -impl MAC for HMAC { - fn new(key: &impl KeyMaterialTrait) -> Result { - Ok(HmacKey::::from_key_material_checked(key, false)?.create_generator()) - } - - fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - Ok(HmacKey::::from_key_material_checked(key, true)?.create_generator()) - } - +impl MAC for HMAC<'_, HASH> { fn output_len(&self) -> usize { self.hasher.output_len() } @@ -459,14 +473,14 @@ impl MAC for HMAC { /// Suspend / resume for `HMAC`. /// -/// The suspended state is exactly the inner hash's: the outer pad state is a function of the key -/// alone and is rebuilt from the re-supplied key on resume, so the state carries one secret fewer. -/// The key is *not* authenticated against the state: resuming with the wrong key cannot be -/// detected and produces a wrong MAC. -impl> - SuspendableKeyed for HMAC +/// The suspended state is exactly the inner hash's: the outer pad state is the key's, which the +/// worker borrows, so resume takes the [`HmacKey`] the worker was made from and the state carries +/// one secret fewer. The key is *not* authenticated against the state: resuming with the wrong key +/// cannot be detected and produces a wrong MAC. +impl<'k, const HASH_STATE_LEN: usize, HASH: HMACParams + Suspendable> + SuspendableKeyed<'k, HASH_STATE_LEN> for HMAC<'k, HASH> { - type Key = dyn KeyMaterialTrait; + type Key = HmacKey; fn suspend(self) -> [u8; HASH_STATE_LEN] { self.hasher.suspend() @@ -474,17 +488,15 @@ impl fn from_suspended( state: [u8; HASH_STATE_LEN], - key: &Self::Key, + key: &'k Self::Key, ) -> Result { - let hasher = HASH::from_suspended(state)?; - let HmacKey { outer, .. } = HmacKey::::from_key_bytes(key.ref_to_bytes()); - Ok(HMAC { hasher, outer }) + Ok(HMAC { hasher: HASH::from_suspended(state)?, key }) } } /* KeyGen functions */ -impl HMAC { +impl HMAC<'_, HASH> { /// Generates a fresh key for this HMAC from `rng`: [`HMACParams::MACKey`] bytes (the hash's /// output length), tagged [`KeyType::MACKey`]. [`FromAnyKeyMaterial::generate`] on /// [`HmacKey`] draws the same key and builds the key object from it in one step. diff --git a/crypto/hmac/tests/hmac_key_tests.rs b/crypto/hmac/tests/hmac_key_tests.rs index 8497cd1b6..d30cbd02f 100644 --- a/crypto/hmac/tests/hmac_key_tests.rs +++ b/crypto/hmac/tests/hmac_key_tests.rs @@ -1,11 +1,11 @@ //! `HmacKey`: the key object holding RFC 2104 Section 4's two pad states, and the workers it hands -//! out producing exactly what `MAC::new` produces from the same material. +//! out producing exactly what RFC 2104's formula gives from the same material. use bouncycastle_core::errors::{KeyMaterialError, MACError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; -use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +use bouncycastle_core::traits::{MAC, RNG, SuspendableKeyed}; use bouncycastle_hmac::{HMAC, HMACParams, HmacKey}; use bouncycastle_rng::FixedSeedRNG; use bouncycastle_sha2::{SHA256, SHA512, SUSPENDED_SHA256_STATE_LEN}; @@ -18,15 +18,30 @@ fn mac_key(fill: u8) -> KeyMaterial { const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; #[test] -fn the_key_and_the_worker_constructor_agree_for_every_key_shape() { +fn the_key_follows_rfc_2104_for_every_key_shape() { // Shorter than the block, exactly the block, and longer than the block (pre-hashed, RFC 2104 - // Section 3), for a 64-byte-block hash and a 136-byte-block one. - fn check(key_bytes: &[u8]) - where - H: HMACParams, - { + // Section 3), for a 64-byte-block hash and a 136-byte-block one, against RFC 2104 Section 2 + // written out over the bare hash. + fn reference(key_bytes: &[u8], msg: &[u8]) -> Vec { + let block_len = H::default().block_bitlen() / 8; + let mut k = vec![0u8; block_len]; + if key_bytes.len() > block_len { + let hashed = H::hash(key_bytes); + k[..hashed.len()].copy_from_slice(&hashed); + } else { + k[..key_bytes.len()].copy_from_slice(key_bytes); + } + let mut inner = H::default(); + inner.do_update(&k.iter().map(|b| b ^ 0x36).collect::>()); + inner.do_update(msg); + let mut outer = H::default(); + outer.do_update(&k.iter().map(|b| b ^ 0x5c).collect::>()); + outer.do_update(&inner.do_final()); + outer.do_final() + } + fn check(key_bytes: &[u8]) { let km = KeyMaterial::<200>::from_bytes_as_type(key_bytes, KeyType::MACKey).unwrap(); - let expected = HMAC::::new_allow_weak_key(&km).unwrap().mac(MSG); + let expected = reference::(key_bytes, MSG); let key = HmacKey::::from_key_material_allow_weak(&km).unwrap(); assert_eq!(key.mac(MSG), expected); let mut generator = key.create_generator(); @@ -57,10 +72,7 @@ fn several_workers_from_one_key_are_independent_and_the_key_is_unchanged() { let na = a.do_final_out(&mut tag_a).unwrap(); assert_eq!((na, nb), (32, 32)); assert_ne!(tag_a, tag_b); - assert_eq!( - tag_a.to_vec(), - HMAC::::new(&mac_key::<32>(0x0b)).unwrap().mac(b"message one") - ); + assert_eq!(tag_a.to_vec(), key.mac(b"message one")); let mut v = key.create_verifier(); v.do_update(b"message one"); @@ -112,10 +124,14 @@ fn construction_applies_the_key_type_and_strength_policy() { )); assert!(HmacKey::::from_key_material_allow_weak(&weak).is_ok()); - // An empty (zeroized) key, HKDF's absent salt, is admitted by the weak form. + // An empty (zeroized) key, HKDF's absent salt, is admitted by the weak form and is the + // all-zero key of RFC 2104 step (1): the zero-length-key vector from `hmac_tests.rs`. let no_salt = KeyMaterial256::new(); let salt_key = HmacKey::::from_key_material_allow_weak(&no_salt).unwrap(); - assert_eq!(salt_key.mac(MSG), HMAC::::new_allow_weak_key(&no_salt).unwrap().mac(MSG)); + assert_eq!( + salt_key.mac(b"Hi There"), + b"\xe4\x84\x11\x26\x27\x15\xc8\x37\x0c\xd5\xe7\xbf\x8e\x82\xbe\xf5\x3b\xd5\x37\x12\xd0\x07\xf3\x42\x93\x51\x84\x3b\x77\xc7\xbb\x9b" + ); } #[test] @@ -136,6 +152,22 @@ fn generate_draws_a_key_of_the_hash_output_length_at_the_rng_strength() { km.set_key_type(KeyType::MACKey).unwrap(); assert_eq!(HmacKey::::from_key_material(&km).unwrap().mac(MSG), tag); + // The drawn material's strength is the RNG's, capped at what the hash's output length can + // carry, by the rule `KeyMaterial::from_rng` applies: a key object has no strength accessor, + // so the rule is pinned on the material. 32 bytes from a 256-bit RNG keep 256 bits; 28 + // bytes, HMAC-SHA224's draw, carry 224 bits, which the strength scale rounds down to 192. + let mut rng3 = FixedSeedRNG::new([0x5Au8; 32]); + assert_eq!(rng3.security_strength(), SecurityStrength::_256bit); + assert_eq!( + KeyMaterial::<32>::from_rng(&mut rng3).unwrap().security_strength(), + SecurityStrength::_256bit + ); + let mut rng4 = FixedSeedRNG::new([0x5Au8; 28]); + assert_eq!( + KeyMaterial::<28>::from_rng(&mut rng4).unwrap().security_strength(), + SecurityStrength::_192bit + ); + // An RNG below the HMAC's strength is refused, through the material's strength tag. let mut weak_rng = FixedSeedRNG::new([0x5Au8; 32]); weak_rng.set_security_strength(SecurityStrength::_128bit); @@ -147,13 +179,13 @@ fn generate_draws_a_key_of_the_hash_output_length_at_the_rng_strength() { } #[test] -fn a_worker_from_the_key_suspends_and_resumes_like_one_from_the_constructor() { +fn a_worker_from_the_key_suspends_and_resumes() { let km = mac_key::<32>(0x0b); let key = HmacKey::::from_key_material(&km).unwrap(); let mut w = key.create_generator(); w.do_update(&MSG[..7]); let state: [u8; SUSPENDED_SHA256_STATE_LEN] = w.suspend(); - let mut resumed = HMAC::::from_suspended(state, &km as &dyn KeyMaterialTrait).unwrap(); + let mut resumed = HMAC::::from_suspended(state, &key).unwrap(); resumed.do_update(&MSG[7..]); assert_eq!(resumed.do_final(), key.mac(MSG)); } diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 481b0e42f..d80dfd495 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -6,8 +6,12 @@ mod hmac_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; use bouncycastle_core::traits::{Algorithm, Hash, MAC}; use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; use bouncycastle_hmac::*; @@ -30,7 +34,9 @@ mod hmac_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - let mut mac = HMAC::::new_allow_weak_key(&zero_length_key).unwrap(); + let mac_key = HmacKey::::from_key_material_allow_weak(&zero_length_key).unwrap(); + + let mut mac = mac_key.create_generator(); mac.do_update("Hi There".as_bytes()); let output = mac.do_final(); assert_eq!(output, b"\xe4\x84\x11\x26\x27\x15\xc8\x37\x0c\xd5\xe7\xbf\x8e\x82\xbe\xf5\x3b\xd5\x37\x12\xd0\x07\xf3\x42\x93\x51\x84\x3b\x77\xc7\xbb\x9b"); @@ -41,7 +47,8 @@ mod hmac_tests { KeyType::MACKey, ) .unwrap(); - let mut mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); let output = mac.do_final(); assert_eq!(output, b"\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68\x4b\x22"); @@ -52,7 +59,8 @@ mod hmac_tests { KeyType::MACKey, ) .unwrap(); - let mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mac = mac_key.create_generator(); // mac.do_update(b"").unwrap(); let output = mac.do_final(); assert_eq!( @@ -68,38 +76,33 @@ mod hmac_tests { b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", KeyType::MACKey).unwrap(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA224::new(&key).unwrap(); + let _: HMAC_SHA224 = HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA256::new(&key).unwrap(); + let _: HMAC_SHA256 = HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA384::new(&key).unwrap(); + let _: HMAC_SHA384 = HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512::new(&key).unwrap(); + let _: HMAC_SHA512 = HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_224::new(&key).unwrap(); + let _: HMAC_SHA512_224 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_256::new(&key).unwrap(); + let _: HMAC_SHA512_256 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_224::new(&key).unwrap(); + let _: HMAC_SHA3_224 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_256::new(&key).unwrap(); + let _: HMAC_SHA3_256 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_384::new(&key).unwrap(); + let _: HMAC_SHA3_384 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_512::new(&key).unwrap(); + let _: HMAC_SHA3_512 = + HmacKey::::from_key_material(&key).unwrap().create_generator(); - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SM3::new(&key).unwrap(); + let _: HMAC_SM3 = HmacKey::::from_key_material(&key).unwrap().create_generator(); } #[test] @@ -111,15 +114,15 @@ mod hmac_tests { .unwrap(); assert_eq!(short_key.security_strength(), SecurityStrength::_112bit); // key is too short, so it is expected to fail - match HMAC::::new(&short_key) { + match HmacKey::::from_key_material(&short_key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* good */ } _ => panic!( "This should have thrown a KeyMaterialError::SecurityStrength error but it didn't" ), } - // It works after allowing weak keys - HMAC::::new_allow_weak_key(&short_key).unwrap(); + // It works through the weak form + HmacKey::::from_key_material_allow_weak(&short_key).unwrap(); // It works with a long enough key let key = KeyMaterial256::from_bytes_as_type( @@ -127,7 +130,7 @@ mod hmac_tests { KeyType::MACKey, ) .unwrap(); - HMAC::::new(&key).unwrap(); + HmacKey::::from_key_material(&key).unwrap(); } #[test] @@ -139,7 +142,7 @@ mod hmac_tests { ) .unwrap(); assert!( - HMAC_SHA256::new(&key).unwrap().verify( + HmacKey::::from_key_material(&key).unwrap().verify( b"Hi There", &hex::decode("4da0a0bb56f010db147b8f6e5f2dbecf7bb35ff00c8b9da31c9b94cc81815873") .unwrap() @@ -152,7 +155,7 @@ mod hmac_tests { KeyType::MACKey, ) .unwrap(); - HMAC_SHA256::new(&key).unwrap().verify( + HmacKey::::from_key_material(&key).unwrap().verify( b"Hi There", &hex::decode("5cb9475aba606afe8c82c2d1b3e1cfb1c814e8a72ce5a7b4fe43b0a0aac45144") .unwrap(), @@ -167,17 +170,19 @@ mod hmac_tests { // SHA-512 has a 128-byte block, so use a 127-byte key let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap(); - let mut mac = HMAC_SHA512::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); let tag = mac.do_final(); - assert!(HMAC_SHA512::new(&key).unwrap().verify(b"Hi There", &tag)); + assert!(HmacKey::::from_key_material(&key).unwrap().verify(b"Hi There", &tag)); // SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range. let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap(); - let mut mac = HMAC_SHA3_224::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); let tag = mac.do_final(); - assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag)); + assert!(HmacKey::::from_key_material(&key).unwrap().verify(b"Hi There", &tag)); } #[test] @@ -185,14 +190,14 @@ mod hmac_tests { // Test: provided key has the correct length, but insufficient tagged security strength // HMAC should still work, but should return an error - // it works with a zero key (as new_allow_weak_key) + // it works with a zero key (through the weak form) // zero-len key let mut zero_key = KeyMaterial256::default(); - HMAC_SHA256::new_allow_weak_key(&zero_key).unwrap(); + HmacKey::::from_key_material_allow_weak(&zero_key).unwrap(); // non-zero len key of all-zero bytes do_hazardous_operations(&mut zero_key, |zero_key| zero_key.set_key_len(32)).unwrap(); - HMAC_SHA256::new_allow_weak_key(&zero_key).unwrap(); + HmacKey::::from_key_material_allow_weak(&zero_key).unwrap(); // Note: zero-len keys that are not Zeroized or MACKey are not allowed @@ -202,7 +207,7 @@ mod hmac_tests { assert_eq!(key.security_strength(), SecurityStrength::_256bit); key.set_security_strength(SecurityStrength::_128bit).unwrap(); // The call should fail, as the key's security strength is set below the required threshold - match HMAC::::new(&key) { + match HmacKey::::from_key_material(&key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* fine */ } _ => { panic!( @@ -210,18 +215,30 @@ mod hmac_tests { ) } } - // It passes after setting .allow_weak_keys() - let mut hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + // It passes through the weak form + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + let mut hmac = hmac_key.create_generator(); hmac.do_update(b"Hi There"); hmac.do_final(); // one-shot APIs still work with a weak key - let out = HMAC::::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + let out = HmacKey::::from_key_material_allow_weak(&key).unwrap().mac(b"Hi There"); + assert!( + HmacKey::::from_key_material_allow_weak(&key) + .unwrap() + .verify(b"Hi There", &out) + ); // likewise with pre-allocated buffers let mut out = [0u8; 64]; - HMAC::::new_allow_weak_key(&key).unwrap().mac_out(b"Hi There", &mut out).unwrap(); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + HmacKey::::from_key_material_allow_weak(&key) + .unwrap() + .mac_out(b"Hi There", &mut out) + .unwrap(); + assert!( + HmacKey::::from_key_material_allow_weak(&key) + .unwrap() + .verify(b"Hi There", &out) + ); } #[test] @@ -233,23 +250,30 @@ mod hmac_tests { .unwrap(); // get the known-good output - let out = HMAC::::new(&key).unwrap().mac(b"Hi There"); + let out = HmacKey::::from_key_material(&key).unwrap().mac(b"Hi There"); // test output that's the wrong length, should simply return False - let mut mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&out[..out.len() - 1])); // test output that's the right length but wrong value -- do_verify - let mut mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&[0x01_u8; 28])); // test output that's the right length but wrong value -- static verify - assert!(!HMAC_SHA224::new(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28])); + assert!( + !HmacKey::::from_key_material(&key) + .unwrap() + .verify(b"Hi There", &[0x01_u8; 28]) + ); // error case: test that it'll refuse to truncate below MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN - 1]; match mac.do_final_out(&mut out) { @@ -263,7 +287,8 @@ mod hmac_tests { } // success case: ... but it will truncate to exactly MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN]; let bytes_written = mac.do_final_out(&mut out).unwrap(); @@ -271,17 +296,21 @@ mod hmac_tests { assert_eq!(&out, b"\x89\x6f\xb1\x12"); // fail case: mac value is correct but truncated - let mac = HMAC_SHA3_224::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mac = mac_key.create_generator(); let mut mac_val = mac.mac(b"Polly want a cracker?"); - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier_key = HmacKey::::from_key_material(&key).unwrap(); + let verifier = verifier_key.create_generator(); assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); // truncation of the mac value is considered a fail - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier_key = HmacKey::::from_key_material(&key).unwrap(); + let verifier = verifier_key.create_generator(); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); // .. as is some extra bytes at the end - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier_key = HmacKey::::from_key_material(&key).unwrap(); + let verifier = verifier_key.create_generator(); mac_val.extend_from_slice(&[0u8; 4]); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); } @@ -325,11 +354,12 @@ mod hmac_tests { .unwrap(); let msg = hex::decode("2579f5df03e0fccde2b515944d88dc81ca3b4a20517cdc54170559f0d2f889e2f543eacf8a84b34563d0139351ea9a77399d274c5c6c1b0f488063b7255f9df648667fe800151ef288a68d6c8c24d57abd7e4f70eed149752beae4a9763cebf03c").unwrap(); let expected = hex::decode("6e927067f724d4fedc96b310c5115979e8dde8a4").unwrap(); - let full = HMAC_SHA512_224::new(&key).unwrap().mac(&msg); + let full = HmacKey::::from_key_material(&key).unwrap().mac(&msg); assert_eq!(full.len(), 28); assert_eq!(&full[..20], &expected[..]); // the same vector through the streaming API in uneven chunks - let mut mac = HMAC_SHA512_224::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); for chunk in msg.chunks(13) { mac.do_update(chunk); } @@ -343,11 +373,12 @@ mod hmac_tests { .unwrap(); let msg = hex::decode("dedb0cc1c2a9b960d3").unwrap(); let expected = hex::decode("9cf6def15b5ead939e1fda675b52147a01a6ccb6").unwrap(); - let full = HMAC_SHA512_224::new(&key).unwrap().mac(&msg); + let full = HmacKey::::from_key_material(&key).unwrap().mac(&msg); assert_eq!(full.len(), 28); assert_eq!(&full[..20], &expected[..]); // the same vector through the streaming API in uneven chunks - let mut mac = HMAC_SHA512_224::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); for chunk in msg.chunks(13) { mac.do_update(chunk); } @@ -369,11 +400,12 @@ mod hmac_tests { .unwrap(); let msg = hex::decode("").unwrap(); let expected = hex::decode("7857d4737760e127f1533185c6ad183ac4e10bd9").unwrap(); - let full = HMAC_SHA512_256::new(&key).unwrap().mac(&msg); + let full = HmacKey::::from_key_material(&key).unwrap().mac(&msg); assert_eq!(full.len(), 32); assert_eq!(&full[..20], &expected[..]); // the same vector through the streaming API in uneven chunks - let mut mac = HMAC_SHA512_256::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); for chunk in msg.chunks(13) { mac.do_update(chunk); } @@ -388,11 +420,12 @@ mod hmac_tests { let msg = hex::decode("9268f10c36fd3366012e841260e60227a968f6c8546dee6abc83b3").unwrap(); let expected = hex::decode("3288232187dcf1ea421f5c12bdeb4fd9d0a0a25b").unwrap(); - let full = HMAC_SHA512_256::new(&key).unwrap().mac(&msg); + let full = HmacKey::::from_key_material(&key).unwrap().mac(&msg); assert_eq!(full.len(), 32); assert_eq!(&full[..20], &expected[..]); // the same vector through the streaming API in uneven chunks - let mut mac = HMAC_SHA512_256::new(&key).unwrap(); + let mac_key = HmacKey::::from_key_material(&key).unwrap(); + let mut mac = mac_key.create_generator(); for chunk in msg.chunks(13) { mac.do_update(chunk); } @@ -400,6 +433,14 @@ mod hmac_tests { } } + /// What `TestFrameworkMAC::test_mac` ran per vector: the key built with the weak form (RFC 4231 + /// keys are below the HMAC's strength), its workers against the vector, and the key policy. + fn test_hmac(key: &impl KeyMaterialTrait, input: &[u8], expected: &[u8]) { + let hmac_key = HmacKey::::from_key_material_allow_weak(key).unwrap(); + TestFrameworkMAC::new().test_mac_key(&hmac_key, input, expected); + TestFrameworkFromAnyKeyMaterial::new().test::>(key); + } + #[cfg(test)] mod core_test_framework_rfc4231 { use super::*; @@ -407,7 +448,6 @@ mod hmac_tests { #[test] fn hmac_sha224() { - let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); do_hazardous_operations(&mut zero_length_key, |zero_length_key| { zero_length_key.set_key_type(KeyType::MACKey) @@ -416,15 +456,14 @@ mod hmac_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_hmac::( &zero_length_key, b"Hello, world", &hex::decode("57454372e6a8780b11150274d7056c6fbcffef902f9c23f24fbbfee9").unwrap(), ); // RFC4231 Test Case 1 - let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -435,7 +474,7 @@ mod hmac_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44").unwrap(), @@ -443,14 +482,14 @@ mod hmac_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap(), ); @@ -462,13 +501,16 @@ mod hmac_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HmacKey::::from_key_material(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("0e2aea68a90c8d37c988bcdb9fca6fa8").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). let key = KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(); - test_framework.test_mac::>( + test_hmac::( &key, b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e").unwrap(), @@ -476,7 +518,7 @@ mod hmac_tests { // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap(), ); @@ -485,7 +527,6 @@ mod hmac_tests { #[test] fn hmac_sha256() { // test with zero-length key - let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); do_hazardous_operations(&mut zero_length_key, |zero_length_key| { zero_length_key.set_key_type(KeyType::MACKey) @@ -494,7 +535,7 @@ mod hmac_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_hmac::( &zero_length_key, b"Hello, world", &hex::decode("c0fa4c55880318c31c1020e7a2cf830c2c695716387795c7a0eb918ba84e4bf0") @@ -502,8 +543,7 @@ mod hmac_tests { ); // RFC4231 Test Case 1 - let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -515,7 +555,7 @@ mod hmac_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843") @@ -524,14 +564,14 @@ mod hmac_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap(), ); @@ -543,19 +583,22 @@ mod hmac_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HmacKey::::from_key_material(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("a3b6167473100ee06e0c796c2955552b").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap(), ); @@ -564,7 +607,6 @@ mod hmac_tests { #[test] fn hmac_sha384() { // test with zero-length key - let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); do_hazardous_operations(&mut zero_length_key, |zero_length_key| { zero_length_key.set_key_type(KeyType::MACKey) @@ -573,15 +615,14 @@ mod hmac_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_hmac::( &zero_length_key, b"Hello, world", &hex::decode("fbd41442f749049355175277afbaff610539e5bfa874c9cf86ef867a43a30b09a5eac6578d5c0cb1ceddc95f97598af7").unwrap(), ); // RFC4231 Test Case 1 - let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -591,7 +632,7 @@ mod hmac_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649").unwrap(), @@ -599,14 +640,14 @@ mod hmac_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap(), ); @@ -618,21 +659,22 @@ mod hmac_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - // Key is shorter than HMAC security strength, so it needs to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + // Key is shorter than HMAC security strength, so it needs the weak form + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + let hmac = hmac_key.create_generator(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("3abf34c3503b2a23a46efc619baef897").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap(), ); @@ -641,7 +683,6 @@ mod hmac_tests { #[test] fn hmac_sha512() { // test with zero-length key - let test_framework = TestFrameworkMAC::new(); let mut zero_length_key = KeyMaterial256::default(); do_hazardous_operations(&mut zero_length_key, |zero_length_key| { zero_length_key.set_key_type(KeyType::MACKey) @@ -650,15 +691,14 @@ mod hmac_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_hmac::( &zero_length_key, b"Hello, world", &hex::decode("e8f7176e01bf9bb883f71f42c143681e86cfafe0b61f3bc0d824e2cde13b5f80199e82d865aebb725461c86a54086aeacac37a86a9f1cf07db567ba5a10f1cc1").unwrap(), ); // RFC4231 Test Case 1 - let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -669,7 +709,7 @@ mod hmac_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_hmac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737").unwrap(), @@ -677,14 +717,14 @@ mod hmac_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap(), ); @@ -696,21 +736,22 @@ mod hmac_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - // Key is shorter than HMAC security strength, so need to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + // Key is shorter than HMAC security strength, so it needs the weak form + let hmac_key = HmacKey::::from_key_material_allow_weak(&key).unwrap(); + let hmac = hmac_key.create_generator(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("415fad6271580a531d4179bc891d87a6").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_hmac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap(), ); @@ -724,10 +765,9 @@ mod hmac_tests { #[test] fn hmac_sm3_known_answers() { use bouncycastle_core::key_material::KeyMaterial; - let test_framework = TestFrameworkMAC::new(); // RFC4231 Test Case 1 key/message - test_framework.test_mac::( + test_hmac::( &KeyMaterial::<20>::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -738,24 +778,25 @@ mod hmac_tests { .unwrap(), ); // RFC4231 Test Case 2 key/message - test_framework.test_mac::( + test_hmac::( &KeyMaterial::<4>::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("2e87f1d16862e6d964b50a5200bf2b10b764faa9680a296a2405f24bec39f882") .unwrap(), ); // RFC4231 Test Case 6 key/message: key larger than the 64-byte block, so it is hashed first - test_framework.test_mac::( + test_hmac::( &KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0") .unwrap(), ); - // zero-length key (weak; needs new_allow_weak_key) + // zero-length key (weak; needs the weak form) let mut zero_length_key = KeyMaterial256::default(); do_hazardous_operations(&mut zero_length_key, |k| k.set_key_type(KeyType::MACKey)).unwrap(); - let mut mac = HMAC_SM3::new_allow_weak_key(&zero_length_key).unwrap(); + let mac_key = HmacKey::::from_key_material_allow_weak(&zero_length_key).unwrap(); + let mut mac = mac_key.create_generator(); mac.do_update(b"abc"); assert_eq!( mac.do_final(), @@ -764,10 +805,11 @@ mod hmac_tests { ); assert_eq!( - HMAC_SM3::new( + HmacKey::::from_key_material( &KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap() ) .unwrap() + .create_generator() .output_len(), 32 ); @@ -784,17 +826,14 @@ mod hmac_tests { let msg = b"Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the - // key is NOT in the serialized state -- it is re-supplied (by reference) to - // from_serialized_state. - // The `+ 'static` on the trait object matches the associated type `type Key = dyn - // KeyMaterialTrait` (a bare `dyn` in an associated type defaults to `'static`). The concrete - // key types are owned, so they satisfy it. - fn round_trip( + // key is NOT in the serialized state -- the key object is re-supplied (by reference) to + // from_suspended. + fn round_trip<'k, const N: usize, H>( mut hmac: H, - key: &(dyn KeyMaterialTrait + 'static), + key: &'k >::Key, input: &[u8], ) where - H: MAC + Clone + SuspendableKeyed, + H: MAC + Clone + SuspendableKeyed<'k, N>, { hmac.do_update(&input[..10]); @@ -826,17 +865,24 @@ mod hmac_tests { } } - round_trip(HMAC_SHA256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA512_224::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA512_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SM3::new(&key).unwrap(), &key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); // test suspend / resume with a key larger than block size let long_key = KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED[..200], KeyType::MACKey).unwrap(); - round_trip(HMAC_SHA256::new(&long_key).unwrap(), &long_key, msg); + let hmac_key = HmacKey::::from_key_material(&long_key).unwrap(); + round_trip(hmac_key.create_generator(), &hmac_key, msg); } /// Tests that no private data is displayed @@ -847,7 +893,8 @@ mod hmac_tests { KeyType::MACKey, ) .unwrap(); - let hmac = HMAC_SHA256::new(&key).unwrap(); + let hmac_key = HmacKey::::from_key_material(&key).unwrap(); + let hmac = hmac_key.create_generator(); // test fmt let fmt_str = format!("{}", &hmac); @@ -860,12 +907,12 @@ mod hmac_tests { /// Exercises the `keygen_from_rng()` function of each HMAC type alias: /// * the generated key must not be the all-zero array, /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that - /// * `HMAC::new(&key)` accepts the freshly generated key, without error. + /// * `HmacKey::from_key_material(&key)` accepts the freshly generated key, without error. /// /// HashDRBG_SHA512 is used throughout because it is the only built-in DRBG that meets the /// 256-bit strength that HMAC-SHA512 and HMAC-SHA3-512 claim; see `keygen_rejects_weak_rng`. macro_rules! keygen_test { - ($test_name:ident, $hmac:ident, $n:literal) => { + ($test_name:ident, $hmac:ident, $hash:ident, $n:literal) => { #[test] fn $test_name() { let mut rng = HashDRBG_SHA512::new_from_os(); @@ -878,22 +925,23 @@ mod hmac_tests { "keygen produced an all-zero key" ); - $hmac::new(&key).expect("HMAC::new should accept a freshly generated key"); + HmacKey::<$hash>::from_key_material(&key) + .expect("HmacKey should accept a freshly generated key"); } }; } - keygen_test!(keygen_hmac_sha224, HMAC_SHA224, 28); - keygen_test!(keygen_hmac_sha256, HMAC_SHA256, 32); - keygen_test!(keygen_hmac_sha384, HMAC_SHA384, 48); - keygen_test!(keygen_hmac_sha512, HMAC_SHA512, 64); - keygen_test!(keygen_hmac_sha512_224, HMAC_SHA512_224, 28); - keygen_test!(keygen_hmac_sha512_256, HMAC_SHA512_256, 32); - keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, 28); - keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, 32); - keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, 48); - keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, 64); - keygen_test!(keygen_hmac_sm3, HMAC_SM3, 32); + keygen_test!(keygen_hmac_sha224, HMAC_SHA224, SHA224, 28); + keygen_test!(keygen_hmac_sha256, HMAC_SHA256, SHA256, 32); + keygen_test!(keygen_hmac_sha384, HMAC_SHA384, SHA384, 48); + keygen_test!(keygen_hmac_sha512, HMAC_SHA512, SHA512, 64); + keygen_test!(keygen_hmac_sha512_224, HMAC_SHA512_224, SHA512_224, 28); + keygen_test!(keygen_hmac_sha512_256, HMAC_SHA512_256, SHA512_256, 32); + keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, SHA3_224, 28); + keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, SHA3_256, 32); + keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, SHA3_384, 48); + keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, SHA3_512, 64); + keygen_test!(keygen_hmac_sm3, HMAC_SM3, SM3, 32); /// `keygen_from_rng` must refuse an RNG whose security strength is below the strength the HMAC /// claims, otherwise the returned key would be tagged stronger than the entropy behind it. diff --git a/crypto/hmac/tests/hmac_wycheproof.rs b/crypto/hmac/tests/hmac_wycheproof.rs index 05d4bcf34..c0327a17e 100644 --- a/crypto/hmac/tests/hmac_wycheproof.rs +++ b/crypto/hmac/tests/hmac_wycheproof.rs @@ -8,23 +8,24 @@ //! suites. //! //! Each file has a full-length-tag group and a truncated-tag group (half the output length). A -//! full-length tag is checked through `verify`. A truncated tag is checked through `mac_out` into a -//! buffer of the tag's length, and `verify` must reject it: it requires the full output length. +//! full-length tag is checked through the key's `verify`. A truncated tag is checked through +//! `mac_out` into a buffer of the tag's length, and `verify` must reject it: it requires the full +//! output length. use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::MAC; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; -use bouncycastle_sha2::hmac::{ - HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, HMAC_SHA512, HMAC_SHA512_224, HMAC_SHA512_256, -}; -use bouncycastle_sha3::hmac::{HMAC_SHA3_224, HMAC_SHA3_256, HMAC_SHA3_384, HMAC_SHA3_512}; -use bouncycastle_sm3::hmac::HMAC_SM3; +use bouncycastle_hmac::{HMACParams, HmacKey}; +use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; +use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; +use bouncycastle_sm3::SM3; /// The longest key in any of the files is 65 bytes. const MAX_KEY_LEN: usize = 128; -/// Runs every case in one `hmac_*_test.json` file through `M`. -fn run(filename: &str, algorithm: &str) { +/// Runs every case in one `hmac_*_test.json` file through `HmacKey`. +fn run(filename: &str, algorithm: &str) { let Some(doc) = wycheproof_json(filename) else { return }; assert_eq!(doc.get("algorithm").and_then(Value::as_str), Some(algorithm), "{filename}"); @@ -44,25 +45,25 @@ fn run(filename: &str, algorithm: &str) { }; // Some keys are shorter than the hash's security strength (128-bit keys for // HMAC-SHA-512, say); the key-strength policy is tested in `hmac_tests.rs`. - let key = KeyMaterial::::from_bytes_as_type( + let material = KeyMaterial::::from_bytes_as_type( &hex_field(test, "key", tc_id), KeyType::MACKey, ) .expect("a MAC key"); - let mac = || M::new_allow_weak_key(&key).expect("an HMAC instance"); + let key = HmacKey::::from_key_material_allow_weak(&material).expect("an HMAC key"); let ctx = format!("{filename} tcId {tc_id}"); - if tag_len == mac().output_len() { - assert_eq!(mac().verify(&msg, &tag), valid, "{ctx}: verify"); + if tag_len == key.create_generator().output_len() { + assert_eq!(key.verify(&msg, &tag), valid, "{ctx}: verify"); if valid { - assert_eq!(mac().mac(&msg), tag, "{ctx}: mac"); + assert_eq!(key.mac(&msg), tag, "{ctx}: mac"); } full += 1; } else { let mut out = vec![0u8; tag_len]; - assert_eq!(mac().mac_out(&msg, &mut out).expect("mac_out"), tag_len, "{ctx}"); + assert_eq!(key.mac_out(&msg, &mut out).expect("mac_out"), tag_len, "{ctx}"); assert_eq!(out == tag, valid, "{ctx}: truncated mac_out"); - assert!(!mac().verify(&msg, &tag), "{ctx}: verify takes only a full-length tag"); + assert!(!key.verify(&msg, &tag), "{ctx}: verify takes only a full-length tag"); truncated += 1; } invalid += usize::from(!valid); @@ -79,55 +80,55 @@ fn run(filename: &str, algorithm: &str) { #[test] fn wycheproof_hmac_sha224() { - run::("hmac_sha224_test.json", "HMACSHA224"); + run::("hmac_sha224_test.json", "HMACSHA224"); } #[test] fn wycheproof_hmac_sha256() { - run::("hmac_sha256_test.json", "HMACSHA256"); + run::("hmac_sha256_test.json", "HMACSHA256"); } #[test] fn wycheproof_hmac_sha384() { - run::("hmac_sha384_test.json", "HMACSHA384"); + run::("hmac_sha384_test.json", "HMACSHA384"); } #[test] fn wycheproof_hmac_sha512() { - run::("hmac_sha512_test.json", "HMACSHA512"); + run::("hmac_sha512_test.json", "HMACSHA512"); } #[test] fn wycheproof_hmac_sha512_224() { - run::("hmac_sha512_224_test.json", "HMACSHA512/224"); + run::("hmac_sha512_224_test.json", "HMACSHA512/224"); } #[test] fn wycheproof_hmac_sha512_256() { - run::("hmac_sha512_256_test.json", "HMACSHA512/256"); + run::("hmac_sha512_256_test.json", "HMACSHA512/256"); } #[test] fn wycheproof_hmac_sha3_224() { - run::("hmac_sha3_224_test.json", "HMACSHA3-224"); + run::("hmac_sha3_224_test.json", "HMACSHA3-224"); } #[test] fn wycheproof_hmac_sha3_256() { - run::("hmac_sha3_256_test.json", "HMACSHA3-256"); + run::("hmac_sha3_256_test.json", "HMACSHA3-256"); } #[test] fn wycheproof_hmac_sha3_384() { - run::("hmac_sha3_384_test.json", "HMACSHA3-384"); + run::("hmac_sha3_384_test.json", "HMACSHA3-384"); } #[test] fn wycheproof_hmac_sha3_512() { - run::("hmac_sha3_512_test.json", "HMACSHA3-512"); + run::("hmac_sha3_512_test.json", "HMACSHA3-512"); } #[test] fn wycheproof_hmac_sm3() { - run::("hmac_sm3_test.json", "HMACSM3"); + run::("hmac_sm3_test.json", "HMACSM3"); } diff --git a/crypto/sha2/benches/hmac_sha2_benches.rs b/crypto/sha2/benches/hmac_sha2_benches.rs index 263c73520..5b526dd0b 100644 --- a/crypto/sha2/benches/hmac_sha2_benches.rs +++ b/crypto/sha2/benches/hmac_sha2_benches.rs @@ -1,7 +1,9 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; -use bouncycastle_core::traits::{MAC, RNG}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::RNG; +use bouncycastle_hmac::HmacKey; use bouncycastle_rng as rng; -use bouncycastle_sha2::hmac::{HMAC_SHA256, HMAC_SHA512}; +use bouncycastle_sha2::{SHA256, SHA512}; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; @@ -15,13 +17,14 @@ fn bench_hmac_sha256(c: &mut Criterion) { } let hmac_key = KeyMaterial256::from_bytes_as_type(&data_block[..32], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&hmac_key).unwrap(); let mut out = [0u8; 32]; - let mut group = c.benchmark_group("hmac::HMAC_SHA256::mac_out() -- 16x1024 one-shot"); + let mut group = c.benchmark_group("hmac::HmacKey::mac_out() -- 16x1024 one-shot"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes", big_data.len() as u64), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len() as u64), |b| { b.iter(|| { - HMAC_SHA256::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + key.mac_out(black_box(&big_data), &mut out).unwrap(); black_box(&out); }) }); @@ -38,13 +41,14 @@ fn bench_hmac_sha512(c: &mut Criterion) { } let hmac_key = KeyMaterial512::from_bytes_as_type(&data_block[..64], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&hmac_key).unwrap(); let mut out = [0u8; 64]; - let mut group = c.benchmark_group("hmac::HMAC_SHA512::mac_out() -- 16x1024 one-shot"); + let mut group = c.benchmark_group("hmac::HmacKey::mac_out() -- 16x1024 one-shot"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes", big_data.len() as u64), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len() as u64), |b| { b.iter(|| { - HMAC_SHA512::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + key.mac_out(black_box(&big_data), &mut out).unwrap(); black_box(&out); }) }); diff --git a/crypto/sha2/src/hkdf.rs b/crypto/sha2/src/hkdf.rs index 47b4629c9..f8a2589a0 100644 --- a/crypto/sha2/src/hkdf.rs +++ b/crypto/sha2/src/hkdf.rs @@ -186,9 +186,9 @@ //! //! | Object | Size (bytes) | //! |---------------------------------------------------|--------------| -//! | `HKDF_SHA256` | 256 | -//! | `HKDF_SHA384` | 448 | -//! | `HKDF_SHA512` | 448 | +//! | `HKDF_SHA256` | 368 | +//! | `HKDF_SHA384` | 656 | +//! | `HKDF_SHA512` | 656 | //! | Suspended `HKDF_SHA256` state | 122 | //! | Suspended `HKDF_SHA384` state | 218 | //! | Suspended `HKDF_SHA512` state | 218 | @@ -213,8 +213,8 @@ //! does not create entropy: asking for 200 bytes of output from a 128-bit seed yields 200 bytes at //! a 128-bit security level, not a 1600-bit key. //! * RFC 5869 Section 3.1 recommends a random salt where one is available; SP 800-56Cr2 permits an -//! all-zero salt. An all-zero salt is not a [`KeyType::MACKey`], so it needs -//! `MAC::new_allow_weak_key` semantics -- which is exactly what the extract phase does internally. +//! all-zero salt. An all-zero salt is below any HMAC's strength, so it needs the HMAC key's +//! `from_key_material_allow_weak` -- which is exactly what the extract phase does internally. use crate::hmac::{ SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA384_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN, diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index c20309299..451ab92b8 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -10,98 +10,88 @@ //! rather than as part of the library's public API. //! //! A key of any length is accepted: per RFC 2104, a key longer than the hash's block is pre-hashed -//! to the output length. The key's two pad states are computed once, when an -//! [`HmacKey`](bouncycastle_hmac::HmacKey) is built or a worker is constructed from key material. +//! to the output length. The key's two pad states are computed once, when the [`HmacKey`] is built. //! //! # Usage //! -//! An HMAC object (and the [`MAC`] trait in general) is used in three phases: +//! An [`HmacKey`] is built once from key material and hands out [`MAC`] workers, one per message: //! -//! * The initialization phase where you specify the underlying hash function and the key material. -//! * The update phase where you feed in the content being MAC'd, either in one-shot or in chunks. -//! * The finalization phase where you either obtain the MAC value or verify an existing MAC value. -//! -//! The initialization phase is primarily performed via the [`MAC::new`] function which performs -//! checks on the provided key to ensure that it is of the correct type [`KeyType::MACKey`] and tagged -//! at the correct security level for the chosen hash function. In cases where you need to use HMAC -//! with an intentionally weak key (such as an all-zero salt), the alternative constructor -//! [`MAC::new_allow_weak_key`] can be used. -//! -//! The update phase supports streaming of the content via the repeated calls to the [`MAC::do_update`] function. -//! One-shot APIs are provided that combine the update and finalization phases into a single function call. +//! * [`FromAnyKeyMaterial::from_key_material`] checks that the material is a [`KeyType::MACKey`] +//! tagged at the security strength of the chosen hash. Where a protocol calls for a weak key +//! (an all-zero salt, say), [`FromAnyKeyMaterial::from_key_material_allow_weak`] skips the +//! strength check. +//! * [`MacGenerationKey::create_generator`] / [`MacVerificationKey::create_verifier`] start a +//! worker, whose [`MAC::do_update`] takes the content in chunks and whose [`MAC::do_final`] / +//! [`MAC::do_verify_final`] finish it. The key's own [`MacGenerationKey::mac`] and +//! [`MacVerificationKey::verify`] are the one-shots. //! //! # Usage Examples //! -//! ## Constructing an HMAC object +//! ## Building the key //! -//! Instantiation of an HMAC object is straightforward. A key of the right length for the chosen hash -//! can be generated with [`HMAC::keygen_from_rng`]: +//! A key of the right length for the chosen hash is drawn with [`FromAnyKeyMaterial::generate`]: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::HashDRBG_SHA256; -//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! use bouncycastle_sha2::SHA256; //! //! let mut rng = HashDRBG_SHA256::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); -//! -//! let hmac = HMAC_SHA256::new(&key).expect( -//! "Should succeed because key is long enough and tagged KeyType::MACKey"); //! ``` //! -//! Alternatively, if you have key material from somewhere else, you can create the key manually, -//! like so: +//! Alternatively, if you have key material from somewhere else, you can build the key from it: //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha2::SHA256; //! -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); //! -//! let hmac = HMAC_SHA256::new(&key).expect( +//! let key = HmacKey::::from_key_material(&key_material).expect( //! "Should succeed because key is long enough and tagged KeyType::MACKey"); //! ``` //! //! ## Computing a MAC //! -//! MAC functionality is accessed via the [`MAC`] trait. -//! -//! The simplest usage is via the one-shot functions. +//! The simplest usage is the key's one-shot: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::HashDRBG_SHA256; -//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! use bouncycastle_sha2::SHA256; //! //! let mut rng = HashDRBG_SHA256::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! //! let data: &[u8] = b"Hello, world!"; -//! let hmac = HMAC_SHA256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey"); -//! let output: Vec = hmac.mac(data); +//! let output: Vec = key.mac(data); //! ``` //! -//! More advanced usage will require creating an HMAC object to hold state between successive calls, -//! for example if input is received in chunks and not all available at the same time: +//! A worker holds state between successive calls, for example if input is received in chunks and +//! not all available at the same time: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; //! use bouncycastle_core::traits::MAC; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_sha2::SHA256; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! //! let mut rng = HashDRBG_SHA256::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! -//! let mut hmac = HMAC_SHA256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey"); +//! let mut hmac: HMAC_SHA256 = key.create_generator(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); //! let output: Vec = hmac.do_final(); @@ -109,31 +99,30 @@ //! //! ## Verifying a MAC //! -//! The [`MAC`] trait also provides functions for MAC verification. The built-in verification -//! functions use constant-time comparisons and so are *strongly recommended* rather than -//! re-computing the MAC value and comparing it yourself. +//! The built-in verification functions use constant-time comparisons and so are *strongly +//! recommended* rather than re-computing the MAC value and comparing it yourself. //! -//! The simplest usage is via the one-shot functions. +//! The simplest usage is the key's one-shot: //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacVerificationKey}; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha2::SHA256; //! //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); +//! let key = HmacKey::::from_key_material(&key_material).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; //! //! // .verify() returns a bool: true if the MAC is valid, false otherwise. -//! if HMAC_SHA256::new(&key).unwrap() -//! .verify(data, -//! b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90 -//! \x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" -//! ) +//! if key.verify(data, +//! b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90 +//! \x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9") //! { //! println!("MAC is valid!"); //! } else { @@ -146,15 +135,18 @@ //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacVerificationKey}; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha2::SHA256; //! //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); -//! let mut hmac = HMAC_SHA256::new(&key).unwrap(); +//! let key = HmacKey::::from_key_material(&key_material).unwrap(); +//! let mut hmac = key.create_verifier(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); //! if hmac.do_verify_final(b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90\x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" @@ -181,7 +173,10 @@ //! //! ```rust //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; //! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha2::SHA256; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! //! let msg_part1 = b"The quick brown fox"; @@ -191,7 +186,8 @@ //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); //! -//! let mut hmac = HMAC_SHA256::new(&key).unwrap(); +//! let key = HmacKey::::from_key_material(&key).unwrap(); +//! let mut hmac = key.create_generator(); //! hmac.do_update(msg_part1); //! //! // suspend the in-progress mac (the key is NOT included in the serialized state) @@ -202,7 +198,7 @@ //! // ... //! //! // ... later, possibly on another host: resume from the serialized state by re-supplying -//! // the same key (make sure you store it securely!). +//! // the same key object (make sure you store the material securely!). //! let mut hmac_resumed = HMAC_SHA256::from_suspended(serialized_state, &key).unwrap(); //! hmac_resumed.do_update(msg_part2); //! let h: Vec = hmac_resumed.do_final(); @@ -215,23 +211,26 @@ //! //! | Object | Size (bytes) | //! |-----------------------------------------------------------|--------------| -//! | `HMAC_SHA224`, `HMAC_SHA256`, and their `HmacKey` | 224 | -//! | `HMAC_SHA384`, `HMAC_SHA512`, and their `HmacKey` | 416 | +//! | `HmacKey` for `HMAC_SHA224`, `HMAC_SHA256` | 224 | +//! | `HmacKey` for `HMAC_SHA384`, `HMAC_SHA512` | 416 | +//! | `HMAC_SHA224`, `HMAC_SHA256` worker | 120 | +//! | `HMAC_SHA384`, `HMAC_SHA512` worker | 216 | //! | Suspended `HMAC_SHA224`/`HMAC_SHA256` state | 108 | //! | Suspended `HMAC_SHA384`/`HMAC_SHA512` state | 204 | //! -//! The worker and the key are each two of the underlying hash object (see the crate-level Memory -//! Usage section): the inner hash and the outer pad state, 2 x 112 = 224 for the SHA-256 family, -//! 2 x 208 = 416 for the SHA-512 family. The suspended state is exactly the inner hash's suspended -//! state -- the key is deliberately excluded -- so it matches the corresponding row for the bare -//! hash. +//! The key is two of the underlying hash object (see the crate-level Memory Usage section): the +//! inner and the outer pad state, 2 x 112 = 224 for the SHA-256 family, 2 x 208 = 416 for the +//! SHA-512 family. The worker is the inner hash plus a reference to the key it borrows. The +//! suspended state is exactly the inner hash's suspended state -- the key is deliberately +//! excluded -- so it matches the corresponding row for the bare hash. //! //! # 🚨 Security Considerations 🚨 //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol (an all-zero HKDF salt, for example), not to -//! silence an error. +//! * The key must carry at least the security strength claimed by the HMAC, and +//! [`FromAnyKeyMaterial::from_key_material`] enforces that. +//! [`FromAnyKeyMaterial::from_key_material_allow_weak`] deliberately skips the check; use it only +//! where a weak or all-zero key is called for by the protocol (an all-zero HKDF salt, for +//! example), not to silence an error. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -254,9 +253,11 @@ use bouncycastle_hmac::{HMAC, HMACParams}; #[allow(unused_imports)] use bouncycastle_core::key_material::KeyType; #[allow(unused_imports)] +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +#[allow(unused_imports)] use bouncycastle_core::traits::{MAC, SuspendableKeyed}; #[allow(unused_imports)] -use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; +use bouncycastle_hmac::{HmacKey, MIN_FIPS_DIGEST_LEN}; /*** String constants ***/ /// Algorithm name string for HMAC over SHA-224. @@ -275,7 +276,7 @@ pub const HMAC_SHA512_256_NAME: &str = "HMAC-SHA512/256"; /*** Type aliases ***/ /// Public type for HMAC using SHA224. #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC; +pub type HMAC_SHA224<'k> = HMAC<'k, SHA224>; impl HMACParams for SHA224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA224_NAME; @@ -289,7 +290,7 @@ impl HMACParams for SHA224 { /// Public type for HMAC using SHA256. #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC; +pub type HMAC_SHA256<'k> = HMAC<'k, SHA256>; impl HMACParams for SHA256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA256_NAME; @@ -303,7 +304,7 @@ impl HMACParams for SHA256 { /// Public type for HMAC using SHA384. #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC; +pub type HMAC_SHA384<'k> = HMAC<'k, SHA384>; impl HMACParams for SHA384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA384_NAME; @@ -317,7 +318,7 @@ impl HMACParams for SHA384 { /// Public type for HMAC using SHA512. #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC; +pub type HMAC_SHA512<'k> = HMAC<'k, SHA512>; impl HMACParams for SHA512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_NAME; @@ -331,7 +332,7 @@ impl HMACParams for SHA512 { /// Public type for HMAC using SHA512/224. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_224 = HMAC; +pub type HMAC_SHA512_224<'k> = HMAC<'k, SHA512_224>; impl HMACParams for SHA512_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_224_NAME; @@ -345,7 +346,7 @@ impl HMACParams for SHA512_224 { /// Public type for HMAC using SHA512/256. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_256 = HMAC; +pub type HMAC_SHA512_256<'k> = HMAC<'k, SHA512_256>; impl HMACParams for SHA512_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_256_NAME; diff --git a/crypto/sha3/benches/hmac_sha3_benches.rs b/crypto/sha3/benches/hmac_sha3_benches.rs index badb2b7cf..c99f0f251 100644 --- a/crypto/sha3/benches/hmac_sha3_benches.rs +++ b/crypto/sha3/benches/hmac_sha3_benches.rs @@ -1,7 +1,9 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; -use bouncycastle_core::traits::{MAC, RNG}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::RNG; +use bouncycastle_hmac::HmacKey; use bouncycastle_rng as rng; -use bouncycastle_sha3::hmac::{HMAC_SHA3_256, HMAC_SHA3_512}; +use bouncycastle_sha3::{SHA3_256, SHA3_512}; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; @@ -15,13 +17,14 @@ fn bench_hmac_sha3_256(c: &mut Criterion) { } let hmac_key = KeyMaterial256::from_bytes_as_type(&data_block[..32], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&hmac_key).unwrap(); let mut out = [0u8; 32]; - let mut group = c.benchmark_group("hmac::HMAC_SHA256::mac_out() -- 16x1024 one-shot"); + let mut group = c.benchmark_group("hmac::HmacKey::mac_out() -- 16x1024 one-shot"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes -- ::hashes()", big_data.len() as u64), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len() as u64), |b| { b.iter(|| { - HMAC_SHA3_256::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + key.mac_out(black_box(&big_data), &mut out).unwrap(); black_box(&out); }) }); @@ -38,13 +41,14 @@ fn bench_hmac_sha3_512(c: &mut Criterion) { } let hmac_key = KeyMaterial512::from_bytes_as_type(&data_block[..64], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&hmac_key).unwrap(); let mut out = [0u8; 64]; - let mut group = c.benchmark_group("hmac::HMAC_SHA512::mac_out() -- 16x1024 one-shot"); + let mut group = c.benchmark_group("hmac::HmacKey::mac_out() -- 16x1024 one-shot"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes -- ::hashes()", big_data.len() as u64), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len() as u64), |b| { b.iter(|| { - HMAC_SHA3_512::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + key.mac_out(black_box(&big_data), &mut out).unwrap(); black_box(&out); }) }); diff --git a/crypto/sha3/benches/sha3_benches.rs b/crypto/sha3/benches/sha3_benches.rs index 51514e166..a12c2e4e9 100644 --- a/crypto/sha3/benches/sha3_benches.rs +++ b/crypto/sha3/benches/sha3_benches.rs @@ -3,8 +3,9 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::{Hash, HashOneShot, MAC, RNG, XOF}; -use bouncycastle_sha3::kmac::KMAC128; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::{Hash, HashOneShot, RNG, XOF}; +use bouncycastle_sha3::kmac::KMAC128Key; use bouncycastle_sha3::parallelhash::ParallelHash128; use bouncycastle_sha3::tuplehash::TupleHash128; use bouncycastle_sha3::{CSHAKE128, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256}; @@ -234,12 +235,13 @@ fn bench_cshake128(c: &mut Criterion) { fn bench_kmac128(c: &mut Criterion) { let big_data = big_data(); let key = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); + let key = KMAC128Key::from_key_material(&key).unwrap(); let mut tag = [0u8; 32]; - let mut group = c.benchmark_group("sha3::KMAC128"); + let mut group = c.benchmark_group("sha3::KMAC128Key"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes -- .mac_out()", big_data.len()), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len()), |b| { b.iter(|| { - KMAC128::new(&key).unwrap().mac_out(black_box(&big_data), &mut tag).unwrap(); + key.mac_out(black_box(&big_data), &mut tag).unwrap(); black_box(&tag); }) }); diff --git a/crypto/sha3/src/cshake.rs b/crypto/sha3/src/cshake.rs index fb1667372..f8063c08e 100644 --- a/crypto/sha3/src/cshake.rs +++ b/crypto/sha3/src/cshake.rs @@ -200,7 +200,7 @@ impl CSHAKEInternal { } } -impl SuspendableComponent for CSHAKEInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for CSHAKEInternal { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN; type Key = (); @@ -208,7 +208,7 @@ impl SuspendableComponent for CSHAKEInternal { self.write_tagged(PARAMS::CSHAKE_STATE_TAG, out) } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { Self::read_tagged(state, PARAMS::CSHAKE_STATE_TAG) } } @@ -453,7 +453,7 @@ impl CSHAKESqueezer { /// Both phases suspend. The sponge's own phase flag records which, so the state is the cSHAKE /// layout under one tag, and a resumed `Unbound` squeezer still has its first read to make. -impl SuspendableComponent for CSHAKESqueezer { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for CSHAKESqueezer { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN; type Key = (); @@ -473,7 +473,7 @@ impl SuspendableComponent for CSHAKESqueezer { } } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let (family, rest) = state.split_at(SHA3_FAMILY_STATE_LEN); let shake = SHAKEInternal::::read_family_state(family, PARAMS::CSHAKE_SQUEEZER_STATE_TAG)?; diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 2eccce4a9..7ec3bdf0f 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -12,98 +12,88 @@ //! A key of any length is accepted: per RFC 2104, a key longer than the hash's block -- for SHA-3 //! the sponge *rate*, FIPS 202 Table 3's "Input block sizes for HMAC", which shrinks as the output //! size grows -- is pre-hashed to the output length. The key's two pad states are computed once, -//! when an [`HmacKey`](bouncycastle_hmac::HmacKey) is built or a worker is constructed from key -//! material. +//! when the [`HmacKey`] is built. //! //! # Usage //! -//! An HMAC object (and the [`MAC`] trait in general) is used in three phases: +//! An [`HmacKey`] is built once from key material and hands out [`MAC`] workers, one per message: //! -//! * The initialization phase where you specify the underlying hash function and the key material. -//! * The update phase where you feed in the content being MAC'd, either in one-shot or in chunks. -//! * The finalization phase where you either obtain the MAC value or verify an existing MAC value. -//! -//! The initialization phase is primarily performed via the [`MAC::new`] function which performs -//! checks on the provided key to ensure that it is of the correct type [`KeyType::MACKey`] and tagged -//! at the correct security level for the chosen hash function. In cases where you need to use HMAC -//! with an intentially week key (such as an all-zero salt), the alternative constructor -//! [`MAC::new_allow_weak_key`] can be used. -//! -//! The update phase supports streaming of the content via the repeated calls to the [`MAC::do_update`] function. -//! One-shot APIs are provided that combine the update and finalization phases into a single function call. +//! * [`FromAnyKeyMaterial::from_key_material`] checks that the material is a [`KeyType::MACKey`] +//! tagged at the security strength of the chosen hash. Where a protocol calls for a weak key +//! (an all-zero salt, say), [`FromAnyKeyMaterial::from_key_material_allow_weak`] skips the +//! strength check. +//! * [`MacGenerationKey::create_generator`] / [`MacVerificationKey::create_verifier`] start a +//! worker, whose [`MAC::do_update`] takes the content in chunks and whose [`MAC::do_final`] / +//! [`MAC::do_verify_final`] finish it. The key's own [`MacGenerationKey::mac`] and +//! [`MacVerificationKey::verify`] are the one-shots. //! //! # Usage Examples //! -//! ## Constructing an HMAC object +//! ## Building the key //! -//! Instantiation of an HMAC object is straightforward. A key of the right length for the chosen hash -//! can be generated with [`HMAC::keygen_from_rng`]: +//! A key of the right length for the chosen hash is drawn with [`FromAnyKeyMaterial::generate`]: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::DefaultRNG; -//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! use bouncycastle_sha3::SHA3_256; //! //! let mut rng = DefaultRNG::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA3_256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); -//! -//! let hmac = HMAC_SHA3_256::new(&key).expect( -//! "Should succeed because key is long enough and tagged KeyType::MACKey"); //! ``` //! -//! Alternatively, if you have key material from somewhere else, you can create the key manually, -//! like so: +//! Alternatively, if you have key material from somewhere else, you can build the key from it: //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha3::SHA3_256; //! -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); //! -//! let hmac = HMAC_SHA3_256::new(&key).expect( +//! let key = HmacKey::::from_key_material(&key_material).expect( //! "Should succeed because key is long enough and tagged KeyType::MACKey"); //! ``` //! //! ## Computing a MAC //! -//! MAC functionality is accessed via the [`MAC`] trait. -//! -//! The simplest usage is via the one-shot functions. +//! The simplest usage is the key's one-shot: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::DefaultRNG; -//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! use bouncycastle_sha3::SHA3_256; //! //! let mut rng = DefaultRNG::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA3_256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! //! let data: &[u8] = b"Hello, world!"; -//! let hmac = HMAC_SHA3_256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey"); -//! let output: Vec = hmac.mac(data); +//! let output: Vec = key.mac(data); //! ``` //! -//! More advanced usage will require creating an HMAC object to hold state between successive calls, -//! for example if input is received in chunks and not all available at the same time: +//! A worker holds state between successive calls, for example if input is received in chunks and +//! not all available at the same time: //! //! ``` -//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; //! use bouncycastle_core::traits::MAC; +//! use bouncycastle_hmac::HmacKey; //! use bouncycastle_rng::DefaultRNG; +//! use bouncycastle_sha3::SHA3_256; //! use bouncycastle_sha3::hmac::HMAC_SHA3_256; //! //! let mut rng = DefaultRNG::new_from_os(); -//! let key: KeyMaterial256 = HMAC_SHA3_256::keygen_from_rng(&mut rng) +//! let key = HmacKey::::generate(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! -//! let mut hmac = HMAC_SHA3_256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey"); +//! let mut hmac: HMAC_SHA3_256 = key.create_generator(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); //! let output: Vec = hmac.do_final(); @@ -111,31 +101,30 @@ //! //! ## Verifying a MAC //! -//! The [`MAC`] trait also provides functions for MAC verification. The built-in verification -//! functions use constant-time comparisons and so are *strongly recommended* rather than -//! re-computing the MAC value and comparing it yourself. +//! The built-in verification functions use constant-time comparisons and so are *strongly +//! recommended* rather than re-computing the MAC value and comparing it yourself. //! -//! The simplest usage is via the one-shot functions. +//! The simplest usage is the key's one-shot: //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacVerificationKey}; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha3::SHA3_256; //! //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); +//! let key = HmacKey::::from_key_material(&key_material).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; //! //! // .verify() returns a bool: true if the MAC is valid, false otherwise. -//! if HMAC_SHA3_256::new(&key).unwrap() -//! .verify(data, -//! b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00 -//! \x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" -//! ) +//! if key.verify(data, +//! b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00 +//! \x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7") //! { //! println!("MAC is valid!"); //! } else { @@ -148,15 +137,18 @@ //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacVerificationKey}; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha3::SHA3_256; //! //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. -//! let key = KeyMaterial256::from_bytes_as_type( +//! let key_material = KeyMaterial256::from_bytes_as_type( //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); -//! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); +//! let key = HmacKey::::from_key_material(&key_material).unwrap(); +//! let mut hmac = key.create_verifier(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); //! if hmac.do_verify_final(b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00\x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" @@ -186,7 +178,10 @@ //! //! ```rust //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; //! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sha3::SHA3_256; //! use bouncycastle_sha3::hmac::HMAC_SHA3_256; //! //! let msg_part1 = b"The quick brown fox"; @@ -196,7 +191,8 @@ //! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", //! KeyType::MACKey).unwrap(); //! -//! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); +//! let key = HmacKey::::from_key_material(&key).unwrap(); +//! let mut hmac = key.create_generator(); //! hmac.do_update(msg_part1); //! //! // suspend the in-progress mac (the key is NOT included in the serialized state) @@ -207,7 +203,7 @@ //! // ... //! //! // ... later, possibly on another host: resume from the serialized state by re-supplying -//! // the same key (make sure you store it securely!). +//! // the same key object (make sure you store the material securely!). //! let mut hmac_resumed = HMAC_SHA3_256::from_suspended(serialized_state, &key).unwrap(); //! hmac_resumed.do_update(msg_part2); //! let h: Vec = hmac_resumed.do_final(); @@ -218,21 +214,23 @@ //! No heap memory is used by the algorithm itself; the `Vec`-returning convenience methods //! allocate only the output buffer, and the `*_out` variants allocate nothing. //! -//! | Object | Size (bytes) | -//! |--------------------------------------------------|--------------| -//! | `HMAC_SHA3_224` .. `HMAC_SHA3_512`, and their `HmacKey` | 880 | -//! | Suspended state, all four ([`SuspendableKeyed`]) | 415 | +//! | Object | Size (bytes) | +//! |---------------------------------------------------------|--------------| +//! | `HmacKey` for `HMAC_SHA3_224` .. `HMAC_SHA3_512` | 880 | +//! | `HMAC_SHA3_224` .. `HMAC_SHA3_512` worker | 448 | +//! | Suspended state, all four ([`SuspendableKeyed`]) | 415 | //! -//! The worker and the key are each two Keccak-f\[1600\] sponges (440 bytes each, see the -//! crate-level Memory Usage section): the inner hash and the outer pad state. The suspended state -//! is exactly the inner hash's suspended state -- the key is deliberately excluded -- so all four -//! share the sponge's single value. +//! The key is two Keccak-f\[1600\] sponges (440 bytes each, see the crate-level Memory Usage +//! section): the inner and the outer pad state. The worker is the inner sponge plus a reference +//! to the key it borrows. The suspended state is exactly the inner hash's suspended state -- the +//! key is deliberately excluded -- so all four share the sponge's single value. //! //! # 🚨 Security Considerations 🚨 //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol, not to silence an error. +//! * The key must carry at least the security strength claimed by the HMAC, and +//! [`FromAnyKeyMaterial::from_key_material`] enforces that. +//! [`FromAnyKeyMaterial::from_key_material_allow_weak`] deliberately skips the check; use it only +//! where a weak or all-zero key is called for by the protocol, not to silence an error. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -259,9 +257,11 @@ use bouncycastle_hmac::{HMAC, HMACParams}; #[allow(unused_imports)] use bouncycastle_core::key_material::KeyType; #[allow(unused_imports)] +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +#[allow(unused_imports)] use bouncycastle_core::traits::{MAC, SuspendableKeyed}; #[allow(unused_imports)] -use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; +use bouncycastle_hmac::{HmacKey, MIN_FIPS_DIGEST_LEN}; /*** String constants ***/ /// The name of HMAC over SHA3-224. @@ -285,7 +285,7 @@ pub const HKDF_SHA3_512_NAME: &str = "HKDF-SHA3-512"; /*** Type aliases ***/ /// Public type for HMAC using SHA3_224. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC; +pub type HMAC_SHA3_224<'k> = HMAC<'k, SHA3_224>; impl HMACParams for SHA3_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_224_NAME; @@ -299,7 +299,7 @@ impl HMACParams for SHA3_224 { /// Public type for HMAC using SHA3_256. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC; +pub type HMAC_SHA3_256<'k> = HMAC<'k, SHA3_256>; impl HMACParams for SHA3_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_256_NAME; @@ -313,7 +313,7 @@ impl HMACParams for SHA3_256 { /// Public type for HMAC using SHA3_384. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC; +pub type HMAC_SHA3_384<'k> = HMAC<'k, SHA3_384>; impl HMACParams for SHA3_384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_384_NAME; @@ -327,7 +327,7 @@ impl HMACParams for SHA3_384 { /// Public type for HMAC using SHA3_512. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC; +pub type HMAC_SHA3_512<'k> = HMAC<'k, SHA3_512>; impl HMACParams for SHA3_512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_512_NAME; diff --git a/crypto/sha3/src/kmac.rs b/crypto/sha3/src/kmac.rs index 3f58db27b..489d06605 100644 --- a/crypto/sha3/src/kmac.rs +++ b/crypto/sha3/src/kmac.rs @@ -1,23 +1,28 @@ //! KMAC, the Keccak Message Authentication Code of NIST SP 800-185 Sec 4. //! //! # KMAC -//! KMAC is a [`MAC`]. [`MAC::new`] takes a key tagged [`KeyType::MACKey`] and produces the nominal -//! output length; [`KMACInternal::new_with_params`] chooses `S` and the output length, which is -//! bound into the function rather than a truncation of it (see [`KMAC128`]): +//! KMAC is a [`MAC`], reached through a [`KMAC128Key`] / [`KMAC256Key`]. The key takes material +//! tagged [`KeyType::MACKey`]; `from_key_material` gives the nominal output length and +//! [`KMACKeyInternal::with_params`] chooses `S` and the output length, which is bound into the +//! function rather than a truncation of it (see [`KMAC128`]): //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sha3::kmac::KMAC128; +//! use bouncycastle_sha3::kmac::KMAC128Key; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let key = KMAC128Key::from_key_material(&material).unwrap(); //! -//! let tag: Vec = KMAC128::new(&key).unwrap().mac(b"Hello, world!"); -//! assert!(KMAC128::new(&key).unwrap().verify(b"Hello, world!", &tag)); +//! let tag: Vec = key.mac(b"Hello, world!"); +//! assert!(key.verify(b"Hello, world!", &tag)); //! -//! // 16-byte tags, under a customization string. -//! let kmac = KMAC128::new_with_params(&key, b"My Tagged Application", 16, false).unwrap(); -//! let short_tag: Vec = kmac.mac(b"Hello, world!"); -//! assert_eq!(short_tag.len(), 16); +//! // 16-byte tags, under a customization string: a different function, so a different key. +//! let short = KMAC128Key::with_params(&material, b"My Tagged Application", 16).unwrap(); +//! let mut kmac = short.create_generator(); +//! kmac.do_update(b"Hello, "); +//! kmac.do_update(b"world!"); +//! assert_eq!(kmac.do_final().len(), 16); //! ``` //! //! # KMACXOF @@ -32,8 +37,9 @@ //! Example of `KMACXOF128`: //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{Hash, MAC, XOF, XOFSqueezer}; -//! use bouncycastle_sha3::kmac::{KMAC128, KMACXOF128}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +//! use bouncycastle_sha3::kmac::{KMAC128Key, KMACXOF128}; //! //! let key = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); //! @@ -45,16 +51,18 @@ //! //! // A final read of 32 bytes is KMAC128 at its nominal length, not a prefix of the stream above. //! let bound: Vec = KMACXOF128::new(&key, b"", false).unwrap().xof(b"Hello, world!", 32); -//! assert_eq!(bound, KMAC128::new(&key).unwrap().mac(b"Hello, world!")); +//! assert_eq!(bound, KMAC128Key::from_key_material(&key).unwrap().mac(b"Hello, world!")); //! assert_ne!(bound[..16], first[..]); //! ``` use crate::cshake::{CSHAKE_COMPONENT_LEN, CSHAKEInternal, CSHAKESqueezer, right_encode}; use crate::{SHAKE128Params, SHAKE256Params, SHAKEParams}; use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; -use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; +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::{Algorithm, Hash, MAC, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{Algorithm, Hash, MAC, RNG, Suspendable, XOF, XOFSqueezer}; use bouncycastle_utils::ct; use bouncycastle_utils::suspendable_state::{ Cursor, CursorMut, LIB_VERSION_LEN, SuspendableComponent, bounded_usize, resume_component, @@ -70,11 +78,19 @@ pub const KMACXOF128_NAME: &str = "KMACXOF128"; /// The name of the KMACXOF256 algorithm (NIST SP 800-185 Sec 4.3.1). pub const KMACXOF256_NAME: &str = "KMACXOF256"; +/// The shortest KMAC output, in bytes. SP 800-185 Sec 8.4.2: applications "shall not select an +/// output length L that is less than 32 bits". +pub const KMAC_MIN_OUTPUT_LEN: usize = 4; +/// The longest KMAC output, in bytes: `8 * n` is what `right_encode` absorbs (Sec 4.3 step 1), +/// and it is a `u64` here. +pub const KMAC_MAX_OUTPUT_LEN: usize = (u64::MAX / 8) as usize; + /// KMAC128: the Keccak MAC of NIST SP 800-185 Sec 4, at a 128-bit security strength. /// -/// [`bouncycastle_core::traits::MAC::new`] gives the common case -- no customization, 32-byte -/// output. [`KMACInternal::new_with_params`] chooses the customization string and output length, -/// [`KMACXOF128`] is the separate arbitrary-length function of Sec 4.3.1. +/// The worker a [`KMAC128Key`] hands out: `from_key_material` gives the common case -- no +/// customization, 32-byte output -- and [`KMACKeyInternal::with_params`] chooses the +/// customization string and output length. [`KMACXOF128`] is the separate arbitrary-length +/// function of Sec 4.3.1. pub type KMAC128 = KMACInternal; /// KMAC256: the Keccak MAC of NIST SP 800-185 Sec 4, at a 256-bit security strength. /// @@ -96,39 +112,167 @@ pub const SUSPENDED_KMAC_STATE_LEN: usize = LIB_VERSION_LEN + CSHAKE_COMPONENT_L /// Length in bytes of the suspended state of KMACXOF. pub const SUSPENDED_KMACXOF_STATE_LEN: usize = LIB_VERSION_LEN + CSHAKE_COMPONENT_LEN; -/// The shortest KMAC output, in bytes: SP 800-185 Sec 8.4.2, "applications of this Recommendation -/// **shall not** select an output length L that is less than 32 bits". [`KMACInternal::new_with_params`] -/// refuses anything shorter. -pub const KMAC_MIN_OUTPUT_LEN: usize = 4; - /// The function-name string every KMAC binds, per SP 800-185 Sec 4.3. Fixed by the specification: /// it is what separates KMAC from any other cSHAKE-derived function. const KMAC_FUNCTION_NAME: &[u8] = b"KMAC"; -/// The sponge both KMAC and KMACXOF start from: cSHAKE under the "KMAC" function name with the -/// key bound in front (Sec 4.3 step 1, `bytepad(encode_string(K), rate)`, absorbed rather than -/// materialised). The key checks are here so the two constructors cannot drift. -fn keyed_cshake( - key: &impl KeyMaterialTrait, - customization: &[u8], - allow_weak_key: bool, -) -> Result, MACError> { - // Same stance as HMAC: an all-zero key is Zeroized rather than MACKey, and is allowed - // through so callers are not forced to re-tag it. - if !(key.key_type() == KeyType::Zeroized || key.key_type() == KeyType::MACKey) { - return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( - "Key type must be a MAC key.", - ))); - } - let strength = SecurityStrength::from_bits(PARAMS::SIZE as usize); - if !allow_weak_key && key.security_strength() < strength { - Err(KeyMaterialError::SecurityStrength( - "KMAC::new(): provided key has a lower security strength than the instantiated KMAC", - ))? - } - let mut cshake = CSHAKEInternal::::new(KMAC_FUNCTION_NAME, customization); - crate::cshake::absorb_bytepad_strings(&mut cshake, &[key.ref_to_bytes()]); - Ok(cshake) +/// Internal struct for the KMAC key. Use [`KMAC128Key`] or [`KMAC256Key`]. +/// +/// The key is the cSHAKE state after the key binding of Sec 4.3 step 1, `bytepad(encode_string(K), +/// rate)`, plus the customization and the output length chosen for it, so every worker starts +/// from that state and absorbs only its message. A different `S` or `L` is a different KMAC +/// function (Sec 1), and so a different key object over the same material. +#[derive(Clone)] +pub struct KMACKeyInternal { + cshake: CSHAKEInternal, + output_len: usize, +} + +/// A KMAC128 key: see [`KMACKeyInternal`]. +pub type KMAC128Key = KMACKeyInternal; +/// A KMAC256 key: see [`KMACKeyInternal`]. +pub type KMAC256Key = KMACKeyInternal; + +impl KMACKeyInternal { + /// The nominal output length: 32 bytes for KMAC128 and 64 for KMAC256. + const NOMINAL_OUTPUT_LEN: usize = (PARAMS::SIZE as usize) / 4; + + /// A key for KMAC under a customization string and an output length of the caller's + /// choosing. `output_len` is `L` in bytes, from [`KMAC_MIN_OUTPUT_LEN`] to + /// [`KMAC_MAX_OUTPUT_LEN`], and is bound into the computation, so it must be the length the + /// verifier will use; `customization` may be empty. + /// + /// Sec 8.4.1 requires the key to be at least as long as the security strength for approved + /// use; that is enforced through the key's [`SecurityStrength`] tag, exactly as HMAC does, + /// and [`Self::with_params_allow_weak`] is the escape hatch. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `output_len` is outside the bounds above; + /// [`MACError::KeyMaterialError`] if the key is not tagged as a MAC key, or is tagged below + /// this KMAC's security strength. + pub fn with_params( + key: &dyn KeyMaterialTrait, + customization: &[u8], + output_len: usize, + ) -> Result { + Self::check_output_len(output_len)?; + Self::with_params_checked(key, customization, output_len, false) + } + + /// As [`with_params`](Self::with_params) without the strength check. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `output_len` is outside the bounds; + /// [`MACError::KeyMaterialError`] if the key is not tagged as a MAC key. + pub fn with_params_allow_weak( + key: &dyn KeyMaterialTrait, + customization: &[u8], + output_len: usize, + ) -> Result { + Self::check_output_len(output_len)?; + Self::with_params_checked(key, customization, output_len, true) + } + + /// The MAC's bounds on `L`. KMACXOF is the `L = 0` form (Sec 4.3.1) and does not pass here. + fn check_output_len(output_len: usize) -> Result<(), MACError> { + if output_len < KMAC_MIN_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "KMAC output length is below the 32-bit floor of SP 800-185 Sec 8.4.2", + )); + } + if output_len > KMAC_MAX_OUTPUT_LEN { + return Err(MACError::InvalidLength("KMAC output length in bits does not fit a u64")); + } + Ok(()) + } + + /// The key binding shared with KMACXOF, which passes `output_len == 0`. + fn with_params_checked( + key: &dyn KeyMaterialTrait, + customization: &[u8], + output_len: usize, + allow_weak_key: bool, + ) -> Result { + // Same stance as HMAC: an all-zero key is Zeroized rather than MACKey, and is allowed + // through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::Zeroized || key.key_type() == KeyType::MACKey) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + let strength = SecurityStrength::from_bits(PARAMS::SIZE as usize); + if !allow_weak_key && key.security_strength() < strength { + Err(KeyMaterialError::SecurityStrength( + "KMAC: provided key has a lower security strength than the instantiated KMAC", + ))? + } + + let mut cshake = CSHAKEInternal::::new(KMAC_FUNCTION_NAME, customization); + // Sec 4.3 step 1: bytepad(encode_string(K), rate), absorbed rather than materialised. + crate::cshake::absorb_bytepad_strings(&mut cshake, &[key.ref_to_bytes()]); + + Ok(Self { cshake, output_len }) + } +} + +impl FromAnyKeyMaterial for KMACKeyInternal { + /// No customization string and the nominal output length, 32 bytes for KMAC128 and 64 for + /// KMAC256; the errors are [`with_params`](Self::with_params)'s. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::with_params(key, &[], Self::NOMINAL_OUTPUT_LEN) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::with_params_allow_weak(key, &[], Self::NOMINAL_OUTPUT_LEN) + } + + /// Draws a key of the security strength's length (Sec 8.4.1: 16 bytes for KMAC128, 32 for + /// KMAC256) from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped at what that + /// length can carry, and builds the key from it with no customization and the nominal + /// output length. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below the KMAC's. + fn generate(rng: &mut dyn RNG) -> Result { + let key_len = (PARAMS::SIZE as usize) / 8; + let mut key = KeyMaterial::<32>::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(&mut key.ref_to_bytes_mut()?[..key_len]) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(key_len)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KeyMaterial::from_rng + // does: a 16-byte KMAC128 key from a 256-bit DRBG is a 128-bit key. + let by_len = SecurityStrength::from_bits(key_len * 8); + let by_rng = rng.security_strength(); + key.set_security_strength(if by_rng < by_len { by_rng } else { by_len }) + })?; + Self::from_key_material(&key) + } +} + +impl MacGenerationKey for KMACKeyInternal { + type Generator<'k> + = KMACInternal + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + KMACInternal { cshake: self.cshake.clone(), output_len: self.output_len } + } +} + +impl MacVerificationKey for KMACKeyInternal { + type Verifier<'k> + = KMACInternal + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } } /// Internal struct for KMAC. Use [`KMAC128`] or [`KMAC256`]. @@ -145,8 +289,8 @@ fn keyed_cshake( /// /// The output length is *absorbed*, so KMAC at one length is unrelated to KMAC at another -- /// Sec 1 puts it as "any change in the requested output length completely changes the function". -/// That is why [`Self::new_with_params`] takes the length up front and [`MAC::do_final`] produces -/// exactly that many bytes. +/// That is why [`KMACKeyInternal::with_params`] takes the length up front and [`MAC::do_final`] +/// produces exactly that many bytes. /// /// [`KMACXOFInternal`] is the separate function of Sec 4.3.1, KMACXOF, which binds /// `right_encode(0)` instead and produces as much output as asked for. Its bytes are *not* a @@ -163,36 +307,6 @@ impl Algorithm for KMACInternal { } impl KMACInternal { - /// A new KMAC with a customization string and an output length of the caller's choosing. - /// - /// `output_len` is `L` in bytes and is bound into the computation, so it must be the length the - /// verifier will use. `customization` may be empty. [`MAC::new`] is this with no customization - /// and the nominal output length. - /// - /// Sec 8.4.1 requires the key to be at least as long as the security strength for approved use; - /// that is enforced through the key's [`SecurityStrength`] tag, exactly as `HMAC` does, and - /// [`MAC::new_allow_weak_key`] is the escape hatch. Sec 8.4.2's floor on the output length, - /// [`KMAC_MIN_OUTPUT_LEN`], has no escape hatch. - /// - /// # Errors - /// [`MACError::KeyMaterialError`] if the key is not tagged as a MAC key, or -- unless - /// `allow_weak_key` -- if it is tagged below this KMAC's security strength. - /// [`MACError::InvalidLength`] if `output_len` is below [`KMAC_MIN_OUTPUT_LEN`]. - pub fn new_with_params( - key: &impl KeyMaterialTrait, - customization: &[u8], - output_len: usize, - allow_weak_key: bool, - ) -> Result { - if output_len < KMAC_MIN_OUTPUT_LEN { - return Err(MACError::InvalidLength( - "KMAC output length is below the 32-bit floor of SP 800-185 Sec 8.4.2", - )); - } - let cshake = keyed_cshake::(key, customization, allow_weak_key)?; - Ok(Self { cshake, output_len }) - } - /// Absorbs `right_encode(value)`, the length binding of Sec 4.3 step 1. fn absorb_right_encode(&mut self, value: u64) { let (buf, len) = right_encode(value); @@ -200,7 +314,7 @@ impl KMACInternal { } } -impl SuspendableComponent for KMACInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for KMACInternal { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN + 8; type Key = (); @@ -212,22 +326,24 @@ impl SuspendableComponent for KMACInternal { debug_assert!(w.is_done()); } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let (cshake, rest) = state.split_at(CSHAKE_COMPONENT_LEN); let cshake = CSHAKEInternal::read_tagged_customized(cshake, PARAMS::KMAC_STATE_TAG)?; let mut r = Cursor::new(rest); - let output_len = bounded_usize(r.u64(), usize::MAX)?; - debug_assert!(r.is_done()); + // The bounds `with_params` applies: a state claiming a length outside them was never + // written by this type. + let output_len = bounded_usize(r.u64(), KMAC_MAX_OUTPUT_LEN)?; if output_len < KMAC_MIN_OUTPUT_LEN { return Err(SuspendableError::InvalidData); } + debug_assert!(r.is_done()); Ok(Self { cshake, output_len }) } } // `Suspendable` rather than `SuspendableKeyed`, keyed though KMAC is. `SuspendableKeyed` lets a // state omit the key because the key is wanted again at resume -- HMAC needs it for the outer -// `K xor opad` step. KMAC's key goes into the sponge in `new_with_params` and is never touched +// `K xor opad` step. KMAC's key goes into the sponge in `with_params` and is never touched // again, so a re-supplied key could neither rebuild anything nor be checked. /// The suspended state is not key-free: Keccak-f is a permutation, so anyone holding the state /// and the data absorbed so far can invert it back to the key. Store it as securely as the key. @@ -242,18 +358,6 @@ impl Suspendable for KMACInternal } impl MAC for KMACInternal { - /// A KMAC with no customization string, producing the nominal output length -- 32 bytes for - /// KMAC128 and 64 for KMAC256. Use [`Self::new_with_params`] to choose either. - fn new(key: &impl KeyMaterialTrait) -> Result { - let len = (PARAMS::SIZE as usize) / 4; - Self::new_with_params(key, &[], len, false) - } - - fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let len = (PARAMS::SIZE as usize) / 4; - Self::new_with_params(key, &[], len, true) - } - fn output_len(&self) -> usize { self.output_len } @@ -352,7 +456,7 @@ impl Algorithm for KMACXOFInternal { impl KMACXOFInternal { /// A new KMACXOF under `key`, optionally customized by `customization`. /// - /// The key requirements are [`KMACInternal::new_with_params`]'s: tagged as a MAC key, and at + /// The key requirements are [`KMACKeyInternal::with_params`]'s: tagged as a MAC key, and at /// least the security strength unless `allow_weak_key`. /// /// # Errors @@ -364,11 +468,13 @@ impl KMACXOFInternal { ) -> Result { // The key binding is identical to KMAC's; only the length encoding differs, and that is // applied when output begins. - Ok(Self { cshake: keyed_cshake::(key, customization, allow_weak_key)? }) + let kmac = + KMACKeyInternal::::with_params_checked(key, customization, 0, allow_weak_key)?; + Ok(Self { cshake: kmac.cshake }) } } -impl SuspendableComponent for KMACXOFInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for KMACXOFInternal { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN; type Key = (); @@ -376,7 +482,7 @@ impl SuspendableComponent for KMACXOFInternal { self.cshake.write_tagged(PARAMS::KMACXOF_STATE_TAG, out) } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let cshake = CSHAKEInternal::read_tagged_customized(state, PARAMS::KMACXOF_STATE_TAG)?; Ok(Self { cshake }) } diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index e181b9a98..5ce50b3b9 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -222,7 +222,7 @@ //! * The sponge state and queue are held in [`bouncycastle_utils::secret::Secret`] and zeroized on //! drop. //! * KMAC's security rests on its key and output lengths (SP 800-185 Sec 8.4): the key check is -//! [`kmac::KMACInternal::new_with_params`]'s, with `allow_weak_key` as the bypass, and an output +//! [`kmac::KMACKeyInternal::with_params`]'s, with `with_params_allow_weak` as the bypass, and an output //! shorter than 8 bytes is the caller's to justify (Sec 8.4.2: never below 4, and below 8 //! only after a risk analysis). //! * cSHAKE has SHAKE's prefix property; the fixed-length KMAC, TupleHash and ParallelHash do diff --git a/crypto/sha3/src/parallelhash.rs b/crypto/sha3/src/parallelhash.rs index 50da8a561..c6d42ee59 100644 --- a/crypto/sha3/src/parallelhash.rs +++ b/crypto/sha3/src/parallelhash.rs @@ -240,7 +240,7 @@ impl ParallelHashInternal { } } -impl SuspendableComponent for ParallelHashInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for ParallelHashInternal { const STATE_LEN: usize = PARALLEL_STATE_LEN + 8; type Key = (); @@ -252,7 +252,7 @@ impl SuspendableComponent for ParallelHashInternal debug_assert!(w.is_done()); } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let (parallel, rest) = state.split_at(PARALLEL_STATE_LEN); let state = ParallelState::read_state(parallel, PARAMS::PARALLELHASH_STATE_TAG)?; let mut r = Cursor::new(rest); @@ -357,7 +357,7 @@ impl ParallelHashXOFInternal { } } -impl SuspendableComponent for ParallelHashXOFInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for ParallelHashXOFInternal { const STATE_LEN: usize = PARALLEL_STATE_LEN; type Key = (); @@ -365,7 +365,7 @@ impl SuspendableComponent for ParallelHashXOFInternal Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { Ok(Self { state: ParallelState::read_state(state, PARAMS::PARALLELHASHXOF_STATE_TAG)? }) } } diff --git a/crypto/sha3/src/tuplehash.rs b/crypto/sha3/src/tuplehash.rs index b703ef93e..3c94dde80 100644 --- a/crypto/sha3/src/tuplehash.rs +++ b/crypto/sha3/src/tuplehash.rs @@ -140,7 +140,7 @@ impl TupleHashInternal { } } -impl SuspendableComponent for TupleHashInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for TupleHashInternal { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN + 8; type Key = (); @@ -152,7 +152,7 @@ impl SuspendableComponent for TupleHashInternal { debug_assert!(w.is_done()); } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let (cshake, rest) = state.split_at(CSHAKE_COMPONENT_LEN); let cshake = CSHAKEInternal::read_tagged_customized(cshake, PARAMS::TUPLEHASH_STATE_TAG)?; let mut r = Cursor::new(rest); @@ -277,7 +277,7 @@ impl TupleHashXOFInternal { } } -impl SuspendableComponent for TupleHashXOFInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for TupleHashXOFInternal { const STATE_LEN: usize = CSHAKE_COMPONENT_LEN; type Key = (); @@ -285,7 +285,7 @@ impl SuspendableComponent for TupleHashXOFInternal self.cshake.write_tagged(PARAMS::TUPLEHASHXOF_STATE_TAG, out) } - fn read_state(state: &[u8], _key: &()) -> Result { + fn read_state(state: &[u8], _key: &'k ()) -> Result { let cshake = CSHAKEInternal::read_tagged_customized(state, PARAMS::TUPLEHASHXOF_STATE_TAG)?; Ok(Self { cshake }) } diff --git a/crypto/sha3/tests/kmac_bc-test-data.rs b/crypto/sha3/tests/kmac_bc-test-data.rs index 651580a71..590643f8b 100644 --- a/crypto/sha3/tests/kmac_bc-test-data.rs +++ b/crypto/sha3/tests/kmac_bc-test-data.rs @@ -6,11 +6,12 @@ use bouncycastle_core::errors::MACError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::MacGenerationKey; use bouncycastle_core::traits::{Hash, MAC, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_core_test_framework::xof::TestFrameworkXOF; use bouncycastle_hex as hex; -use bouncycastle_sha3::kmac::{KMAC128, KMAC256, KMACXOF128, KMACXOF256}; +use bouncycastle_sha3::kmac::{KMAC128Key, KMAC256Key, KMACXOF128, KMACXOF256}; const TEST_DATA_DIR: &str = "crypto/sp800-185"; @@ -82,10 +83,10 @@ fn nist_sp800_185_kmac_sample_values() { let key = key_material(&v.key); let got = match v.strength { - 128 => KMAC128::new_with_params(&key, v.s.as_bytes(), want, false) + 128 => KMAC128Key::with_params(&key, v.s.as_bytes(), want) .expect("a valid key") .mac(&v.msg), - 256 => KMAC256::new_with_params(&key, v.s.as_bytes(), want, false) + 256 => KMAC256Key::with_params(&key, v.s.as_bytes(), want) .expect("a valid key") .mac(&v.msg), other => panic!("COUNT {i}: unexpected strength {other}"), @@ -173,7 +174,7 @@ fn do_final_binds_the_length_when_nothing_has_been_read() { match f.strength { 128 => check_do_final_binds_length( || KMACXOF128::new(&key, s, false).expect("a valid key"), - |n| KMAC128::new_with_params(&key, s, n, false).expect("a valid key").mac(&f.msg), + |n| KMAC128Key::with_params(&key, s, n).expect("a valid key").mac(&f.msg), &f.msg, &f.output, &x.output, @@ -181,7 +182,7 @@ fn do_final_binds_the_length_when_nothing_has_been_read() { ), 256 => check_do_final_binds_length( || KMACXOF256::new(&key, s, false).expect("a valid key"), - |n| KMAC256::new_with_params(&key, s, n, false).expect("a valid key").mac(&f.msg), + |n| KMAC256Key::with_params(&key, s, n).expect("a valid key").mac(&f.msg), &f.msg, &f.output, &x.output, @@ -338,13 +339,13 @@ fn mac_out_and_do_final_out_agree_with_the_sample_values() { let ctx = format!("COUNT {i}: KMAC{} S={:?}", v.strength, v.s); match v.strength { 128 => check_out_variants( - || KMAC128::new_with_params(&key, s, n, false).unwrap(), + || KMAC128Key::with_params(&key, s, n).unwrap().create_generator(), &v.msg, &v.output, &ctx, ), 256 => check_out_variants( - || KMAC256::new_with_params(&key, s, n, false).unwrap(), + || KMAC256Key::with_params(&key, s, n).unwrap().create_generator(), &v.msg, &v.output, &ctx, diff --git a/crypto/sha3/tests/kmac_tests.rs b/crypto/sha3/tests/kmac_tests.rs index 41e5a6deb..808815528 100644 --- a/crypto/sha3/tests/kmac_tests.rs +++ b/crypto/sha3/tests/kmac_tests.rs @@ -1,10 +1,13 @@ //! KMAC and KMACXOF behaviour tests. The SP 800-185 sample values are in `kmac_bc-test-data.rs`. -use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; +use bouncycastle_core::errors::{KeyMaterialError, MACError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, Hash, MAC, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{Algorithm, Hash, MAC, XOF, XOFSqueezer}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; use bouncycastle_sha3::kmac::{ - KMAC_MIN_OUTPUT_LEN, KMAC128, KMAC256, KMACXOF128, KMACXOF256, SUSPENDED_KMAC_STATE_LEN, + KMAC_MAX_OUTPUT_LEN, KMAC_MIN_OUTPUT_LEN, KMAC128, KMAC128Key, KMAC256, KMAC256Key, KMACXOF128, + KMACXOF256, }; /// A 32-byte MAC key carries a 256-bit strength, so it satisfies both KMAC128 and KMAC256 without @@ -19,59 +22,20 @@ fn key_material(bytes: &[u8]) -> KeyMaterial<32> { #[test] fn output_length_changes_the_function() { let key = key_material(&[0x42u8; 32]); - let short = KMAC128::new_with_params(&key, b"", 16, false).unwrap().mac(b"abc"); - let long = KMAC128::new_with_params(&key, b"", 32, false).unwrap().mac(b"abc"); + let short = KMAC128Key::with_params(&key, b"", 16).unwrap().mac(b"abc"); + let long = KMAC128Key::with_params(&key, b"", 32).unwrap().mac(b"abc"); assert_eq!(short.len(), 16); assert_eq!(long.len(), 32); assert_ne!(&long[..16], &short[..], "a longer KMAC must not extend a shorter one"); } -/// SP 800-185 Sec 8.4.2: an output length under 32 bits is not allowed as a MAC. The floor holds -/// at construction and when a suspended state names the length. -#[test] -fn output_length_floor() { - let key = key_material(&[0x42u8; 32]); - assert_eq!(KMAC_MIN_OUTPUT_LEN, 4); - for n in [0, 1, 3] { - assert!( - matches!( - KMAC128::new_with_params(&key, b"", n, false), - Err(MACError::InvalidLength(_)) - ), - "{n} bytes" - ); - assert!( - matches!(KMAC256::new_with_params(&key, b"", n, true), Err(MACError::InvalidLength(_))), - "{n} bytes, weak keys allowed" - ); - } - assert_eq!(KMAC128::new_with_params(&key, b"", 4, false).unwrap().mac(b"abc").len(), 4); - - // The output length is the last eight bytes of the suspended state, little-endian. - let mut k = KMAC128::new_with_params(&key, b"", 4, false).unwrap(); - k.do_update(b"abc"); - let good = k.suspend(); - let len_at = SUSPENDED_KMAC_STATE_LEN - 8; - assert_eq!(&good[len_at..], &4u64.to_le_bytes(), "layout check"); - assert_eq!(KMAC128::from_suspended(good).unwrap().do_final().len(), 4); - for n in [0u64, 3] { - let mut bad = good; - bad[len_at..].copy_from_slice(&n.to_le_bytes()); - assert!( - matches!(KMAC128::from_suspended(bad), Err(SuspendableError::InvalidData)), - "a suspended state naming {n} bytes" - ); - } -} - /// The customization string separates one use of KMAC from another (Sec 4.2). #[test] fn customization_separates_the_functions() { let key = key_material(&[0x42u8; 32]); - let plain = KMAC128::new_with_params(&key, b"", 32, false).unwrap().mac(b"abc"); - let custom = - KMAC128::new_with_params(&key, b"My Tagged Application", 32, false).unwrap().mac(b"abc"); + let plain = KMAC128Key::with_params(&key, b"", 32).unwrap().mac(b"abc"); + let custom = KMAC128Key::with_params(&key, b"My Tagged Application", 32).unwrap().mac(b"abc"); assert_ne!(plain, custom, "a customization string must change the output"); } @@ -81,29 +45,21 @@ fn streaming_and_verification() { let key = key_material(&[0x11u8; 32]); let msg: Vec = (0..=255u8).collect(); - let one = KMAC128::new_with_params(&key, b"", 32, false).unwrap().mac(&msg); + let key = KMAC128Key::with_params(&key, b"", 32).unwrap(); + let one = key.mac(&msg); - let mut k = KMAC128::new_with_params(&key, b"", 32, false).unwrap(); + let mut k = key.create_generator(); for chunk in msg.chunks(13) { k.do_update(chunk); } assert_eq!(k.do_final(), one, "chunked input must equal the one-shot"); - assert!( - KMAC128::new_with_params(&key, b"", 32, false).unwrap().verify(&msg, &one), - "the correct tag must verify" - ); + assert!(key.verify(&msg, &one), "the correct tag must verify"); let mut wrong = one.clone(); wrong[0] ^= 1; - assert!( - !KMAC128::new_with_params(&key, b"", 32, false).unwrap().verify(&msg, &wrong), - "a corrupted tag must not verify" - ); - assert!( - !KMAC128::new_with_params(&key, b"", 32, false).unwrap().verify(&msg, &one[..16]), - "a truncated tag must not verify" - ); + assert!(!key.verify(&msg, &wrong), "a corrupted tag must not verify"); + assert!(!key.verify(&msg, &one[..16]), "a truncated tag must not verify"); } /// Sec 8.4.1 wants the key at least as long as the security strength; the tag on the key material @@ -116,25 +72,55 @@ fn weak_keys_are_refused_unless_allowed() { weak.security_strength() < bouncycastle_core::security_strength::SecurityStrength::_256bit ); - assert!(KMAC256::new(&weak).is_err(), "a 128-bit key must not instantiate KMAC256"); - assert!(KMAC256::new_allow_weak_key(&weak).is_ok(), "... unless explicitly allowed"); - assert!(KMAC128::new(&weak).is_ok(), "but it is enough for KMAC128"); + assert!( + KMAC256Key::from_key_material(&weak).is_err(), + "a 128-bit key must not make a KMAC256 key" + ); + assert!( + KMAC256Key::from_key_material_allow_weak(&weak).is_ok(), + "... unless explicitly allowed" + ); + assert!(KMAC256Key::with_params(&weak, b"S", 16).is_err()); + assert!(KMAC256Key::with_params_allow_weak(&weak, b"S", 16).is_ok()); + assert!(KMAC128Key::from_key_material(&weak).is_ok(), "but it is enough for KMAC128"); } -/// The default constructor: no customization, nominal output length. +/// The construction suite for both keys: a weak key accepted only through the `_allow_weak` +/// form, a no-strength key refused at any strength, generate at the key's strength. #[test] -fn default_constructor_uses_the_nominal_length() { +fn the_keys_enforce_the_material_policy() { let key = key_material(&[0x42u8; 32]); - assert_eq!(KMAC128::new(&key).unwrap().output_len(), 32); - assert_eq!(KMAC256::new(&key).unwrap().output_len(), 64); + let framework = TestFrameworkFromAnyKeyMaterial::new(); + framework.test::(&key); + framework.test::(&key); +} + +/// `from_key_material`: no customization, nominal output length. +#[test] +fn from_key_material_uses_the_nominal_length() { + let key = key_material(&[0x42u8; 32]); + assert_eq!(KMAC128Key::from_key_material(&key).unwrap().create_generator().output_len(), 32); + assert_eq!(KMAC256Key::from_key_material(&key).unwrap().create_generator().output_len(), 64); // ... and agrees with spelling the same thing out in full. assert_eq!( - KMAC128::new(&key).unwrap().mac(b"abc"), - KMAC128::new_with_params(&key, b"", 32, false).unwrap().mac(b"abc"), + KMAC128Key::from_key_material(&key).unwrap().mac(b"abc"), + KMAC128Key::with_params(&key, b"", 32).unwrap().mac(b"abc"), ); } +/// `generate` draws a key of the strength's length and tags it a MAC key, so the key it builds +/// is a full-strength one at the nominal length. +#[test] +fn generate_draws_a_key_at_the_nominal_length() { + let mut rng = bouncycastle_rng::HashDRBG_SHA512::new_from_os(); + let key = KMAC256Key::generate(&mut rng).unwrap(); + assert_eq!(key.create_generator().output_len(), 64); + let tag = key.mac(b"abc"); + assert!(key.verify(b"abc", &tag)); + assert_ne!(KMAC256Key::generate(&mut rng).unwrap().mac(b"abc"), tag, "a fresh key each time"); +} + #[test] fn algorithm_names() { assert_eq!(KMAC128::ALG_NAME, "KMAC128"); @@ -165,7 +151,7 @@ fn kmacxof_output_is_one_stream() { assert_ne!(&long[..32], &via_hash[..], "the Hash view binds L, so it leaves the stream"); assert_eq!( via_hash, - KMAC128::new(&key).unwrap().mac(b"abc"), + KMAC128Key::from_key_material(&key).unwrap().mac(b"abc"), "... and lands on fixed-length KMAC128 at the nominal length" ); } @@ -194,19 +180,19 @@ fn kmacxof_algorithm_names() { assert_eq!(KMACXOF256::ALG_NAME, "KMACXOF256"); } -/// `new_allow_weak_key` is `new` without the strength check: same customization, same nominal -/// length, same tag. +/// `from_key_material_allow_weak` is `from_key_material` without the strength check: same +/// customization, same nominal length, same tag. #[test] -fn new_allow_weak_key_uses_the_nominal_length() { +fn from_key_material_allow_weak_uses_the_nominal_length() { let key = key_material(&[0x42u8; 32]); - let k = KMAC128::new_allow_weak_key(&key).unwrap(); - assert_eq!(k.output_len(), 32); - assert_eq!(k.mac(b"abc"), KMAC128::new(&key).unwrap().mac(b"abc")); + let k = KMAC128Key::from_key_material_allow_weak(&key).unwrap(); + assert_eq!(k.create_generator().output_len(), 32); + assert_eq!(k.mac(b"abc"), KMAC128Key::from_key_material(&key).unwrap().mac(b"abc")); - let k = KMAC256::new_allow_weak_key(&key).unwrap(); - assert_eq!(k.output_len(), 64); - assert_eq!(k.mac(b"abc"), KMAC256::new(&key).unwrap().mac(b"abc")); + let k = KMAC256Key::from_key_material_allow_weak(&key).unwrap(); + assert_eq!(k.create_generator().output_len(), 64); + assert_eq!(k.mac(b"abc"), KMAC256Key::from_key_material(&key).unwrap().mac(b"abc")); } /// The same stance as HMAC: a key tagged `MACKey` or `Zeroized` is accepted, anything else is @@ -217,11 +203,11 @@ fn key_type_is_checked() { let cipher_key = KeyMaterial::<32>::from_bytes_as_type(&[0x42u8; 32], KeyType::SymmetricCipherKey).unwrap(); assert!(matches!( - KMAC128::new(&cipher_key), + KMAC128Key::from_key_material(&cipher_key), Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) )); assert!(matches!( - KMAC128::new_with_params(&cipher_key, b"", 32, true), + KMAC128Key::with_params_allow_weak(&cipher_key, b"", 32), Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) )); assert!(matches!( @@ -231,9 +217,15 @@ fn key_type_is_checked() { let zero = KeyMaterial::<32>::new(); assert_eq!(zero.key_type(), KeyType::Zeroized); - assert!(KMAC128::new(&zero).is_err(), "a zeroized key has no security strength"); - assert!(KMAC128::new_with_params(&zero, b"", 32, true).is_ok(), "... but is the right type"); - assert!(KMAC128::new_allow_weak_key(&zero).is_ok()); + assert!( + KMAC128Key::from_key_material(&zero).is_err(), + "a zeroized key has no security strength" + ); + assert!( + KMAC128Key::with_params_allow_weak(&zero, b"", 32).is_ok(), + "... but is the right type" + ); + assert!(KMAC128Key::from_key_material_allow_weak(&zero).is_ok()); assert!(KMACXOF128::new(&zero, b"", true).is_ok()); } @@ -265,3 +257,36 @@ fn kmacxof_hash_view_partial_bits() { Err(bouncycastle_core::errors::HashError::InvalidLength(_)) )); } + +/// SP 800-185 Sec 8.4.2's floor and the `right_encode` cap, on both constructors: below the floor +/// a zero-length tag would verify every message, above the cap `8 * L` does not fit the `u64`. +#[test] +fn output_length_is_bounded() { + let key = key_material(&[0x42; 32]); + assert_eq!(KMAC_MIN_OUTPUT_LEN, 4); + for n in 0..KMAC_MIN_OUTPUT_LEN { + assert!( + matches!(KMAC128Key::with_params(&key, b"", n), Err(MACError::InvalidLength(_))), + "an output length of {n} must be refused" + ); + assert!( + matches!( + KMAC256Key::with_params_allow_weak(&key, b"", n), + Err(MACError::InvalidLength(_)) + ), + "an output length of {n} must be refused by the weak-key form too" + ); + } + let shortest = KMAC128Key::with_params(&key, b"", KMAC_MIN_OUTPUT_LEN).unwrap(); + assert_eq!(shortest.mac(b"abc").len(), KMAC_MIN_OUTPUT_LEN); + assert!(!shortest.create_verifier().verify(b"abc", &[]), "an empty tag never verifies"); + + // Construction only: the length is absorbed at the final, nothing is allocated for it here. + assert!(KMAC128Key::with_params(&key, b"", KMAC_MAX_OUTPUT_LEN).is_ok()); + if let Some(over) = KMAC_MAX_OUTPUT_LEN.checked_add(1) { + assert!(matches!( + KMAC128Key::with_params(&key, b"", over), + Err(MACError::InvalidLength(_)) + )); + } +} diff --git a/crypto/sha3/tests/kmac_wycheproof.rs b/crypto/sha3/tests/kmac_wycheproof.rs index 5e01ef607..311139f2c 100644 --- a/crypto/sha3/tests/kmac_wycheproof.rs +++ b/crypto/sha3/tests/kmac_wycheproof.rs @@ -11,18 +11,18 @@ //! KMAC of that size and goes through `verify`, as well as through `mac` for the valid ones. use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::MAC; +use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; -use bouncycastle_sha3::kmac::{KMAC128, KMAC256}; +use bouncycastle_sha3::kmac::{KMAC128Key, KMAC256Key}; /// The longest key in either file is 129 bytes. const MAX_KEY_LEN: usize = 160; -/// Runs every case in one KMAC file; `new_kmac(key, output_len)` builds the KMAC under test. -fn run( +/// Runs every case in one KMAC file; `new_kmac(key, output_len)` builds the KMAC key under test. +fn run( filename: &str, algorithm: &str, - new_kmac: impl Fn(&KeyMaterial, usize) -> M, + new_kmac: impl Fn(&KeyMaterial, usize) -> K, ) { let Some(doc) = wycheproof_json(filename) else { return }; @@ -67,13 +67,13 @@ fn run( #[test] fn wycheproof_kmac128() { run("kmac128_no_customization_test.json", "KMAC128", |key, output_len| { - KMAC128::new_with_params(key, b"", output_len, false).expect("a KMAC128 instance") + KMAC128Key::with_params(key, b"", output_len).expect("a KMAC128 key") }); } #[test] fn wycheproof_kmac256() { run("kmac256_no_customization_test.json", "KMAC256", |key, output_len| { - KMAC256::new_with_params(key, b"", output_len, false).expect("a KMAC256 instance") + KMAC256Key::with_params(key, b"", output_len).expect("a KMAC256 key") }); } diff --git a/crypto/sha3/tests/sp800_185_suspend_tests.rs b/crypto/sha3/tests/sp800_185_suspend_tests.rs index 65eae588b..f7a480c3b 100644 --- a/crypto/sha3/tests/sp800_185_suspend_tests.rs +++ b/crypto/sha3/tests/sp800_185_suspend_tests.rs @@ -7,6 +7,7 @@ use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; use bouncycastle_core::traits::{Hash, MAC, Suspendable, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; use bouncycastle_sha3::kmac::*; @@ -97,10 +98,10 @@ fn kmac_round_trips() { resumed.do_update(PART2); assert!(resumed.do_verify_final(&expected)); } - mac_round_trip(|| KMAC128::new(&key()).unwrap()); - mac_round_trip(|| KMAC256::new(&key()).unwrap()); + mac_round_trip(|| KMAC128Key::from_key_material(&key()).unwrap().create_generator()); + mac_round_trip(|| KMAC256Key::from_key_material(&key()).unwrap().create_generator()); mac_round_trip(|| { - KMAC128::new_with_params(&key(), b"My Tagged Application", 16, false).unwrap() + KMAC128Key::with_params(&key(), b"My Tagged Application", 16).unwrap().create_generator() }); } @@ -161,7 +162,9 @@ fn each_type_rejects_the_others() { rejected::<_, KMACXOF128>(squeezer.clone().suspend(), "an unbound squeezer in KMACXOF128"); rejected::<_, CSHAKE128>(squeezer.suspend(), "an unbound squeezer in cSHAKE128"); - let mut k = KMAC128::new(&key()).unwrap(); + let k_key = KMAC128Key::from_key_material(&key()).unwrap(); + + let mut k = k_key.create_generator(); k.do_update(PART1); rejected::<_, TupleHash128>(k.clone().suspend(), "a KMAC128 state in TupleHash128"); rejected::<_, KMAC256>(k.suspend(), "a KMAC128 state in KMAC256"); @@ -196,11 +199,31 @@ fn corrupt_fields_are_rejected() { bad[SQUEEZING_FLAG] = 1; rejected::<_, CSHAKE128>(bad, "a squeezing sponge in an absorbing cSHAKE"); - let mut k = KMAC128::new(&key()).unwrap(); + let k_key = KMAC128Key::from_key_material(&key()).unwrap(); + + let mut k = k_key.create_generator(); k.do_update(PART1); - let mut bad = k.suspend(); + let good = k.suspend(); + let mut bad = good; bad[CUSTOMIZED] = 0; rejected::<_, KMAC128>(bad, "a KMAC claiming an empty function name"); + // The output length is the trailing u64, bounded as `with_params` bounds it. + let output_len = good.len() - 8; + let mut bad = good; + bad[output_len..].copy_from_slice(&0u64.to_le_bytes()); + rejected::<_, KMAC128>(bad, "a KMAC claiming a zero output length"); + let mut bad = good; + bad[output_len..].copy_from_slice(&3u64.to_le_bytes()); + rejected::<_, KMAC128>(bad, "a KMAC claiming an output length below the 32-bit floor"); + let mut bad = good; + bad[output_len..].copy_from_slice(&(u64::MAX / 4).to_le_bytes()); + rejected::<_, KMAC128>(bad, "a KMAC whose output length in bits overflows a u64"); + let mut edge = good; + edge[output_len..].copy_from_slice(&(KMAC_MIN_OUTPUT_LEN as u64).to_le_bytes()); + assert!(KMAC128::from_suspended(edge).is_ok(), "the floor itself is a valid output length"); + let mut edge = good; + edge[output_len..].copy_from_slice(&(KMAC_MAX_OUTPUT_LEN as u64).to_le_bytes()); + assert!(KMAC128::from_suspended(edge).is_ok(), "the cap itself is a valid output length"); let mut k = KMACXOF128::new(&key(), b"", false).unwrap(); k.do_update(PART1); @@ -246,7 +269,7 @@ fn state_tags_are_distinct() { tag(SHAKE128::new()), tag(SHAKE256::new()), tag(CSHAKE128::new(b"", b"S")), - tag(KMAC128::new(&key()).unwrap()), + tag(KMAC128Key::from_key_material(&key()).unwrap().create_generator()), tag(KMACXOF128::new(&key(), b"", false).unwrap()), tag(TupleHash128::new(b"", 32)), tag(TupleHashXOF128::new(b"")), @@ -254,7 +277,7 @@ fn state_tags_are_distinct() { tag(ParallelHashXOF128::new(nz(8), b"")), tag(TupleHashXOF128::new(b"").into_squeezer()), tag(CSHAKE256::new(b"", b"S")), - tag(KMAC256::new(&key()).unwrap()), + tag(KMAC256Key::from_key_material(&key()).unwrap().create_generator()), tag(KMACXOF256::new(&key(), b"", false).unwrap()), tag(TupleHash256::new(b"", 32)), tag(TupleHashXOF256::new(b"")), diff --git a/crypto/sm3/benches/hmac_sm3_benches.rs b/crypto/sm3/benches/hmac_sm3_benches.rs index 97cabfbb2..95e7bc50c 100644 --- a/crypto/sm3/benches/hmac_sm3_benches.rs +++ b/crypto/sm3/benches/hmac_sm3_benches.rs @@ -1,7 +1,9 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -use bouncycastle_core::traits::{MAC, RNG}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::RNG; +use bouncycastle_hmac::HmacKey; use bouncycastle_rng as rng; -use bouncycastle_sm3::hmac::HMAC_SM3; +use bouncycastle_sm3::SM3; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; @@ -15,13 +17,14 @@ fn bench_hmac_sm3(c: &mut Criterion) { } let hmac_key = KeyMaterial256::from_bytes_as_type(&data_block[..32], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&hmac_key).unwrap(); let mut out = [0u8; 32]; - let mut group = c.benchmark_group("hmac::HMAC_SM3::mac_out() -- 16x1024 one-shot"); + let mut group = c.benchmark_group("hmac::HmacKey::mac_out() -- 16x1024 one-shot"); group.throughput(Throughput::Bytes(big_data.len() as u64)); - group.bench_function(format!("{} bytes -- ::hashes()", big_data.len() as u64), |b| { + group.bench_function(format!("{} bytes -- key.mac_out()", big_data.len() as u64), |b| { b.iter(|| { - HMAC_SM3::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + key.mac_out(black_box(&big_data), &mut out).unwrap(); black_box(&out); }) }); diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 11f556217..0d928fd72 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -11,18 +11,19 @@ //! //! A key of any length is accepted: per RFC 2104, a key longer than the hash's block -- the 512-bit //! block GB/T 32905-2016 s. 5.2 pads to -- is pre-hashed to the output length. The key's two pad -//! states are computed once, when an [`HmacKey`](bouncycastle_hmac::HmacKey) is built or a worker -//! is constructed from key material. +//! states are computed once, when the [`HmacKey`] is built. //! //! # Usage Examples //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_sm3::hmac::HMAC_SM3; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_sm3::SM3; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x0b; 32], KeyType::MACKey).unwrap(); -//! let tag = HMAC_SM3::new(&key).unwrap().mac(b"Hi There"); +//! let key_material = KeyMaterial256::from_bytes_as_type(&[0x0b; 32], KeyType::MACKey).unwrap(); +//! let key = HmacKey::::from_key_material(&key_material).unwrap(); +//! let tag = key.mac(b"Hi There"); //! assert_eq!(tag.len(), 32); //! ``` //! @@ -52,7 +53,7 @@ use bouncycastle_core::key_material::KeyType; #[allow(unused_imports)] use bouncycastle_core::traits::MAC; #[allow(unused_imports)] -use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; +use bouncycastle_hmac::{HmacKey, MIN_FIPS_DIGEST_LEN}; /*** end of doc-only imports ***/ /*** String constants ***/ @@ -64,7 +65,7 @@ pub const HKDF_SM3_NAME: &str = "HKDF-SM3"; /*** Type aliases ***/ /// Public type for HMAC using SM3. #[allow(non_camel_case_types)] -pub type HMAC_SM3 = HMAC; +pub type HMAC_SM3<'k> = HMAC<'k, SM3>; impl HMACParams for SM3 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SM3_NAME; diff --git a/crypto/utils/src/suspendable_state.rs b/crypto/utils/src/suspendable_state.rs index d22ce2e9c..9e676bd24 100644 --- a/crypto/utils/src/suspendable_state.rs +++ b/crypto/utils/src/suspendable_state.rs @@ -35,7 +35,7 @@ //! /// A toy: a counter that must never exceed 100, and a key it is checked against on resume. //! struct Counter { count: usize } //! -//! impl SuspendableComponent for Counter { +//! impl<'k> SuspendableComponent<'k> for Counter { //! const STATE_LEN: usize = 8; //! type Key = u8; //! fn write_state(&self, out: &mut [u8]) { @@ -185,10 +185,12 @@ pub fn check_lib_ver( /// `write_state` and `read_state` are given exactly [`STATE_LEN`](Self::STATE_LEN) bytes. The /// version header is not part of it: a composite writes one header for the whole state, through /// [`suspend_component`] and [`resume_component`]. -pub trait SuspendableComponent: Sized { +pub trait SuspendableComponent<'k>: Sized { /// The number of bytes `write_state` fills and `read_state` reads. const STATE_LEN: usize; - /// The key that must be re-supplied to resume. It is never written into the state. + /// The key that must be re-supplied to resume. It is never written into the state. A + /// component that borrows its key for `'k` names the key's type here and is rebuilt over the + /// reference it is given; one that owns a copy ignores the lifetime. type Key: ?Sized; /// Writes the state into `out`, which is exactly `STATE_LEN` bytes. fn write_state(&self, out: &mut [u8]); @@ -197,13 +199,15 @@ pub trait SuspendableComponent: Sized { /// # Errors /// [`SuspendableError::InvalidData`] if `state` is not one this component could have /// written, or `key` is not a key the component accepts. - fn read_state(state: &[u8], key: &Self::Key) -> Result; + fn read_state(state: &[u8], key: &'k Self::Key) -> Result; } /// The `suspend` of a component: the version header, then its state. /// /// `N` must be `LIB_VERSION_LEN + C::STATE_LEN`, checked at compile time. -pub fn suspend_component(component: &C) -> [u8; N] { +pub fn suspend_component<'k, C: SuspendableComponent<'k>, const N: usize>( + component: &C, +) -> [u8; N] { const { assert!( N == LIB_VERSION_LEN + C::STATE_LEN, @@ -222,9 +226,9 @@ pub fn suspend_component(component: &C) /// # Errors /// [`SuspendableError::IncompatibleVersion`] from the header check, otherwise whatever /// [`SuspendableComponent::read_state`] returns. -pub fn resume_component( +pub fn resume_component<'k, C: SuspendableComponent<'k>, const N: usize>( state: &[u8; N], - key: &C::Key, + key: &'k C::Key, ) -> Result { const { assert!( diff --git a/mem_usage_benches/src/bench_aes_mem_usage.rs b/mem_usage_benches/src/bench_aes_mem_usage.rs index 40cd2b2e4..ff0bd6d61 100644 --- a/mem_usage_benches/src/bench_aes_mem_usage.rs +++ b/mem_usage_benches/src/bench_aes_mem_usage.rs @@ -47,7 +47,7 @@ #![allow(unused_imports)] use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle::core::hazmat::ElectronicCodeBook; +use bouncycastle::core::hazmat::{BlockPermutation, ElectronicCodeBook}; use bouncycastle::core::key_material::{KeyMaterial, KeyType}; /// This exists so /usr/bin/time can measure the base memory footprint of the harness itself. diff --git a/mem_usage_benches/src/bench_ascon_mem_usage.rs b/mem_usage_benches/src/bench_ascon_mem_usage.rs index e8cf7d2e9..1bdfe8000 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, AsconAEAD128Key}; 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}; @@ -68,6 +68,7 @@ use bouncycastle::cipher::{Decrypting, Encrypting}; use bouncycastle::core::errors::{HashError, RNGError, SymmetricCipherError}; use bouncycastle::core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle::core::security_strength::SecurityStrength; +use bouncycastle::core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Hash, HashOneShot, RNG, SymmetricCipherDecryptor, SymmetricCipherEncryptor, XOF, XOFSqueezer, @@ -93,8 +94,8 @@ const NONCE: [u8; NONCE_LEN] = [ /// Stands in for the caller's RNG and hands back [`NONCE`], so that the encrypting init runs /// Ascon's initialization and nothing else: a DRBG's generate would be the deepest part of that -/// chain, and the default `do_encrypt_init`'s OS entropy call is outside what the frame remarks -/// can see. Not a random number generator; it exists only for this harness. +/// chain, and a DRBG's own stack is outside what the frame remarks can see. Not a random number +/// generator; it exists only for this harness. struct FixedNonce; impl RNG for FixedNonce { @@ -181,6 +182,12 @@ fn key() -> KeyMaterial { KeyMaterial::::from_bytes_as_type(&KEY, KeyType::SymmetricCipherKey).unwrap() } +/// The key object the workers are made from, likewise in its own frame. +#[inline(never)] +fn aead_key() -> AsconAEAD128Key { + AsconAEAD128Key::from_key_material(&key()).unwrap() +} + /// A message filled at run time, so that its contents are not a constant the compiler could fold /// the whole computation over. #[inline(never)] @@ -194,7 +201,7 @@ fn message(fill: u8) -> [u8; N] { #[inline(never)] fn encryptor() -> Enc { - Enc::do_encrypt_init_rng(&key(), &mut FixedNonce).unwrap().0 + aead_key().create_encryptor(&mut FixedNonce).unwrap().0 } /// An encryptor that has taken the AAD and the message, so the final pads a partial block. @@ -209,21 +216,21 @@ fn encryptor_after_data() -> Enc { #[inline(never)] fn decryptor() -> Dec { - Dec::do_decrypt_init(&key(), &NONCE).unwrap() + aead_key().create_decryptor(&NONCE) } /// `MSG_LEN` bytes sealed under [`NONCE`] with the AAD, as ciphertext || tag. #[inline(never)] fn sealed() -> [u8; SEALED_LEN] { let mut out = [0u8; SEALED_LEN]; - Enc::encrypt_with_aad_rng_out( - &key(), - &mut FixedNonce, - &message::(0x5a), - &message::(0xa5), - &mut out, - ) - .unwrap(); + aead_key() + .encrypt_with_aad_out( + &mut FixedNonce, + &message::(0x5a), + &message::(0xa5), + &mut out, + ) + .unwrap(); out } @@ -276,16 +283,31 @@ fn cxof128_squeezer() -> AsconCXof128Squeezer { // ---- Ascon-AEAD128, encrypting ------------------------------------------------------------ -/// Initialization: the key check, then `IV || K || N` through `Ascon-p[12]`. +/// The key: the key check, in its own frame. #[inline(never)] -fn bench_aead_encrypt_init() { - eprintln!("Ascon_AEAD128::do_encrypt_init_rng"); +fn bench_aead_from_key_material() { + eprintln!("AsconAEAD128Key::from_key_material"); let k = key(); measure(|| { let op = black_box( - >::do_encrypt_init_rng + >::from_key_material + as fn(&KeyMaterial) -> Result, + ); + let key = op(&k).unwrap(); + black_box(&key); + }); +} + +/// Initialization: the nonce draw, then `IV || K || N` through `Ascon-p[12]`. +#[inline(never)] +fn bench_aead_encrypt_init() { + eprintln!("AsconAEAD128Key::create_encryptor"); + let k = aead_key(); + measure(|| { + let op = black_box( + >::create_encryptor as fn( - &KeyMaterial, + &AsconAEAD128Key, &mut dyn RNG, ) -> Result<(Enc, [u8; NONCE_LEN]), CipherErr>, ); @@ -303,7 +325,7 @@ fn bench_aead_encrypt_update_aad() { measure(move || { let mut enc = enc; let op = black_box( - >::do_update_aad + >::do_update_aad as fn(&mut Enc, &[u8]) -> Result<(), CipherErr>, ); op(&mut enc, &aad).unwrap(); @@ -320,7 +342,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 +357,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(); @@ -350,7 +372,7 @@ fn bench_aead_encrypt_final_detached() { measure(move || { let mut last = [0u8; TAG_LEN]; let op = black_box( - >::do_encrypt_final_detached_out + >::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(); @@ -361,23 +383,23 @@ fn bench_aead_encrypt_final_detached() { /// The one-shot with AAD and the tag inline: init, AAD, data and final in one call. #[inline(never)] fn bench_aead_encrypt_oneshot() { - eprintln!("Ascon_AEAD128::encrypt_with_aad_rng_out, {MSG_LEN} B"); - let k = key(); + eprintln!("AsconAEAD128Key::encrypt_with_aad_out, {MSG_LEN} B"); + let k = aead_key(); let aad = black_box(message::(0x5a)); let pt = black_box(message::(0xa5)); measure(move || { let mut ct = [0u8; SEALED_LEN]; let op = black_box( - >::encrypt_with_aad_rng_out + >::encrypt_with_aad_out as fn( - &KeyMaterial, + &AsconAEAD128Key, &mut dyn RNG, &[u8], &[u8], &mut [u8], - ) -> Result<([u8; NONCE_LEN], usize), CipherErr>, + ) -> Result<(usize, [u8; NONCE_LEN]), CipherErr>, ); - let (_, n) = op(&k, &mut FixedNonce, &aad, &pt, &mut ct).unwrap(); + let (n, _) = op(&k, &mut FixedNonce, &aad, &pt, &mut ct).unwrap(); print!("{:x?}", &ct[..n]); }); } @@ -386,14 +408,14 @@ fn bench_aead_encrypt_oneshot() { #[inline(never)] fn bench_aead_decrypt_init() { - eprintln!("Ascon_AEAD128::do_decrypt_init"); - let k = key(); + eprintln!("AsconAEAD128Key::create_decryptor"); + let k = aead_key(); measure(|| { let op = black_box( - >::do_decrypt_init - as fn(&KeyMaterial, &[u8; NONCE_LEN]) -> Result, + >::create_decryptor + as fn(&AsconAEAD128Key, &[u8; NONCE_LEN]) -> Dec, ); - let dec = op(&k, &NONCE).unwrap(); + let dec = op(&k, &NONCE); black_box(&dec); }); } @@ -406,7 +428,7 @@ fn bench_aead_decrypt_update_aad() { measure(move || { let mut dec = dec; let op = black_box( - >::do_update_aad + >::do_update_aad as fn(&mut Dec, &[u8]) -> Result<(), CipherErr>, ); op(&mut dec, &aad).unwrap(); @@ -425,7 +447,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 +462,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(); @@ -457,7 +479,7 @@ fn bench_aead_decrypt_final_detached() { measure(move || { let mut last = [0u8; TAG_LEN]; let op = black_box( - >::do_decrypt_final_detached_out + >::do_decrypt_final_detached_out as fn(Dec, &[u8; TAG_LEN], &mut [u8; TAG_LEN]) -> Result, ); let n = op(dec, &tag, &mut last).unwrap(); @@ -468,16 +490,16 @@ fn bench_aead_decrypt_final_detached() { /// The one-shot with AAD and the tag inline. #[inline(never)] fn bench_aead_decrypt_oneshot() { - eprintln!("Ascon_AEAD128::decrypt_with_aad_out, {SEALED_LEN} B"); - let k = key(); + eprintln!("AsconAEAD128Key::decrypt_with_aad_out, {SEALED_LEN} B"); + let k = aead_key(); let aad = black_box(message::(0x5a)); let ct = black_box(sealed()); measure(move || { let mut pt = [0u8; MSG_LEN]; let op = black_box( - >::decrypt_with_aad_out + >::decrypt_with_aad_out as fn( - &KeyMaterial, + &AsconAEAD128Key, &[u8; NONCE_LEN], &[u8], &[u8], @@ -642,6 +664,7 @@ fn bench_cxof128_oneshot() { fn main() { print_struct_sizes() // bench_do_nothing() + // bench_aead_from_key_material() // bench_aead_encrypt_init() // bench_aead_encrypt_update_aad() // bench_aead_encrypt_out() diff --git a/mem_usage_benches/src/bench_ccm_mem_usage.rs b/mem_usage_benches/src/bench_ccm_mem_usage.rs index b659a3a8f..22a187913 100644 --- a/mem_usage_benches/src/bench_ccm_mem_usage.rs +++ b/mem_usage_benches/src/bench_ccm_mem_usage.rs @@ -28,15 +28,18 @@ #![allow(dead_code)] #![allow(unused_imports)] +use bouncycastle::aes::AES128Key; use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle::cipher::modes::{CCM, CcmDecryptor, CcmEncryptor}; +use bouncycastle::cipher::modes::{CCM, CCM_Packet, CcmPacketDecryptor, CcmPacketEncryptor}; use bouncycastle::core::hazmat::{ - AeadPacketDecryptMode, AeadPacketEncryptMode, ElectronicCodeBook, + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, }; use bouncycastle::core::key_material::{KeyMaterial, KeyType}; +use bouncycastle::core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; use bouncycastle::core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; +use bouncycastle::rng::HashDRBG_SHA512; /// The parameters the ACVP vectors and most protocols use: 12-byte nonce, 16-byte tag. const NONCE_LEN: usize = 12; @@ -53,10 +56,12 @@ const MESSAGE_LEN: usize = DATA_LEN; /// The mode's in-place one-shots over the keyed permutation: the direct path. type Aes128CcmMode = CCM; -type Aes128CcmEncryptor = - CcmEncryptor; -type Aes128CcmDecryptor = - CcmDecryptor; +/// The key bound to the fixed frame, whose streams are the pair below. +type Aes128CcmPacketKey = AES128Key>; +type Aes128CcmEncryptor<'k> = + CcmPacketEncryptor<'k, AES128Internal, 16, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>; +type Aes128CcmDecryptor<'k> = + CcmPacketDecryptor<'k, AES128Internal, 16, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN>; fn key() -> KeyMaterial { KeyMaterial::::from_bytes_as_type(&[0x42u8; N], KeyType::SymmetricCipherKey).unwrap() @@ -66,6 +71,10 @@ fn perm() -> AES128Internal { AES128Internal::new(&key::<16>()).unwrap() } +fn frame_key() -> Aes128CcmPacketKey { + Aes128CcmPacketKey::from_key_material(&key::<16>()).unwrap() +} + /// The message every bench processes, filled at run time and then only ever reached through a /// `black_box`ed reference, so that it is a whole stack array in every bench alike. Without that, /// a `[0xA5; N]` literal is a constant the compiler may keep in read-only data in one bench, or @@ -87,7 +96,7 @@ fn bench_do_nothing() { /// Prints the in-memory size of each CCM value: the persistent cost of holding one open. /// -/// A mode-bound key is the keyed permutation alone; the trait pair is the mode's packet state +/// A mode-bound key is the keyed permutation alone; the trait pair is the mode's `Ccm` state /// (the permutation, three blocks and five counters, independent of `NONCE_LEN` and `TAG_LEN`) /// plus the `AAD_LEN` buffer and a few words, at any `DATA_LEN`. #[inline(never)] @@ -100,27 +109,34 @@ fn print_struct_sizes() { eprintln!("AES256Internal {:>7} B", size_of::()); eprintln!( - "--- the trait pair: packet state + AAD_LEN + bookkeeping, independent of DATA_LEN ---" + "--- the trait pair: `Ccm` state + AAD_LEN + bookkeeping, independent of DATA_LEN ---" + ); + eprintln!( + "CcmPacketEncryptor<'_, .., {AAD_LEN}, {DATA_LEN}> {:>7} B", + size_of::() + ); + eprintln!( + "CcmPacketDecryptor<'_, .., {AAD_LEN}, {DATA_LEN}> {:>7} B", + size_of::() ); - eprintln!("CcmEncryptor<.., {AAD_LEN}, {DATA_LEN}> {:>7} B", size_of::()); - eprintln!("CcmDecryptor<.., {AAD_LEN}, {DATA_LEN}> {:>7} B", size_of::()); eprintln!( - "CcmEncryptor<.., 64, 240> {:>7} B", - size_of::>() + "CcmPacketEncryptor<'_, .., 64, 240> {:>7} B", + size_of::>() ); eprintln!( - "CcmEncryptor<.., 7, 4, ..> {:>7} B", - size_of::>() + "CcmPacketEncryptor<'_, .., 7, 4, ..> {:>7} B", + size_of::>() ); eprintln!( - "CcmEncryptor {:>7} B", - size_of::>() + "CcmPacketEncryptor<'_, AES256Internal> {:>7} B", + size_of::>( + ) ); print!("{}", size_of::()); } -/// The in-place path over the message: the packet state plus the caller's one buffer, and +/// The in-place path over the message: the `Ccm` state plus the caller's one buffer, and /// nothing else. This is the baseline for `bench_streaming_encrypt`, `bench_streaming_decrypt` /// and `bench_oneshot_encrypt_out_detached`. #[inline(never)] @@ -135,20 +151,21 @@ fn bench_in_place_encrypt() { print!("{:x?}", &tag); } -/// The same message through the trait encryptor's **streaming** methods: `do_encrypt_init` -/// builds the value, `do_update_out` writes each chunk's ciphertext straight out, and the final -/// returns the tag. The caller's two arrays are the whole of the stack that scales. +/// The same message through the frame key's **streaming** encryptor: `create_encryptor` builds +/// the value under a drawn nonce, `do_encrypt_out` writes each chunk's ciphertext straight out, +/// and the final returns the tag. The caller's two arrays are the whole of the stack that scales. #[inline(never)] fn bench_streaming_encrypt() { eprintln!( - "CcmEncryptor do_encrypt_init/do_update_out/do_encrypt_final_detached_out, {MESSAGE_LEN} B in 1 KiB chunks" + "CcmPacketEncryptor create_encryptor/do_encrypt_out/do_encrypt_final_detached_out, {MESSAGE_LEN} B in 1 KiB chunks" ); - let k = key::<16>(); + let k = frame_key(); + let mut rng = HashDRBG_SHA512::new_from_os(); let plaintext = message(); let plaintext = core::hint::black_box(&plaintext); let mut ciphertext = [0u8; MESSAGE_LEN]; - let (mut enc, _nonce) = Aes128CcmEncryptor::do_encrypt_init(&k).unwrap(); + let (mut enc, _nonce) = k.create_encryptor(&mut rng).unwrap(); let mut written = 0; for chunk in plaintext.chunks(1024) { written += enc.do_encrypt_out(chunk, &mut ciphertext[written..]).unwrap(); @@ -166,10 +183,10 @@ fn bench_streaming_encrypt() { #[inline(never)] fn bench_streaming_decrypt() { eprintln!( - "CcmDecryptor do_decrypt_init/do_update_out/do_decrypt_final, {MESSAGE_LEN} B in 1 KiB chunks" + "CcmPacketDecryptor create_decryptor/do_decrypt_out/do_decrypt_final, {MESSAGE_LEN} B in 1 KiB chunks" ); - let k = key::<16>(); + let k = frame_key(); let nonce = [0x24u8; NONCE_LEN]; let mut sealed = [0u8; MESSAGE_LEN + TAG_LEN]; sealed[..MESSAGE_LEN].fill(core::hint::black_box(0xA5)); @@ -184,7 +201,7 @@ fn bench_streaming_decrypt() { let sealed = core::hint::black_box(&sealed); let mut opened = [0u8; MESSAGE_LEN]; - let mut dec = Aes128CcmDecryptor::do_decrypt_init(&k, &nonce).unwrap(); + let mut dec = k.create_decryptor(&nonce); let mut written = 0; for chunk in sealed.chunks(1024) { written += dec.do_decrypt_out(chunk, &mut opened[written..]).unwrap(); @@ -193,20 +210,19 @@ fn bench_streaming_decrypt() { print!("{}", written + m); } -/// The trait encryptor's **one-shot**, which runs the packet one-shot into the caller's output -/// buffer under a generated nonce, so it should measure what `bench_in_place_encrypt` measures -/// plus a second array and the DRBG the nonce is drawn from -- no adapter value and no `AAD_LEN` -/// buffer. +/// The frame key's **one-shot**, written over its stream, into the caller's output buffer under +/// a nonce drawn from the caller's DRBG, so it should measure what `bench_streaming_encrypt` +/// measures plus that DRBG. #[inline(never)] fn bench_oneshot_encrypt_out_detached() { - eprintln!("CcmEncryptor::encrypt_detached_out, {MESSAGE_LEN} B"); + eprintln!("AES128Key::encrypt_detached_out, {MESSAGE_LEN} B"); - let k = key::<16>(); + let k = frame_key(); + let mut rng = HashDRBG_SHA512::new_from_os(); let plaintext = message(); let plaintext = core::hint::black_box(&plaintext); let mut ciphertext = [0u8; MESSAGE_LEN]; - let (_, _, tag) = - Aes128CcmEncryptor::encrypt_detached_out(&k, &[], plaintext, &mut ciphertext).unwrap(); + let (_, _, tag) = k.encrypt_detached_out(&mut rng, &[], plaintext, &mut ciphertext).unwrap(); print!("{:x?}", &tag); } diff --git a/mem_usage_benches/src/bench_sha2_mem_usage.rs b/mem_usage_benches/src/bench_sha2_mem_usage.rs index 6eacec986..5bd573626 100644 --- a/mem_usage_benches/src/bench_sha2_mem_usage.rs +++ b/mem_usage_benches/src/bench_sha2_mem_usage.rs @@ -26,6 +26,7 @@ #![allow(unused_imports)] use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; use bouncycastle::core::traits::{Hash, HashOneShot, MAC, Suspendable}; use bouncycastle::hmac::HmacKey; use bouncycastle::sha2::hmac::{HMAC_SHA256, HMAC_SHA512}; @@ -109,8 +110,9 @@ fn bench_hmac_sha256() { eprintln!("HMAC-SHA-256/mac_out"); let key = KeyMaterial256::from_bytes_as_type(&MSG[..32], KeyType::MACKey).unwrap(); + let key = HmacKey::::from_key_material(&key).unwrap(); let mut out = [0u8; 32]; - HMAC_SHA256::new(&key).unwrap().mac_out(&MSG, &mut out).unwrap(); + key.mac_out(&MSG, &mut out).unwrap(); println!("{:x?}", out); } diff --git a/mem_usage_benches/src/bench_sha3_mem_usage.rs b/mem_usage_benches/src/bench_sha3_mem_usage.rs index d9ec2a5e2..1cecd2333 100644 --- a/mem_usage_benches/src/bench_sha3_mem_usage.rs +++ b/mem_usage_benches/src/bench_sha3_mem_usage.rs @@ -26,7 +26,7 @@ #![allow(unused_imports)] use bouncycastle::core::traits::{Hash, HashOneShot, Suspendable, XOF, XOFSqueezer}; -use bouncycastle::sha3::kmac::{KMAC128, KMAC256, KMACXOF128, KMACXOF256}; +use bouncycastle::sha3::kmac::{KMAC128, KMAC128Key, KMAC256, KMAC256Key, KMACXOF128, KMACXOF256}; use bouncycastle::sha3::parallelhash::{ ParallelHash128, ParallelHash256, ParallelHashXOF128, ParallelHashXOF256, }; @@ -56,6 +56,8 @@ fn print_struct_sizes() { println!("\nSP 800-185"); println!("size_of: {}", size_of::()); println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); println!("size_of: {}", size_of::()); println!("size_of: {}", size_of::()); println!("size_of: {}", size_of::());