diff --git a/CLAUDE.md b/CLAUDE.md index 908439d61..17c5b7bdf 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. + ## Working from specifications QUALITY_AND_STYLE.md is where the requirement for spec-corresponding comments and justified deviations lives. This @@ -243,4 +293,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/Cargo.toml b/Cargo.toml index 11e1f13cc..1b5d9ecec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ bouncycastle = { path = "./" } bouncycastle-aes = { path = "./crypto/aes" } bouncycastle-ascon = { path = "./crypto/ascon" } bouncycastle-base64 = { path = "./crypto/base64" } +bouncycastle-blake2 = { path = "./crypto/blake2" } +bouncycastle-blake3 = { path = "./crypto/blake3" } bouncycastle-cipher = { path = "./crypto/cipher" } bouncycastle-core = { path = "crypto/core" } bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" } @@ -24,6 +26,7 @@ bouncycastle-hmac = { path = "./crypto/hmac" } bouncycastle-mlkem = { path = "./crypto/mlkem" } bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory" } bouncycastle-mldsa = { path = "./crypto/mldsa" } +bouncycastle-mldsa-common = { path = "./crypto/mldsa-common" } bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory" } bouncycastle-rng = { path = "./crypto/rng" } bouncycastle-sha2 = { path = "./crypto/sha2" } @@ -50,6 +53,8 @@ edition.workspace = true bouncycastle-aes.workspace = true bouncycastle-ascon.workspace = true bouncycastle-base64.workspace = true +bouncycastle-blake2.workspace = true +bouncycastle-blake3.workspace = true bouncycastle-cipher.workspace = true bouncycastle-core.workspace = true bouncycastle-factory.workspace = true diff --git a/INTRODUCTION.md b/INTRODUCTION.md index 270c3720a..891af1347 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -114,7 +114,7 @@ pub type SHAKE128 = SHAKE; pub type SHAKE256 = SHAKE; ``` -so that in the end `SHA3_256::new().hash(&data)` just does what you expect. The "If it compiles, then it's safe" +so that in the end `SHA3_256::hash(&data)` just does what you expect. The "If it compiles, then it's safe" paradigm is, however, still somewhat aspirational and not a total _fait accompli_, and as the library matures, we will continue to find ways to refine our type system to turn ever more runtime error conditions into compile-time conditions. diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 458177530..ad10025ef 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. # Quality @@ -88,7 +92,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, @@ -123,17 +133,21 @@ 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 -* `_init / _update / _final`: indicates phase of a stateful streaming API. Other verbs can be used here as appropriate - to the primitive, such as `absorb / squeeze`, `encrypt / decrypt`, etc. `_init` is typically a static constructor - (though exceptions may exist), and `_final` indicates that this function call renders the object unusable afterwards - by consuming `self` via a move: `_final(self, ..)`. +* `_update / _final`: indicates phase of a stateful streaming API. Other verbs can be used here as appropriate to the + primitive, such as `absorb / squeeze`, `encrypt / decrypt`, etc. `_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 89d3c28a5..d2592dc08 100644 --- a/cli/src/aes_ccm_cmd.rs +++ b/cli/src/aes_ccm_cmd.rs @@ -19,11 +19,9 @@ //! command to point at a multi-gigabyte file. `aes256-ctr` piped through a separate MAC, or //! `ascon-aead128`, are the streaming alternatives. //! -//! The AAD is different: `--aad-file` is streamed. CCM needs the AAD's length before its first -//! byte (A.2.2 puts the encoding of `a` in front of `A`), and a regular file's size is known -//! before it is read, so the file is declared by its size and fed to the MAC in 1 KiB chunks -//! without ever being held whole. A file with no size to declare -- a pipe, `/dev/stdin` -- is -//! read whole instead. +//! 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 type +//! keeps for itself.) //! //! # The nonce is supplied, not generated //! @@ -42,32 +40,53 @@ //! `--nonce` must be 7..=13 bytes and `--tag-len` one of 4, 6, 8, 10, 12, 14, 16, both from //! Appendix A.1. The nonce length fixes the maximum payload at `2^(8 * (15 - n)) - 1` bytes, which //! this command checks against the actual input length. Because those are const generic parameters -//! of the mode, the runtime value is dispatched to one of the seven nonce lengths and seven tag +//! of the key type, the runtime value is dispatched to one of the seven nonce lengths and seven tag //! lengths below. //! //! The output layout is Sec 6.1 step 8's own: `ciphertext || tag`. -use std::fs::File; use std::io::{self, Read}; -use std::marker::PhantomData; use std::process::exit; -use bouncycastle::aes::{ - AES_CCM_128_Packet, AES_CCM_192_Packet, AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN, -}; -use bouncycastle::cipher::modes::CcmPacket; -use bouncycastle::cipher::modes::ModeNames; -use bouncycastle::cipher::{Decrypting, Encrypting}; +use bouncycastle::aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle::cipher::modes::CCM; use bouncycastle::core::errors::SymmetricCipherError; -use bouncycastle::core::hazmat::ElectronicCodeBook; use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::symmetric_key::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial, +}; use bouncycastle::hex; use crate::helpers; -use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_key}; +use crate::helpers::block_mode_helpers::{CipherDirection, load_key}; + +/// The CCM key type for one AES key length, at every nonce and tag length `run` can dispatch to. +/// +/// The one-shots this command uses are on the key's role traits, and the key type carries the +/// nonce and tag lengths as const parameters, so a generic `run` over the key length needs a way +/// to name the key type for an `(N, T)` it chooses at run time: this generic associated type. +trait CcmKeys { + /// The key type at nonce length `N` and tag length `T`. + type Key: FromKeyMaterial + + AeadDeclaredEncryptionKey + + AeadDeclaredDecryptionKey; +} + +struct Aes128; +struct Aes192; +struct Aes256; -/// Bytes of `--aad-file` read per call, matching the other commands' streaming chunk. -const CHUNK_LEN: usize = 1024; +impl CcmKeys<16> for Aes128 { + type Key = AES128Key>; +} + +impl CcmKeys<24> for Aes192 { + type Key = AES192Key>; +} + +impl CcmKeys<32> for Aes256 { + type Key = AES256Key>; +} /// AES-128 CCM. See the module docs and the subcommand help. pub(crate) fn aes128_ccm_cmd( @@ -81,8 +100,7 @@ pub(crate) fn aes128_ccm_cmd( tag_len: usize, output_hex: bool, ) { - run( - PhantomData::>, + run::( action, &load_key::<16>(key, key_file, "AES-128"), nonce, @@ -106,8 +124,7 @@ pub(crate) fn aes192_ccm_cmd( tag_len: usize, output_hex: bool, ) { - run( - PhantomData::>, + run::( action, &load_key::<24>(key, key_file, "AES-192"), nonce, @@ -131,8 +148,7 @@ pub(crate) fn aes256_ccm_cmd( tag_len: usize, output_hex: bool, ) { - run( - PhantomData::>, + run::( action, &load_key::<32>(key, key_file, "AES-256"), nonce, @@ -196,98 +212,18 @@ fn load_nonce(nonce: &Option, nonce_file: &Option) -> Vec { bytes } -/// Where the AAD comes from. -enum Aad { - /// `--aad` (hex), a `--aad-file` with no size to declare, or no AAD at all: held whole. - Bytes(Vec), - /// A `--aad-file` that is a regular file: declared by its size and read in [`CHUNK_LEN`] - /// pieces by [`feed_aad`], never held whole. - File { file: File, path: String, len: usize }, -} - -impl Aad { - /// The AAD length to declare to [`CcmPacket::new_with_lengths`]. - fn len(&self) -> usize { - match self { - Aad::Bytes(bytes) => bytes.len(), - Aad::File { len, .. } => *len, - } - } -} - /// Loads the AAD from `--aad-file` (raw bytes, never hex-decoded) or `--aad` (hex); the file wins /// if both are given, as for `aes*-gcm`. Empty if neither is given. -/// -/// A regular file is only opened and sized here; [`feed_aad`] reads it. Anything else -- a pipe, a -/// character device -- has no size to declare in advance, so it is read whole. -fn load_aad(aad: &Option, aad_file: &Option) -> Aad { +fn load_aad(aad: &Option, aad_file: &Option) -> Vec { if let Some(path) = aad_file { - let file = File::open(path).unwrap_or_else(|e| { - eprintln!("Error: couldn't read file '{path}': {e}"); - exit(-1) - }); - let metadata = file.metadata().unwrap_or_else(|e| { - eprintln!("Error: couldn't read file '{path}': {e}"); - exit(-1) - }); - if !metadata.is_file() { - return Aad::Bytes(helpers::read_from_file_raw(path)); - } - let Ok(len) = usize::try_from(metadata.len()) else { - eprintln!("Error: AAD file '{path}' is too large for this platform."); - exit(-1) - }; - Aad::File { file, path: path.clone(), len } + helpers::read_from_file_raw(path) } else if let Some(v) = aad { - Aad::Bytes(hex::decode(v).unwrap_or_else(|_| { + hex::decode(v).unwrap_or_else(|_| { eprintln!("Error: associated data is not valid hex. Use --aad-file for raw bytes."); exit(-1) - })) + }) } else { - Aad::Bytes(Vec::new()) - } -} - -/// Supplies all of the AAD declared as [`Aad::len`] to `ccm`, reading an [`Aad::File`] a chunk at -/// a time. -/// -/// The declared length is the file's size when it was opened. If the file changes size while it -/// is being read, the declared length is wrong, and the tag would be computed over a length -/// encoding that does not match the AAD; that is reported and the command exits rather than -/// producing it. -fn feed_aad( - ccm: &mut CcmPacket, - aad: &mut Aad, -) where - P: ElectronicCodeBook + ModeNames, -{ - match aad { - Aad::Bytes(bytes) => { - // Declared as exactly `bytes.len()`, and supplied in this one call. - ccm.do_update_aad(bytes).expect("declared AAD length matches what was sent"); - } - Aad::File { file, path, len } => { - let mut buf = [0u8; CHUNK_LEN]; - let mut read = 0usize; - loop { - let n = file.read(&mut buf).unwrap_or_else(|e| { - eprintln!("Error: couldn't read file '{path}': {e}"); - exit(-1) - }); - if n == 0 { - break; - } - read += n; - if ccm.do_update_aad(&buf[..n]).is_err() { - eprintln!("Error: AAD file '{path}' grew while it was being read."); - exit(-1) - } - } - if read != *len { - eprintln!("Error: AAD file '{path}' shrank while it was being read."); - exit(-1) - } - } + Vec::new() } } @@ -300,16 +236,11 @@ fn read_all_stdin() -> Vec { /// Turns the runtime nonce and tag lengths into the mode's const generic parameters. /// -/// `_aes` is the caller's `AES_CCM_*_Packet` type at the default lengths, and serves only to fix -/// the block cipher `P` by inference, so that each entry point names the public alias rather than -/// the cipher inside it; the lengths actually used are dispatched on below. -/// -/// `NONCE_LEN` and `TAG_LEN` are const parameters of `CcmPacket` -- that is what makes A.1's length -/// conditions compile-time checks rather than runtime ones -- so a command-line value has to be -/// matched into one of the permitted instantiations. The two nested matches are the price of that, -/// and they are exhaustive over A.1's sets: 7 nonce lengths x 7 tag lengths. -fn run( - _aes: PhantomData>, +/// `NONCE_LEN` and `TAG_LEN` are const parameters of the key type -- that is what makes A.1's +/// length conditions compile-time checks rather than runtime ones -- so a command-line value has to +/// be matched into one of the permitted instantiations. The two nested matches are the price of +/// that, and they are exhaustive over A.1's sets: 7 nonce lengths x 7 tag lengths. +fn run, const KEY_LEN: usize>( action: &CipherDirection, key: &KeyMaterial, nonce: &Option, @@ -318,9 +249,7 @@ fn run( aad_file: &Option, tag_len: usize, output_hex: bool, -) where - P: ElectronicCodeBook + ModeNames, -{ +) { // Reject this before opening nonce/AAD files or waiting for stdin. Appendix A.1: "t is an // element of {4, 6, 8, 10, 12, 14, 16}". if !matches!(tag_len, 4 | 6 | 8 | 10 | 12 | 14 | 16) { @@ -332,34 +261,20 @@ fn run( } let nonce_bytes = load_nonce(nonce, nonce_file); - let mut aad = load_aad(aad, aad_file); + let aad = load_aad(aad, aad_file); let input = read_all_stdin(); let encrypt = matches!(action, CipherDirection::Encrypt); macro_rules! with_tag_len { ($n:literal) => { match tag_len { - 4 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 6 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 8 => { - go::(key, &nonce_bytes, &mut aad, input, encrypt, output_hex) - } - 10 => go::( - key, &nonce_bytes, &mut aad, input, encrypt, output_hex, - ), - 12 => go::( - key, &nonce_bytes, &mut aad, input, encrypt, output_hex, - ), - 14 => go::( - key, &nonce_bytes, &mut aad, input, encrypt, output_hex, - ), - 16 => go::( - key, &nonce_bytes, &mut aad, input, encrypt, output_hex, - ), + 4 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 6 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 8 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 10 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 12 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 14 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), + 16 => go::(key, &nonce_bytes, &aad, input, encrypt, output_hex), _ => unreachable!("tag length was validated before stdin was read"), } }; @@ -382,24 +297,19 @@ fn run( } } -/// Reports [`CcmPacket::new_with_lengths`]'s refusal of a payload past the `q` limit and exits. +/// Reports the one-shots' refusal of a payload past the `q` limit and exits. /// -/// The only [`SymmetricCipherError::GenericError`] it can return is that limit: A.1's +/// The only [`SymmetricCipherError::GenericError`] they can return is that limit: A.1's /// `p < 2^8q`, where `q = 15 - n`. Both directions hit it -- the decrypt side on the input minus /// its tag -- so both report it here, with the numbers, since the fix is a shorter nonce. -fn payload_past_the_q_limit( - msg: &str, - payload_len: usize, -) -> ! -where - P: ElectronicCodeBook + ModeNames, -{ +fn payload_past_the_q_limit(msg: &str, payload_len: usize) -> ! { + let q = 15 - NONCE_LEN; + // `2^8q - 1`; at n = 7, q = 8 and the limit is every u64. + let limit = if q >= 8 { u64::MAX } else { (1u64 << (8 * q)) - 1 }; eprintln!("Error: {msg}"); eprintln!( - " Payload is {payload_len} bytes; with a {NONCE_LEN}-byte nonce, q = {} and the \ - limit is {} bytes.", - 15 - NONCE_LEN, - CcmPacket::::MAX_PAYLOAD_LEN, + " Payload is {payload_len} bytes; with a {NONCE_LEN}-byte nonce, q = {q} and the \ + limit is {limit} bytes." ); eprintln!(" Use a shorter nonce for a larger payload."); exit(-1) @@ -407,47 +317,32 @@ where /// One fully-instantiated CCM run. /// -/// `input` is processed in place through [`CcmPacket`]'s own streaming API rather than through the -/// one-shot [`CcmPacket::encrypt_out`]/[`CcmPacket::decrypt_out`], which each need a second, freshly allocated buffer -/// the size of `input`: the declared-length constructor already has everything a one-shot needs, -/// so there is no second buffer to allocate or copy into. The AAD goes in through -/// [`CcmPacket::new_with_lengths`] and [`feed_aad`], so a `--aad-file` is streamed rather than loaded. -fn go( +/// `input` is processed in place through the key's in-place one-shots, so there is no second +/// buffer the size of `input` to allocate or copy into, and on the decrypt side nothing is written +/// to stdout until the tag has verified: the one-shot zeroizes the buffer on a failed check, so +/// Sec 6.2's "the payload P and the MAC T shall not be revealed" holds end to end. +fn go, const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize>( key: &KeyMaterial, nonce_bytes: &[u8], - aad: &mut Aad, + aad: &[u8], mut input: Vec, encrypt: bool, output_hex: bool, -) where - P: ElectronicCodeBook + ModeNames, -{ - type Enc = - CcmPacket; - type Dec = - CcmPacket; - +) { // `run` dispatched on this exact length, so the conversion cannot fail. let Ok(nonce) = <[u8; NONCE_LEN]>::try_from(nonce_bytes) else { eprintln!("Error: internal nonce length mismatch."); exit(-1) }; + // `load_key` has already checked the length and type, so this is the schedule expansion. + let key = F::Key::::from_key_material(key).unwrap_or_else(|e| { + eprintln!("Error: AES-CCM key is not usable: {e:?}"); + exit(-1) + }); if encrypt { - match Enc::::new_with_lengths( - key, - &nonce, - aad.len(), - input.len(), - ) { - Ok(mut ccm) => { - feed_aad(&mut ccm, aad); - // `new` already accepted this exact length as `input.len()`, and this is the one - // and only call supplying it, so `take_owed` can never see too much and `owed` - // can never be left nonzero: neither of these can fail on the path that reaches - // them. - ccm.do_encrypt_inplace(&mut input).expect("declared length matches what was sent"); - let tag = ccm.do_encrypt_final().expect("declared length was fully supplied"); + match key.encrypt_detached_in_place_nonce(&nonce, aad, &mut input) { + Ok(tag) => { helpers::write_bytes_or_hex(&input, output_hex); helpers::write_bytes_or_hex(&tag, output_hex); if output_hex { @@ -455,7 +350,7 @@ fn go( } } Err(SymmetricCipherError::GenericError(msg)) => { - payload_past_the_q_limit::(msg, input.len()) + payload_past_the_q_limit::(msg, input.len()) } Err(e) => { eprintln!("Error: AES-CCM encryption failed: {e:?}"); @@ -464,8 +359,8 @@ fn go( } } else { // `split_last_chunk_mut` is `None` exactly when there is no room for a `TAG_LEN`-byte tag, - // which is the same octet-level test (and the same allowance for an empty payload plus its - // tag) that `CcmPacket::decrypt_out`'s own doc comment explains for Sec 6.2 step 1. + // the same octet-level test (and the same allowance for an empty payload plus its tag) + // as Sec 6.2 step 1. let Some((data, tag)) = input.split_last_chunk_mut::() else { eprintln!( "Error: input is {} bytes, shorter than the {TAG_LEN}-byte tag it must end with.", @@ -473,41 +368,19 @@ fn go( ); exit(-1) }; - match Dec::::new_with_lengths( - key, - &nonce, - aad.len(), - data.len(), - ) { - Ok(mut ccm) => { - feed_aad(&mut ccm, aad); - // As the encrypt arm above: `data.len()` is exactly the length just declared, and - // it is supplied in this one call, so this cannot fail. - ccm.do_decrypt_inplace(data).expect("declared length matches what was sent"); - match ccm.do_decrypt_final(tag) { - Ok(()) => { - helpers::write_bytes_or_hex(data, output_hex); - if output_hex { - crate::helpers::write_stdout(b"\n"); - } - } - Err(SymmetricCipherError::AEADTagCheckFailed) => { - // Nothing has been written to stdout at this point, which is what - // processing in place still buys here: Sec 6.2's "the payload P and the - // MAC T shall not be revealed" holds end to end. - eprintln!( - "Error: AES-CCM authentication failed; the input is not authentic." - ); - exit(-1) - } - Err(e) => { - eprintln!("Error: AES-CCM decryption failed: {e:?}"); - exit(-1) - } + match key.decrypt_detached_in_place(&nonce, aad, data, tag) { + Ok(()) => { + helpers::write_bytes_or_hex(data, output_hex); + if output_hex { + crate::helpers::write_stdout(b"\n"); } } + Err(SymmetricCipherError::AEADTagCheckFailed) => { + eprintln!("Error: AES-CCM authentication failed; the input is not authentic."); + exit(-1) + } Err(SymmetricCipherError::GenericError(msg)) => { - payload_past_the_q_limit::(msg, data.len()) + payload_past_the_q_limit::(msg, data.len()) } Err(e) => { eprintln!("Error: AES-CCM decryption failed: {e:?}"); diff --git a/cli/src/aes_cfb8_cmd.rs b/cli/src/aes_cfb8_cmd.rs index ee6690cdb..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/blake2_cmd.rs b/cli/src/blake2_cmd.rs new file mode 100644 index 000000000..20d4ed5f2 --- /dev/null +++ b/cli/src/blake2_cmd.rs @@ -0,0 +1,52 @@ +//! BLAKE2b-512 and BLAKE2s-256 (RFC 7693): the unkeyed hashes and their built-in keyed (MAC) mode. + +use bouncycastle::blake2::{BLAKE2b_512, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MACKey}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; + +use crate::helpers::{load_mac_key, stream_hash}; +use crate::mac_cmd::do_mac; + +#[allow(non_camel_case_types)] +pub(crate) enum BLAKE2Variant { + BLAKE2b_512, + BLAKE2s_256, +} + +/// BLAKE2b-512 / BLAKE2s-256 of stdin, streamed in 1 KiB pieces. +pub(crate) fn blake2_cmd(variant: BLAKE2Variant, output_hex: bool) { + match variant { + BLAKE2Variant::BLAKE2b_512 => stream_hash(BLAKE2b_512::new(), output_hex), + BLAKE2Variant::BLAKE2s_256 => stream_hash(BLAKE2s_256::new(), output_hex), + } +} + +/// Keyed BLAKE2b-512 / BLAKE2s-256 (RFC 7693 s. 3.3) of stdin: compute a tag, or with `--verify` +/// check one and report the result in the exit status. +pub(crate) fn blake2_mac_cmd( + variant: BLAKE2Variant, + key: &Option, + key_file: &Option, + verify_val: &Option, + output_hex: bool, +) { + // RFC 7693 s. 2.1 table, "Key bytes": "0 <= kk <= 64" (BLAKE2b) / "0 <= kk <= 32" (BLAKE2s). + // kk = 0 is the unkeyed hash, which is the other command, so a MAC key is at least 1 byte. + // + // The allow-weak form, as the HMAC and KMAC 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 variant { + BLAKE2Variant::BLAKE2b_512 => { + let key = load_mac_key(key, key_file, "BLAKE2b-512-MAC", 1, 64); + // infallible: a 1..=64-byte MACKey (or Zeroized) key is exactly what the key object + // accepts once the strength check is skipped. + let mac_key = BLAKE2b_512_MACKey::from_key_material_allow_weak(&key).unwrap(); + do_mac(mac_key.create_generator(), verify_val, output_hex); + } + BLAKE2Variant::BLAKE2s_256 => { + let key = load_mac_key(key, key_file, "BLAKE2s-256-MAC", 1, 32); + // infallible: as above, with the 1..=32-byte range of BLAKE2s. + let mac_key = BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).unwrap(); + do_mac(mac_key.create_generator(), verify_val, output_hex); + } + } +} diff --git a/cli/src/blake3_cmd.rs b/cli/src/blake3_cmd.rs new file mode 100644 index 000000000..30f924115 --- /dev/null +++ b/cli/src/blake3_cmd.rs @@ -0,0 +1,84 @@ +//! BLAKE3 (BLAKE3 specification, version 20211102173700): the `hash`, `keyed_hash` and +//! `derive_key` modes, each with output of any length. + +use std::io::{self, Read}; +use std::process::exit; + +use bouncycastle::blake3::{BLAKE3, BLAKE3DeriveKey, BLAKE3KeyedHashKey}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{MAC, XOFSqueezer}; + +use crate::helpers::{load_mac_key, stream_xof, write_bytes_or_hex, write_stdout}; +use crate::mac_cmd::do_mac; + +/// BLAKE3 spec s. 2.3: `keyed_hash` "takes an input of any length together with a 256-bit key". +/// The crate has no public constant for it. +const BLAKE3_KEY_LEN: usize = 32; + +/// The default-length BLAKE3 output: BLAKE3 spec s. 2.1, "The chaining value of the root node, +/// encoded as 32 bytes in little-endian order, is the default-length BLAKE3 hash of the input." +pub(crate) const BLAKE3_DEFAULT_OUT_LEN: usize = 32; + +/// BLAKE3 `hash` mode of stdin, streamed in 1 KiB pieces, squeezing `output_len` bytes. +pub(crate) fn blake3_cmd(output_len: usize, output_hex: bool) { + stream_xof(BLAKE3::new(), output_len, output_hex); +} + +/// BLAKE3 `derive_key` mode: the key material is streamed from stdin, the context string is the +/// argument, and `output_len` bytes of derived key are written out. +/// +/// BLAKE3 spec s. 6.2: the context string "should be hardcoded, globally unique, and +/// application-specific"; this command takes it as an argument only so that scripts can name the +/// application's fixed string. +pub(crate) fn blake3_derive_key_cmd(context: &str, output_len: usize, output_hex: bool) { + stream_xof(BLAKE3DeriveKey::new(context.as_bytes()), output_len, output_hex); +} + +/// BLAKE3 `keyed_hash` mode of stdin: write `output_len` bytes of keyed output, or with `--verify` +/// check a 32-byte tag and report the result in the exit status. +pub(crate) fn blake3_keyed_hash_cmd( + key: &Option, + key_file: &Option, + verify_val: &Option, + output_len: usize, + output_hex: bool, +) { + // Verification goes through MAC::do_verify_final, which compares the default 32-byte tag in + // constant time and requires an exact length match; an extended output length has no + // meaning there, so refuse the combination rather than silently ignore --length. + if verify_val.is_some() && output_len != BLAKE3_DEFAULT_OUT_LEN { + eprintln!( + "Error: --verify checks the {BLAKE3_DEFAULT_OUT_LEN}-byte tag and cannot be combined \ + with --length." + ); + exit(-1); + } + + let key = load_mac_key(key, key_file, "BLAKE3-KeyedHash", BLAKE3_KEY_LEN, BLAKE3_KEY_LEN); + // The allow-weak form, as the HMAC and KMAC commands use: a CLI is used for test vectors and + // scripting, where a low-entropy or all-zero key is a legitimate thing to want. + // infallible: a 32-byte MACKey (or Zeroized) key is exactly what the key object accepts once + // the strength check is skipped. + let mac_key = BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).unwrap(); + let mut mac = mac_key.create_generator(); + + if verify_val.is_some() { + // do_mac streams stdin and, given a value to verify, exits with the result. + do_mac(mac, verify_val, output_hex); + return; + } + + // Hand-rolled rather than a helper: stream_xof needs an XOF and do_mac finishes with + // MAC::do_final, but extended keyed output needs the inherent into_squeezer afterwards. + let mut buf: [u8; 1024] = [0u8; 1024]; + let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); + while bytes_read != 0 { + mac.do_update(&buf[..bytes_read]); + bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); + } + + // BLAKE3 spec s. 2.6: the keyed output stream, of which the MAC tag is the first 32 bytes. + let out = mac.into_squeezer().do_output_final(output_len); + write_bytes_or_hex(&out, output_hex); + write_stdout(b"\n"); +} 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) -> Vec { } } +/// Loads a MAC key from `--key` (hex) or `--key-file` (raw bytes), tagged as a MAC key, for every +/// MAC command. If both are given the file wins, as the commands' help text says. +/// +/// A key outside `min_len..=max_len` bytes is refused here with a message naming the limit, rather +/// than as an opaque error from the algorithm's constructor. An all-zero key comes back tagged +/// [`KeyType::Zeroized`], which the MAC key objects accept. +pub(crate) fn load_mac_key( + key: &Option, + key_file: &Option, + alg_name: &str, + min_len: usize, + max_len: usize, +) -> KeyMaterial512 { + let key_bytes: Vec = if let Some(file) = key_file { + read_from_file_raw(file) + } else if let Some(k) = key { + hex::decode(k).unwrap_or_else(|_| { + eprintln!("Error: --key is not valid hex."); + exit(-1) + }) + } else { + eprintln!("Error: either `key` or `key-file` must be supplied."); + exit(-1) + }; + + if key_bytes.len() < min_len || key_bytes.len() > max_len { + if min_len == max_len { + eprintln!( + "Error: {alg_name} requires a {min_len}-byte key; got {} bytes.", + key_bytes.len() + ); + } else { + eprintln!( + "Error: {alg_name} requires a key of {min_len} to {max_len} bytes; got {} bytes.", + key_bytes.len() + ); + } + exit(-1); + } + + // infallible: the length is at most 64 (checked above, and no caller passes max_len > 64), + // which fits a KeyMaterial512, and MACKey is a valid type to load. + KeyMaterial512::from_bytes_as_type(&key_bytes, KeyType::MACKey).unwrap() +} + /// Reads either bin or hex pub(crate) fn read_from_file(filename: &str) -> Vec { let file = File::open(&filename); diff --git a/cli/src/helpers/stream_mode_helpers.rs b/cli/src/helpers/stream_mode_helpers.rs index fa0349e9f..d26a272a7 100644 --- a/cli/src/helpers/stream_mode_helpers.rs +++ b/cli/src/helpers/stream_mode_helpers.rs @@ -28,38 +28,32 @@ //! stdin is read as binary so the commands compose in a pipeline. `-x` renders the *output* as hex. //! For hex input, pipe through `hex-decode` first. -use crate::helpers::block_mode_helpers::{CHUNK_LEN, CipherDirection, FixedIv}; +use crate::helpers::block_mode_helpers::{CHUNK_LEN, CipherDirection, start_encryptor}; use crate::helpers::{flush_stdout, write_bytes_or_hex, write_stdout}; -use bouncycastle::core::key_material::KeyMaterial; +use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey}; use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; use std::io; use std::io::Read; use std::process::exit; -/// Encrypts stdin to stdout under the stream mode `E`. With no `iv`, the IV is generated and +/// Encrypts stdin to stdout under the key's stream mode. With no `iv`, 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 CFB and CFB8, 12 bytes for CTR. -pub(crate) fn encrypt_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/hkdf_cmd.rs b/cli/src/hkdf_cmd.rs index 499d11af6..3fc0e1723 100644 --- a/cli/src/hkdf_cmd.rs +++ b/cli/src/hkdf_cmd.rs @@ -7,6 +7,9 @@ use bouncycastle::hex; use bouncycastle::hkdf; use bouncycastle::sha2::hkdf::{HKDF_SHA256, HKDF_SHA384, HKDF_SHA512}; +/// The salt is held in a fixed-size key buffer; the library itself puts no bound on it. +const MAX_SALT_LEN: usize = 1024; + pub(crate) fn hkdf_cmd( hkdfname: &str, salt: &Option, @@ -28,11 +31,11 @@ pub(crate) fn hkdf_cmd( eprintln!("Error: either `salt` or `salt-file` must be supplied."); exit(-1) }; - if salt_bytes.len() > 128 { - eprintln!("Error: The CLI only supports HKDF salts up to 128 bytes (1024 bits)."); + if salt_bytes.len() > MAX_SALT_LEN { + eprintln!("Error: The CLI only supports HKDF salts up to {MAX_SALT_LEN} bytes."); exit(-1); } - let mut salt_key = KeyMaterial::<1024>::from_bytes(&salt_bytes).unwrap(); + let mut salt_key = KeyMaterial::::from_bytes(&salt_bytes).unwrap(); // force it just so the CLI behaves properly even with all-zero or zero-length keys do_hazardous_operations(&mut salt_key, |salt_key| salt_key.set_key_type(KeyType::MACKey)) .unwrap(); @@ -46,13 +49,14 @@ pub(crate) fn hkdf_cmd( exit(-1) }; + // RFC 5869 Section 2.3: info is "optional context and application specific information (can + // be a zero-length string)", so neither flag is required. let info = if let Some(file) = additional_input_file { fs::read(file).unwrap() } else if let Some(hex_str) = additional_input { hex::decode(hex_str).unwrap() } else { - eprintln!("Error: either `additional_input` or `additional_input_file` must be supplied."); - exit(-1) + Vec::new() }; // RFC 5869: PRK = HKDF-Extract(salt, IKM), then OKM = HKDF-Expand(PRK, info, L). The IKM is diff --git a/cli/src/mac_cmd.rs b/cli/src/mac_cmd.rs index 562bc9b55..1b2b61697 100644 --- a/cli/src/mac_cmd.rs +++ b/cli/src/mac_cmd.rs @@ -1,14 +1,16 @@ +use std::io; use std::io::Read; use std::process::exit; -use std::{fs, io}; -use bouncycastle::core::hazmat::do_hazardous_operations; -use bouncycastle::core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; +use crate::helpers::load_mac_key; +use bouncycastle::core::errors::MACError; +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 { @@ -19,26 +21,6 @@ pub(crate) enum HMACVariant { SM3, } -/// Loads a MAC key from `--key` (hex) or `--key-file` (raw), tagged as a MAC key. -fn load_mac_key(key: &Option, key_file: &Option) -> KeyMaterial512 { - let key_bytes: Vec = if key.is_some() { - hex::decode(key.as_ref().unwrap()).unwrap() - } else if key_file.is_some() { - fs::read(key_file.as_ref().unwrap()).unwrap() - } else { - eprintln!("Error: either `key` or `key-file` must be supplied."); - exit(-1) - }; - - if key_bytes.len() > 64 { - 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(); - key -} - pub(crate) fn mac_cmd( hmac_variant: HMACVariant, key: &Option, @@ -46,29 +28,29 @@ pub(crate) fn mac_cmd( verify_val: &Option, output_hex: bool, ) { - let key = load_mac_key(key, key_file); + let key = load_mac_key(key, key_file, "HMAC", 0, 64); // 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); } } } @@ -84,26 +66,33 @@ pub(crate) fn kmac_cmd( verify_val: &Option, output_hex: bool, ) { - let key = load_mac_key(key, key_file); + let key = load_mac_key(key, key_file, "KMAC", 0, 64); 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}"), } } -fn do_mac(mut mac: impl MAC, verify_val: &Option, output_hex: bool) { +/// 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) +} + +pub(crate) 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]; let mut bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin"); @@ -120,10 +109,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 d4872f25d..4f7c4c01d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,6 +6,8 @@ mod aes_ctr_cmd; mod aes_ecb_cmd; mod aes_gcm_cmd; mod ascon_cmd; +mod blake2_cmd; +mod blake3_cmd; mod encoders_cmd; mod helpers; mod hkdf_cmd; @@ -17,6 +19,7 @@ mod sha2_cmd; mod sha3_cmd; mod sm3_cmd; +use crate::blake2_cmd::BLAKE2Variant; use crate::mac_cmd::HMACVariant; use crate::mldsa_cmd::MLDSAAction; use crate::sha2_cmd::SHA2Variant; @@ -117,8 +120,7 @@ enum Subcommands { /// processing or stream processing", because Appendix A.2.1 puts the payload length inside the /// first block the MAC covers. It does buy one thing: on `decrypt` NO plaintext is written /// until the tag has verified, so unlike `ascon-aead128` a non-zero exit leaves nothing to - /// discard. For large inputs use `ascon-aead128`, which streams. The AAD is the exception: - /// `--aad-file` is streamed, because a file's size can be declared before it is read. + /// discard. For large inputs use `ascon-aead128`, which streams. The AAD is read whole as well. /// /// Input may be any length: CCM pads internally and the payload is not block-aligned. /// @@ -150,9 +152,8 @@ enum Subcommands { #[arg(long)] aad: Option, - /// A file containing the associated data, as raw bytes (never hex-decoded). A regular file - /// is streamed rather than loaded. If both aad and aad_file options are provided, the file - /// will be used. + /// A file containing the associated data, as raw bytes (never hex-decoded). If both aad and + /// aad_file options are provided, the file will be used. #[arg(long)] aad_file: Option, @@ -495,9 +496,8 @@ enum Subcommands { #[arg(long)] aad: Option, - /// A file containing the associated data, as raw bytes (never hex-decoded). A regular file - /// is streamed rather than loaded. If both aad and aad_file options are provided, the file - /// will be used. + /// A file containing the associated data, as raw bytes (never hex-decoded). If both aad and + /// aad_file options are provided, the file will be used. #[arg(long)] aad_file: Option, @@ -737,9 +737,8 @@ enum Subcommands { #[arg(long)] aad: Option, - /// A file containing the associated data, as raw bytes (never hex-decoded). A regular file - /// is streamed rather than loaded. If both aad and aad_file options are provided, the file - /// will be used. + /// A file containing the associated data, as raw bytes (never hex-decoded). If both aad and + /// aad_file options are provided, the file will be used. #[arg(long)] aad_file: Option, @@ -1010,6 +1009,137 @@ enum Subcommands { /// Supports streaming for low memory footprint and continuous processing from stdin to stdout. Base64Encode, + /// Perform BLAKE2b-512 (RFC 7693) of the content provided on stdin. + /// Supports streaming update for low memory footprint. + BLAKE2b_512 { + #[arg(short)] + /// Output the digest in hex format. + x: bool, + }, + + /// Perform BLAKE2s-256 (RFC 7693) of the content provided on stdin. + /// Supports streaming update for low memory footprint. + BLAKE2s_256 { + #[arg(short)] + /// Output the digest in hex format. + x: bool, + }, + + /// Perform keyed BLAKE2b-512 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the + /// content provided on stdin. The key is 1 to 64 bytes. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE2b_512_MAC { + /// The MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A MAC value to be verified. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + #[arg(short)] + /// Output the tag in hex format. + x: bool, + }, + + /// Perform keyed BLAKE2s-256 (RFC 7693 Sec 3.3, BLAKE2's built-in MAC; not HMAC) of the + /// content provided on stdin. The key is 1 to 32 bytes. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE2s_256_MAC { + /// The MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A MAC value to be verified. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + #[arg(short)] + /// Output the tag in hex format. + x: bool, + }, + + /// Perform BLAKE3 of the content provided on stdin, with output of any length (32 bytes by + /// default). Shorter outputs are prefixes of longer ones. + /// Supports streaming update for low memory footprint. + BLAKE3 { + /// Length of the output in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + + /// Perform BLAKE3 in keyed_hash mode (a MAC and PRF) of the content provided on stdin. The key + /// must be exactly 32 bytes. Output of any length (32 bytes by default); --verify checks the + /// 32-byte tag. + /// Supports streaming update for low memory footprint. + /// Note: in production uses, secrets should not be passed on the command-line because they get + /// logged in shell history. Use the file-based input instead. + BLAKE3_KeyedHash { + /// The 32-byte MAC key in hex. + /// The `key_file` option is preferred to avoid leaving key material in command history. + #[arg(long)] + key: Option, + + /// A file containing the 32-byte MAC key in binary. + /// If both key and key_file options are provided, the file will be used. + #[arg(short, long)] + key_file: Option, + + /// A 32-byte tag (hex) to be verified. Cannot be combined with --length. + /// The command will output either 0 for success or -1 for verification failure. + #[arg(short, long)] + verify: Option, + + /// Length of the output in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + + /// Perform BLAKE3 in derive_key mode: derive a key of any length (32 bytes by default) from + /// the key material provided on stdin, under a context string. The context string should be + /// hardcoded, globally unique and application-specific (BLAKE3 spec Sec 6.2). + /// Supports streaming update for low memory footprint. + BLAKE3_DeriveKey { + /// The context string, e.g. "example.com 2019-12-25 16:18:03 session tokens v1" (BLAKE3 + /// spec Sec 6.2). + #[arg(short, long)] + context: String, + + /// Length of the derived key in bytes. + #[arg(short, long, default_value_t = blake3_cmd::BLAKE3_DEFAULT_OUT_LEN)] + length: usize, + + #[arg(short)] + /// Output in hex format. + x: bool, + }, + /// The HashML-DSA-44 signature algorithm. HashMLDSA44 { action: MLDSAAction, @@ -1093,8 +1223,8 @@ enum Subcommands { /// Supports streaming for low memory footprint and continuous processing from stdin to stdout. HexEncode, - /// Perform HKDF-SHA256 of the provided input keying material. - /// HKDF.extract_and_expand(salt, ikm, additional_info, L) + /// Perform HKDF-SHA256 (RFC 5869 extract-then-expand) of the provided input keying material: + /// OKM = HKDF-Expand(HKDF-Extract(salt, ikm), info, len) /// Note: in production uses, secrets should not be passed on the command-line because they get /// logged in shell history. Use the file-based input instead. HKDF_SHA256 { @@ -1118,11 +1248,11 @@ enum Subcommands { #[arg(short, long)] ikm_file: Option, - /// Additional input data in hex. + /// The HKDF `info` input (RFC 5869 Section 2.3) in hex. Optional; defaults to empty. #[arg(long)] additional_input: Option, - /// A file containing the additional input data in binary. + /// A file containing the HKDF `info` input in binary. Optional; defaults to empty. /// If both additional_input and additional_input_file options are provided, the file will be used. #[arg(short, long)] additional_input_file: Option, @@ -1136,8 +1266,8 @@ enum Subcommands { x: bool, }, - /// Perform HKDF-SHA384 of the provided input keying material. - /// HKDF.extract_and_expand(salt, ikm, additional_info, L) + /// Perform HKDF-SHA384 (RFC 5869 extract-then-expand) of the provided input keying material: + /// OKM = HKDF-Expand(HKDF-Extract(salt, ikm), info, len) /// Note: in production uses, secrets should not be passed on the command-line because they get /// logged in shell history. Use the file-based input instead. HKDF_SHA384 { @@ -1161,11 +1291,11 @@ enum Subcommands { #[arg(short, long)] ikm_file: Option, - /// Additional input data in hex. + /// The HKDF `info` input (RFC 5869 Section 2.3) in hex. Optional; defaults to empty. #[arg(long)] additional_input: Option, - /// A file containing the additional input data in binary. + /// A file containing the HKDF `info` input in binary. Optional; defaults to empty. /// If both additional_input and additional_input_file options are provided, the file will be used. #[arg(short, long)] additional_input_file: Option, @@ -1179,8 +1309,8 @@ enum Subcommands { x: bool, }, - /// Perform HKDF-SHA512 of the provided input keying material. - /// HKDF.extract_and_expand(salt, ikm, additional_info, L) + /// Perform HKDF-SHA512 (RFC 5869 extract-then-expand) of the provided input keying material: + /// OKM = HKDF-Expand(HKDF-Extract(salt, ikm), info, len) /// Note: in production uses, secrets should not be passed on the command-line because they get /// logged in shell history. Use the file-based input instead. HKDF_SHA512 { @@ -1204,11 +1334,11 @@ enum Subcommands { #[arg(short, long)] ikm_file: Option, - /// Additional input data in hex. + /// The HKDF `info` input (RFC 5869 Section 2.3) in hex. Optional; defaults to empty. #[arg(long)] additional_input: Option, - /// A file containing the additional input data in binary. + /// A file containing the HKDF `info` input in binary. Optional; defaults to empty. /// If both additional_input and additional_input_file options are provided, the file will be used. #[arg(short, long)] additional_input_file: Option, @@ -1233,6 +1363,7 @@ enum Subcommands { key: Option, /// A file containing the MAC key in binary. + /// If both key and key_file options are provided, the file will be used. #[arg(short, long)] key_file: Option, @@ -1350,7 +1481,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)] @@ -1377,7 +1508,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)] @@ -1898,6 +2029,25 @@ fn run() { Some(Subcommands::Base64Encode) => { encoders_cmd::base64_encode_cmd(); } + Some(Subcommands::BLAKE2b_512 { x }) => { + blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2b_512, *x); + } + Some(Subcommands::BLAKE2s_256 { x }) => { + blake2_cmd::blake2_cmd(BLAKE2Variant::BLAKE2s_256, *x); + } + Some(Subcommands::BLAKE2b_512_MAC { key, key_file, verify, x }) => { + blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2b_512, key, key_file, verify, *x) + } + Some(Subcommands::BLAKE2s_256_MAC { key, key_file, verify, x }) => { + blake2_cmd::blake2_mac_cmd(BLAKE2Variant::BLAKE2s_256, key, key_file, verify, *x) + } + Some(Subcommands::BLAKE3 { length, x }) => blake3_cmd::blake3_cmd(*length, *x), + Some(Subcommands::BLAKE3_KeyedHash { key, key_file, verify, length, x }) => { + blake3_cmd::blake3_keyed_hash_cmd(key, key_file, verify, *length, *x) + } + Some(Subcommands::BLAKE3_DeriveKey { context, length, x }) => { + blake3_cmd::blake3_derive_key_cmd(context, *length, *x) + } Some(Subcommands::HashMLDSA44 { action, ctxfile, skfile, pkfile, sigfile, x }) => { mldsa_cmd::hash_mldsa44_sha512_cmd(action, ctxfile, skfile, pkfile, sigfile, *x); } diff --git a/cli/tests/test_blake.sh b/cli/tests/test_blake.sh new file mode 100755 index 000000000..24a6ee4ab --- /dev/null +++ b/cli/tests/test_blake.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash +# The blake2b-512 / blake2s-256 / blake2b-512-mac / blake2s-256-mac / blake3 / blake3-keyed-hash +# / blake3-derive-key subcommands, end to end through the binary: known answers in hex and binary, +# --length, --key / --key-file loading, key-length rejection and the --verify exit codes. +# +# The known-answer values were copied by script, never retyped, from the vectors embedded in the +# algorithm crates' tests: for BLAKE2, `RFC_ABC` (RFC 7693 Appendix A / B) and the `KAT` table +# (from the official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json; message +# bytes 0, 1, ..., len - 1, key bytes 0, 1, ..., kk - 1) in crypto/blake2/tests/blake2{b,s}_tests.rs; +# for BLAKE3, crypto/blake3/tests/vectors/mod.rs (from the official +# github.com/BLAKE3-team/BLAKE3 test_vectors/test_vectors.json; input bytes i % 251). + +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +# RFC 7693 Appendix A / B: BLAKE2b-512("abc") / BLAKE2s-256("abc"). +BLAKE2B_ABC=ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923 +BLAKE2S_ABC=508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982 +# blake2-kat.json, message 0, 1, ..., 254: unkeyed, and keyed with the bytes 0, 1, ..., kk - 1. +BLAKE2B_255=5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928 +BLAKE2B_255_KEYED=142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461 +BLAKE2S_255=f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d +BLAKE2S_255_KEYED=3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd +# blake2-kat.json, empty message, keyed. +BLAKE2B_0_KEYED=10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568 +BLAKE2S_0_KEYED=48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49 +# test_vectors.json `key` and `context_string` fields. +BLAKE3_KEY="whats the Elvish word for friend" +BLAKE3_CONTEXT="BLAKE3 2019-12-27 16:29:52 test vectors context" +# test_vectors.json, input_len 0: the 131-byte hash, keyed_hash and derive_key outputs. +BLAKE3_0_HASH=af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d +BLAKE3_0_KEYED=92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f +BLAKE3_0_DERIVE=2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3e6842f13bddd573c098c3f17361f1f206b8cad9d088aa4a3f746752c6b0ce6a83b0da81d59649257cdf8eb3e9f7d4998e41021fac119deefb896224ac99f860011f73609e6e0e4540f93b273e56547dfd3aa1a035ba6689d89a0 +# test_vectors.json, input_len 1025: the 131-byte hash, keyed_hash and derive_key outputs. +BLAKE3_1025_HASH=d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a +BLAKE3_1025_KEYED=357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69362396b77fdc0d2634a552970843722066c3c15902ae5097e00ff53f1e116f1cd5352720113a837ab2452cafbde4d54085d9cf5d21ca613071551b25d52e69d6c81123872b6f19cd3bc1333edf0c52b94de23ba772cf82636cff4542540a7738d5b930 +BLAKE3_1025_DERIVE=effaa245f065fbf82ac186839a249707c3bddf6d3fdda22d1b95a3c970379bcb5d31013a167509e9066273ab6e2123bc835b408b067d88f96addb550d96b6852dad38e320b9d940f86db74d398c770f462118b35d2724efa13da97194491d96dd37c3c09cbef665953f2ee85ec83d88b88d11547a6f911c8217cca46defa2751e7f3ad +# test_vectors.json, input_len 102400 (100 x the 1 KiB read buffer): the hash output. +BLAKE3_102400_HASH=bc3e3d41a1146b069abffad3c0d44860cf664390afce4d9661f7902e7943e085e01c59dab908c04c3342b816941a26d69c2605ebee5ec5291cc55e15b76146e6745f0601156c3596cb75065a9c57f35585a52e1ac70f69131c23d611ce11ee4ab1ec2c009012d236648e77be9295dd0426f29b764d65de58eb7d01dd42248204f45f8e + + +# ---- inputs --------------------------------------------------------------------------------- + +# `counting N` writes the bytes 0, 1, ..., N - 1 (mod 256): the BLAKE2 KAT message and key. +counting() { + local i out='' + for ((i = 0; i < $1; i++)); do out+=$(printf '%02x' $((i % 256))); done + unhex "$out" +} + +# `counting_hex N`: the same bytes, as hex. +counting_hex() { + counting "$1" | "$BC_RUST" hex-encode +} + +# `blake3_input N` writes the BLAKE3 test-vector input: "a repeating sequence of 251 bytes: 0, 1, +# 2, ..., 249, 250, 0, 1, ..." (test_vectors.json `_comment`). +blake3_input() { + local i block='' + for ((i = 0; i < 251; i++)); do block+=$(printf '%02x' "$i"); done + local reps=$(($1 / 251 + 1)) all='' + for ((i = 0; i < reps; i++)); do all+=$block; done + unhex "${all:0:$((2 * $1))}" +} + +# `bin_hex CMD...` runs CMD, whose binary output ends in the newline every digest command +# writes, and prints that output without the newline, as hex. +bin_hex() { + "$@" >"$TMP/.bin" + assert_eq "$(tail -c 1 "$TMP/.bin" | "$BC_RUST" hex-encode)" 0a "binary output ends in a newline" + head -c -1 "$TMP/.bin" | "$BC_RUST" hex-encode +} + +# ---- blake2b-512 / blake2s-256 -------------------------------------------------------------- + +test_blake2_hashes_match_rfc7693_appendices() { + printf abc >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake2b-512 -x <"$TMP/msg")" "$BLAKE2B_ABC" "BLAKE2b-512(abc)" + assert_eq "$(hex_out "$BC_RUST" blake2s-256 -x <"$TMP/msg")" "$BLAKE2S_ABC" "BLAKE2s-256(abc)" +} + +# A two-block (BLAKE2b) / four-block (BLAKE2s) message from the official KAT, in hex and binary. +test_blake2_hashes_match_the_official_kat_in_hex_and_binary() { + counting 255 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake2b-512 -x <"$TMP/msg")" "$BLAKE2B_255" "blake2b hex" + assert_eq "$(bin_hex "$BC_RUST" blake2b-512 <"$TMP/msg")" "$BLAKE2B_255" "blake2b binary" + assert_eq "$(hex_out "$BC_RUST" blake2s-256 -x <"$TMP/msg")" "$BLAKE2S_255" "blake2s hex" + assert_eq "$(bin_hex "$BC_RUST" blake2s-256 <"$TMP/msg")" "$BLAKE2S_255" "blake2s binary" +} + +# ---- blake2b-512-mac / blake2s-256-mac ------------------------------------------------------ + +test_blake2_macs_match_the_official_keyed_kat_in_hex_and_binary() { + local kb ks + counting 255 >"$TMP/msg" + kb=$(counting_hex 64) + ks=$(counting_hex 32) + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key "$kb" -x <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac hex" + assert_eq "$(bin_hex "$BC_RUST" blake2b-512-mac --key "$kb" <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac binary" + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key "$kb" -x "$TMP/msg" + expect_ok "the right tag verifies" "$BC_RUST" "$sub" --key "$key" -v "$tag" <"$TMP/msg" + assert_eq "$("$BC_RUST" "$sub" --key "$key" -v "$tag" <"$TMP/msg")" "" "verify writes nothing" + expect_fail "a wrong tag fails" \ + "$BC_RUST" "$sub" --key "$key" -v "$(flip_hex "$tag" 0 1)" <"$TMP/msg" + expect_fail "a truncated tag fails" \ + "$BC_RUST" "$sub" --key "$key" -v "${tag:0:$((${#tag} - 2))}" <"$TMP/msg" +} + +test_blake2b_512_mac_verify_exit_codes() { + blake2_verify blake2b-512-mac 64 "$BLAKE2B_255_KEYED" +} + +test_blake2s_256_mac_verify_exit_codes() { + blake2_verify blake2s-256-mac 32 "$BLAKE2S_255_KEYED" +} + +# The key file holds raw bytes, like the HMAC commands' --key-file; it wins over --key. +test_blake2_mac_key_file_is_loaded_as_raw_bytes() { + counting 255 >"$TMP/msg" + counting 64 >"$TMP/key64" + counting 32 >"$TMP/key32" + assert_eq "$(hex_out "$BC_RUST" blake2b-512-mac --key-file "$TMP/key64" -x <"$TMP/msg")" \ + "$BLAKE2B_255_KEYED" "blake2b-512-mac --key-file" + assert_eq "$(hex_out "$BC_RUST" blake2s-256-mac -k "$TMP/key32" -x <"$TMP/msg")" \ + "$BLAKE2S_255_KEYED" "blake2s-256-mac -k" + assert_eq "$(hex_out "$BC_RUST" blake2s-256-mac -k "$TMP/key32" --key 00 -x <"$TMP/msg")" \ + "$BLAKE2S_255_KEYED" "the key file wins over --key" +} + +# RFC 7693 s. 2.1 table, "Key bytes": "0 <= kk <= 64" (BLAKE2b) / "0 <= kk <= 32" (BLAKE2s); +# kk = 0 is the unkeyed hash, which is the other command. +test_blake2_mac_rejects_bad_keys() { + local sub + printf abc >"$TMP/msg" + expect_fail "a 33-byte key" "$BC_RUST" blake2s-256-mac --key "$(counting_hex 33)" <"$TMP/msg" + assert_stderr_has "1 to 32 bytes" + expect_fail "a 65-byte key" "$BC_RUST" blake2b-512-mac --key "$(counting_hex 65)" <"$TMP/msg" + assert_stderr_has "1 to 64 bytes" + for sub in blake2b-512-mac blake2s-256-mac; do + expect_fail "$sub: an empty key" "$BC_RUST" "$sub" --key "" <"$TMP/msg" + assert_stderr_has "got 0 bytes" + expect_fail "$sub: no key" "$BC_RUST" "$sub" <"$TMP/msg" + assert_stderr_has "must be supplied" + expect_fail "$sub: bad hex" "$BC_RUST" "$sub" --key "not hex" <"$TMP/msg" + assert_stderr_has "not valid hex" + done +} + +# ---- blake3 --------------------------------------------------------------------------------- + +test_blake3_default_length_is_the_first_32_bytes_of_the_vector() { + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 -x "$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 --length 131 -x <"$TMP/msg")" "$BLAKE3_1025_HASH" \ + "131 bytes" + assert_eq "$(bin_hex "$BC_RUST" blake3 -l 131 <"$TMP/msg")" "$BLAKE3_1025_HASH" \ + "131 bytes, binary" + for len in 0 1 31 33 63 64 65 100; do + assert_eq "$(hex_out "$BC_RUST" blake3 -l "$len" -x <"$TMP/msg")" \ + "${BLAKE3_1025_HASH:0:$((2 * len))}" "length $len" + done +} + +# 100 KiB is many 1 KiB reads and many 1 KiB chunks: the streaming loop and the CV stack together. +test_blake3_streams_a_long_input() { + blake3_input 102400 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3 -x <"$TMP/msg")" "${BLAKE3_102400_HASH:0:64}" "100 KiB" +} + +# ---- blake3-keyed-hash ---------------------------------------------------------------------- + +test_blake3_keyed_hash_matches_the_vector_in_hex_and_binary() { + local key + key=$(printf '%s' "$BLAKE3_KEY" | "$BC_RUST" hex-encode) + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" -x <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "hex" + assert_eq "$(bin_hex "$BC_RUST" blake3-keyed-hash --key "$key" <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "binary" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" --length 131 -x <"$TMP/msg")" \ + "$BLAKE3_1025_KEYED" "131 bytes" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash --key "$key" -x "$TMP/msg" + expect_ok "the right tag verifies" "$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" <"$TMP/msg" + assert_eq "$("$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" <"$TMP/msg")" "" \ + "verify writes nothing" + expect_fail "a wrong tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "$(flip_hex "$tag" 31 128)" <"$TMP/msg" + # The tag is the 32-byte default output; neither a prefix nor the extended output verifies. + expect_fail "a truncated tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "${tag:0:62}" <"$TMP/msg" + expect_fail "a 33-byte tag fails" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "${BLAKE3_1025_KEYED:0:66}" <"$TMP/msg" + # --verify checks the 32-byte tag, so a different --length is refused, not ignored. + expect_fail "--verify with --length" \ + "$BC_RUST" blake3-keyed-hash --key "$key" -v "$tag" -l 64 <"$TMP/msg" + assert_stderr_has "cannot be combined with --length" +} + +test_blake3_keyed_hash_key_file_is_loaded_as_raw_bytes() { + printf '%s' "$BLAKE3_KEY" >"$TMP/key" + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-keyed-hash -k "$TMP/key" -x <"$TMP/msg")" \ + "${BLAKE3_1025_KEYED:0:64}" "key file" +} + +# BLAKE3 spec s. 2.3: keyed_hash takes "a 256-bit key"; any other length is refused with a +# message and a non-zero exit, whether it comes from --key or --key-file. +test_blake3_keyed_hash_rejects_a_key_that_is_not_32_bytes() { + local len hex + hex=$(printf '%s%s' "$BLAKE3_KEY" "$BLAKE3_KEY" | "$BC_RUST" hex-encode) + printf abc >"$TMP/msg" + for len in 0 1 16 31 33 64; do + expect_fail "a $len-byte key" \ + "$BC_RUST" blake3-keyed-hash --key "${hex:0:$((2 * len))}" <"$TMP/msg" + assert_stderr_has "requires a 32-byte key" + done + printf '%s' "${BLAKE3_KEY:0:31}" >"$TMP/key31" + expect_fail "a 31-byte key file" "$BC_RUST" blake3-keyed-hash -k "$TMP/key31" <"$TMP/msg" + assert_stderr_has "got 31 bytes" +} + +# ---- blake3-derive-key ---------------------------------------------------------------------- + +test_blake3_derive_key_matches_the_vector() { + blake3_input 1025 >"$TMP/msg" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key --context "$BLAKE3_CONTEXT" -x <"$TMP/msg")" \ + "${BLAKE3_1025_DERIVE:0:64}" "hex" + assert_eq "$(bin_hex "$BC_RUST" blake3-derive-key --context "$BLAKE3_CONTEXT" <"$TMP/msg")" \ + "${BLAKE3_1025_DERIVE:0:64}" "binary" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key -c "$BLAKE3_CONTEXT" -l 131 -x <"$TMP/msg")" \ + "$BLAKE3_1025_DERIVE" "131 bytes" + assert_eq "$(hex_out "$BC_RUST" blake3-derive-key -c "$BLAKE3_CONTEXT" -x "$TMP/msg" + other=$(hex_out "$BC_RUST" blake3-derive-key -c "another context" -x <"$TMP/msg") + [ "$other" != "${BLAKE3_1025_DERIVE:0:64}" ] || fail "the context must change the key" + [ "$other" != "${BLAKE3_1025_HASH:0:64}" ] || fail "derive_key is not the plain hash" + expect_fail "no context" "$BC_RUST" blake3-derive-key -x <"$TMP/msg" +} + +run_all diff --git a/cli/tests/test_hkdf.sh b/cli/tests/test_hkdf.sh index fcd813306..740879144 100755 --- a/cli/tests/test_hkdf.sh +++ b/cli/tests/test_hkdf.sh @@ -22,6 +22,18 @@ test_hkdf_sha256_matches_rfc5869_a1() { "RFC 5869 A.1 OKM" } +# RFC 5869 A.3: zero-length salt and no info at all; `--additional-input` is optional. +test_hkdf_sha256_matches_rfc5869_a3() { + local got + got=$(hex_out "$BC_RUST" hkdf-sha256 \ + --salt "" \ + --ikm 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b \ + --len 42 -x) + assert_eq "$got" \ + "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8" \ + "RFC 5869 A.3 OKM" +} + test_hkdf_sha384_matches_wycheproof_tc11() { local got got=$(hex_out "$BC_RUST" hkdf-sha384 \ @@ -74,6 +86,18 @@ test_hkdf_sha512_files_match_hex() { hkdf_files_match_hex hkdf-sha512 } +# When a value is given both as hex and as a file, the file is used, as the help text says. +test_hkdf_sha256_file_wins_over_hex() { + local from_files with_both + rng 32 >"$TMP/salt" + rng 32 >"$TMP/ikm" + rng 16 >"$TMP/info" + from_files=$(hex_out "$BC_RUST" hkdf-sha256 -s "$TMP/salt" -i "$TMP/ikm" -a "$TMP/info" --len 42 -x) + with_both=$(hex_out "$BC_RUST" hkdf-sha256 --salt 01 -s "$TMP/salt" --ikm 02 -i "$TMP/ikm" \ + --additional-input 03 -a "$TMP/info" --len 42 -x) + assert_eq "$with_both" "$from_files" "the file inputs must win over the hex ones" +} + # ---- output length ------------------------------------------------------------------------ # `hkdf_short_output_is_a_prefix SUBCOMMAND LEN`: RFC 5869's output is the first L octets of the diff --git a/cli/tests/test_hmac.sh b/cli/tests/test_hmac.sh index a932f7833..a698ae679 100755 --- a/cli/tests/test_hmac.sh +++ b/cli/tests/test_hmac.sh @@ -46,4 +46,18 @@ test_hmac_sm3_round_trip() { hmac_round_trip hmac-sm3 32 } +# When both `--key` and `--key-file` are given, the file is used, as the help text says. +test_hmac_sha256_key_file_wins_over_key() { + local from_file with_both from_hex other + rng 32 >"$TMP/key" + rng 32 >"$TMP/other_key" + rng 100 >"$TMP/msg" + other=$(hex "$TMP/other_key") + from_file=$(hex_out "$BC_RUST" hmac-sha256 -k "$TMP/key" -x <"$TMP/msg") + with_both=$(hex_out "$BC_RUST" hmac-sha256 --key "$other" -k "$TMP/key" -x <"$TMP/msg") + from_hex=$(hex_out "$BC_RUST" hmac-sha256 --key "$other" -x <"$TMP/msg") + assert_eq "$with_both" "$from_file" "the key file must win over the hex key" + [ "$from_hex" != "$from_file" ] || fail "the two keys must give different tags" +} + run_all 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/Cargo.toml b/crypto/aes/Cargo.toml index 1bd18ab01..2db123d2b 100644 --- a/crypto/aes/Cargo.toml +++ b/crypto/aes/Cargo.toml @@ -21,3 +21,7 @@ harness = false [[bench]] name = "aes_modes_benches" harness = false + +[[bench]] +name = "aes_gcm_packet_benches" +harness = false diff --git a/crypto/aes/benches/aes_gcm_packet_benches.rs b/crypto/aes/benches/aes_gcm_packet_benches.rs new file mode 100644 index 000000000..e9504797d --- /dev/null +++ b/crypto/aes/benches/aes_gcm_packet_benches.rs @@ -0,0 +1,231 @@ +//! 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 -- `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. + +use bouncycastle_aes::{AES_GCM_256, AES256Key}; +use bouncycastle_cipher::modes::GCM; +use bouncycastle_cipher::{Decrypting, Encrypting}; +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_core_test_framework::FixedSeedRNG; +use bouncycastle_rng::HashDRBG_SHA512; +use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +/// One TLS-record-sized packet. +const PACKET_LEN: usize = 16 * 1024; +const NONCE_LEN: usize = 12; +const TAG_LEN: usize = 16; + +type Enc<'k> = AES_GCM_256<'k, Encrypting>; +type Dec<'k> = AES_GCM_256<'k, Decrypting>; +type Key = AES256Key; + +fn material() -> KeyMaterial<32> { + let bytes: [u8; 32] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); + KeyMaterial::<32>::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).expect("a valid key") +} + +fn rng() -> FixedSeedRNG { + FixedSeedRNG::::new([0x24u8; NONCE_LEN]) +} + +/// Encrypt one packet and finish: the shape of both encrypt arms once the encryptor exists. +fn encrypt_packet( + mut enc: Enc, + packet: &[u8; PACKET_LEN], + out: &mut [u8; PACKET_LEN], +) -> [u8; TAG_LEN] { + enc.do_encrypt_out(packet, out).unwrap(); + let (_, tag) = enc.do_encrypt_final_detached_out(&mut [0u8; TAG_LEN]).unwrap(); + tag +} + +/// Decrypt one packet and check its tag: the shape of both decrypt arms once the decryptor exists. +fn decrypt_packet( + mut dec: Dec, + ct: &[u8; PACKET_LEN], + tag: &[u8; TAG_LEN], + out: &mut [u8; PACKET_LEN], +) -> usize { + let n = dec.do_decrypt_out(ct, out).unwrap(); + let mut last = [0u8; TAG_LEN]; + n + dec.do_decrypt_final_detached_out(tag, &mut last).unwrap() +} + +fn bench_encrypt_packets(c: &mut Criterion) { + let km = material(); + let key = Key::from_key_material(&km).expect("a valid key"); + let packet = [0xA5u8; PACKET_LEN]; + let mut rng = rng(); + + let mut group = c.benchmark_group("gcm::AES_256 16KiB packet encrypt"); + group.throughput(Throughput::Bytes(PACKET_LEN as u64)); + + group.bench_function("AES256Key: from_key_material per packet (expand per packet)", |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + 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, + ) + }); + group.bench_function("AES256Key: create_encryptor (stored schedule and H)", |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + let (enc, nonce) = black_box(&key).create_encryptor(&mut rng).unwrap(); + black_box((nonce, encrypt_packet(enc, black_box(&packet), out))) + }, + BatchSize::LargeInput, + ) + }); + group.bench_function( + "AES256Key: from_key_material per packet (OS DRBG per packet, expand per packet)", + |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + 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, + ) + }, + ); + group.bench_function("AES256Key: create_encryptor (OS DRBG per packet)", |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + let (enc, nonce) = black_box(&key).create_encryptor(&mut os_rng).unwrap(); + black_box((nonce, encrypt_packet(enc, black_box(&packet), out))) + }, + BatchSize::LargeInput, + ) + }); + + group.finish(); +} + +fn bench_decrypt_packets(c: &mut Criterion) { + let km = material(); + let key = Key::from_key_material(&km).expect("a valid key"); + let packet = [0xA5u8; PACKET_LEN]; + + // One valid packet to decrypt, made outside the timing. + let mut ct = [0u8; PACKET_LEN]; + 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("AES256Key: from_key_material per packet (expand per packet)", |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + 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, + ) + }); + group.bench_function("AES256Key: create_decryptor (stored schedule and H)", |b| { + b.iter_batched_ref( + || [0u8; PACKET_LEN], + |out| { + let dec = black_box(&key).create_decryptor(&nonce); + black_box(decrypt_packet(dec, black_box(&ct), &tag, out)) + }, + BatchSize::LargeInput, + ) + }); + + group.finish(); +} + +/// Construction alone, no data: what each packet pays before its first byte. +fn bench_init(c: &mut Criterion) { + let km = material(); + let key = Key::from_key_material(&km).expect("a valid key"); + let nonce = [0x24u8; NONCE_LEN]; + let mut rng = rng(); + + let mut group = c.benchmark_group("gcm::AES_256 init"); + + 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( + "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( + "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( + "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(); + black_box(black_box(&key).create_encryptor(&mut os_rng).unwrap()) + }) + }); + + group.finish(); +} + +criterion_group!(benches, bench_encrypt_packets, bench_decrypt_packets, bench_init); +criterion_main!(benches); diff --git a/crypto/aes/benches/aes_modes_benches.rs b/crypto/aes/benches/aes_modes_benches.rs index 90c66754d..86abc1220 100644 --- a/crypto/aes/benches/aes_modes_benches.rs +++ b/crypto/aes/benches/aes_modes_benches.rs @@ -23,14 +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_cipher::modes::hazmat::ecb::Ecb; -use bouncycastle_cipher::modes::{Cbc, CcmPacket, Cfb, Cfb8, Ctr, GCM_NONCE_LEN, Gcm}; +use bouncycastle_aes::hazmat::AES128Internal; +use bouncycastle_aes::{AES128Key, AES256Key}; +use bouncycastle_cipher::modes::hazmat::ecb::{ECB, Ecb}; +use bouncycastle_cipher::modes::{ + 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::{AeadDeclaredEncryptMode, EcbEncryptionKey, ElectronicCodeBook}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::symmetric_key::{ + AeadEncryptionKey, BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, MacGenerationKey, + SymmetricDecryptionKey, SymmetricEncryptionKey, +}; use bouncycastle_core::traits::{ - AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, - StreamCipherEncryptor, SymmetricCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; @@ -41,28 +48,47 @@ 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. +/// 16-byte tag. The mode's in-place one-shots over the keyed permutation are the direct path; +/// the frame parameters are unused by them. const CCM_NONCE_LEN: usize = 12; const CCM_TAG_LEN: usize = 16; -type Aes128CcmEnc = - CcmPacket; +type Aes128CcmMode = CCM; + +/// 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 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 { + 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)); + KeyMaterial::::from_bytes_as_type(&bytes, KeyType::MACKey).unwrap() +} + fn data() -> Vec<[u8; BLOCK_LEN]> { (0..NUM_BLOCKS) .map(|i| core::array::from_fn(|j| (i.wrapping_mul(31).wrapping_add(j)) as u8)) @@ -70,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); }, @@ -93,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); }, @@ -119,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, @@ -206,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, @@ -249,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::<12>::new([0x24u8; 12]); let flat: Vec = data().as_flattened().to_vec(); let mut group = c.benchmark_group("modes::ctr::AES_128"); @@ -259,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, @@ -276,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"); @@ -286,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); }, @@ -301,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(); @@ -328,7 +363,7 @@ fn bench_init(c: &mut Criterion) { /// Decryption does the same work in the opposite order (Sec 6.1 and Sec 6.2), and AAD alongside a /// payload costs the sum of the two measurements here, so neither is measured separately. fn bench_ccm_aes128(c: &mut Criterion) { - let key = key::<16>(); + let perm = AES128Internal::new(&key::<16>()).unwrap(); let nonce = [0x24u8; CCM_NONCE_LEN]; let data = [0xA5u8; DATA_LEN]; let no_aad: [u8; 0] = []; @@ -338,15 +373,14 @@ fn bench_ccm_aes128(c: &mut Criterion) { group.bench_function("encrypt 16KiB, no AAD", |b| { b.iter_batched_ref( - || [0u8; DATA_LEN], - |out| { + || data, + |buf| { black_box( - Aes128CcmEnc::encrypt_detached_out( - black_box(&key), + Aes128CcmMode::encrypt_detached_in_place_nonce( + black_box(&perm), &nonce, &no_aad, - black_box(&data), - out, + buf, ) .unwrap(), ) @@ -356,16 +390,15 @@ 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] = []; black_box( - Aes128CcmEnc::encrypt_detached_out( - black_box(&key), + Aes128CcmMode::encrypt_detached_in_place_nonce( + black_box(&perm), &nonce, black_box(&data), - &no_aad, &mut out, ) .unwrap(), @@ -376,6 +409,60 @@ fn bench_ccm_aes128(c: &mut Criterion) { group.finish(); } +/// The frame key's [`AeadEncryptionKey`] one-shot against the mode's in-place one-shot on the +/// same message. +/// +/// 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 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]; + let mut rng = FixedSeedRNG::::new(nonce); + + let mut group = c.benchmark_group("modes::ccm::one_shot"); + group.throughput(Throughput::Bytes(CCM_BUFFER_LEN as u64)); + + group.bench_function("AeadEncryptionKey::encrypt_detached_out 4KiB", |b| { + b.iter_batched_ref( + || [0u8; CCM_BUFFER_LEN], + |out| { + black_box( + black_box(&key) + .encrypt_detached_out(&mut rng, &no_aad, black_box(&data), out) + .unwrap(), + ) + }, + BatchSize::LargeInput, + ) + }); + + // The same 4 KiB and nonce through the mode's in-place one-shot, for the ratio. + group.bench_function("CCM::encrypt_detached_in_place_nonce 4KiB", |b| { + b.iter_batched_ref( + || data, + |buf| { + black_box( + Aes128CcmMode::encrypt_detached_in_place_nonce( + black_box(&perm), + &nonce, + &no_aad, + buf, + ) + .unwrap(), + ) + }, + BatchSize::LargeInput, + ) + }); + + group.finish(); +} + /// GCM over the same 16 KiB as the CTR and CCM groups; see the module docs for what to compare /// it against. The nonce comes from a cheap deterministic RNG created outside the timed loop, so /// the figures measure the mode and not OS entropy or DRBG construction. @@ -383,7 +470,7 @@ fn bench_ccm_aes128(c: &mut Criterion) { /// Decryption is the same work in the other order, and AAD alongside a payload costs the sum of /// the two measurements here, so neither is measured separately. fn bench_gcm_aes128(c: &mut Criterion) { - let key = key::<16>(); + let key = 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]); @@ -396,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, @@ -416,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(), ) }) }); @@ -434,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]); @@ -447,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, @@ -464,8 +536,70 @@ fn bench_gcm_aes256(c: &mut Criterion) { group.finish(); } +/// 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. 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 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 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| { + b.iter(|| black_box(key_object_256.mac(black_box(&data)))) + }); + + group.finish(); +} + +/// 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. +fn bench_gmac(c: &mut Criterion) { + let data: Vec = data().concat(); + let mut group = c.benchmark_group("modes::gmac"); + group.throughput(Throughput::Bytes(DATA_LEN as u64)); + let mut rng = FixedSeedRNG::::new([0x24u8; GCM_NONCE_LEN]); + + let key128 = mac_key::<16>(); + let key_object_128 = AES128Key::::from_key_material(&key128).unwrap(); + group.bench_function("AES_128 key object 16KiB", |b| { + b.iter(|| { + let (gen_, _) = key_object_128.create_generator(&mut rng).unwrap(); + black_box(gen_.mac(black_box(&data))) + }) + }); + let key256 = mac_key::<32>(); + let key_object_256 = AES256Key::::from_key_material(&key256).unwrap(); + group.bench_function("AES_256 key object 16KiB", |b| { + b.iter(|| { + let (gen_, _) = key_object_256.create_generator(&mut rng).unwrap(); + black_box(gen_.mac(black_box(&data))) + }) + }); + + group.finish(); +} + criterion_group!( benches, bench_aes128cbc, bench_cfb_aes128, bench_cfb8_aes128, bench_ctr_aes128, - bench_ecb_aes128, bench_ccm_aes128, bench_gcm_aes128, bench_gcm_aes256, bench_init + bench_ecb_aes128, bench_ccm_aes128, bench_ccm_one_shot_pair, bench_gcm_aes128, + bench_gcm_aes256, bench_init, bench_cmac, bench_gmac ); criterion_main!(benches); diff --git a/crypto/aes/src/cbc.rs b/crypto/aes/src/cbc.rs index f853644e4..a682b54d2 100644 --- a/crypto/aes/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -2,93 +2,83 @@ //! //! 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 +//! 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 //! -//! 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_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; //! -//! // 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) +//! 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"); //! -//! // 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"); +//! // 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 = AESDec::decrypt(&key, &iv, &ciphertext).expect("decryption"); +//! let recovered = key.decrypt(&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_CBC_128, AES_BLOCK_LEN}; +//! 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_cipher::{Decrypting, Encrypting}; -//! use bouncycastle_cipher::padding::PKCS7; -//! -//! // Define ourselves convenience types. -//! type AESEnc = AES_CBC_128; -//! type AESDec = AES_CBC_128; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! -//! 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, 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, 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. -//! 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, &iv).expect("decrypt init"); +//! // 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"); -//! 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]); @@ -101,41 +91,48 @@ //! 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_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; //! -//! // Define ourselves a convenience type for the encryption direction with no padding. -//! type Enc = AES_CBC_128; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +//! let 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 (_iv, written) = Enc::encrypt_out(&key, &[0u8; 16], &mut out).expect("aligned"); +//! 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. -//! 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::AES_CBC_128; -//! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::SymmetricCipherEncryptor; +//! 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 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, _iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); +//! // 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; //! ``` //! @@ -156,23 +153,21 @@ 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. #[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 +175,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 +192,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 4399aa43a..196cffc7b 100644 --- a/crypto/aes/src/ccm.rs +++ b/crypto/aes/src/ccm.rs @@ -6,23 +6,20 @@ //! 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. //! -//! There are two families, because CCM must know the payload length before it starts (Sec 3), -//! and the two learn that length from different places: -//! -//! * [`AES_CCM_128`] and friends implement [`AEADCipherEncryptor`] / [`AEADCipherDecryptor`] -//! like every other mode in this crate. Their one-shots, with the whole message in hand, take -//! any payload and AAD length and generate the nonce unless the caller supplies one through -//! the trait's `_nonce` forms. Their streaming methods, whose constructor is handed no lengths, -//! 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. -//! * [`AES_CCM_128_Packet`] and friends are the inherent API for Sec 3's "packet environment", -//! where "all of the data is available in storage before CCM is applied": they are told the AAD -//! and payload lengths per message, read off the slices by the one-shots or declared up front -//! to `new` or `new_with_lengths`, and then process the payload in place. The nonce is -//! **supplied**, 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. The trait -//! family's one-shots are these under a generated nonce. +//! 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 //! @@ -37,64 +34,64 @@ //! 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`. +//! 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, < 16 MiB -//! AES_CCM_128_Packet +//! // 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 -//! AES_CCM_128_Packet +//! AES128Key> //! ``` //! //! # Usage Examples //! -//! ## Generic AEAD API +//! ## Fixed frames, through the generic AEAD pair //! -//! 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: +//! 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::AES_CCM_128; +//! 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_cipher::{Decrypting, Encrypting}; +//! 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. -//! type AESEnc = AES_CCM_128; -//! type AESDec = AES_CCM_128; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); +//! let key = AES128Key::>::from_key_material(&material) +//! .expect("a valid AES-128 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"); +//! // 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[..]); -//! 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"); +//! 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 = AESDec::do_decrypt_init(&key, &nonce).expect("init"); +//! 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"); @@ -102,114 +99,63 @@ //! assert_eq!(opened, frame); //! ``` //! -//! ## One-shot API +//! ## Any length, a supplied nonce, in the spec's own layout //! -//! [`CcmPacket`]'s inherent `encrypt_out` / `decrypt_out`, expose the CCM-specific parameters, specifically -//! the ability to provide the nonce, and to produce and consume the spec's own ciphertext layout, -//! `ciphertext || tag` (Sec 6.1 step 8): +//! 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::{AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_aes::{AES256Key, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_cipher::modes::CCM; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! type AESEnc = AES_CCM_256_Packet; -//! type AESDec = AES_CCM_256_Packet; +//! use bouncycastle_core::symmetric_key::{AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial}; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +//! 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 generated. It is the caller's responsibility that it never repeat under this 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 mut sealed = vec![0u8; message.len() + CCM_TAG_LEN]; -//! let n = AESEnc::encrypt_out(&key, &nonce, aad, message, &mut sealed).expect("encryption"); -//! assert_eq!(n, sealed.len(), "the ciphertext plus the tag"); +//! 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 mut opened = vec![0u8; message.len()]; -//! let n = AESDec::decrypt_out(&key, &nonce, aad, &sealed, &mut opened).expect("decryption"); -//! assert_eq!(&opened[..n], message); +//! 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!(AESDec::decrypt_out(&key, &nonce, aad, &tampered, &mut opened).is_err()); -//! assert!(AESDec::decrypt_out(&key, &nonce, b"other header", &sealed, &mut opened).is_err()); -//! ``` -//! -//! ## Detached tag -//! -//! For a wire format that carries the tag separately, `encrypt_detached_out` / `decrypt_detached_out` -//! return and take it on its own: -//! -//! ``` -//! use bouncycastle_aes::{AES_CCM_128_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; -//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! type AESEnc = AES_CCM_128_Packet; -//! type AESDec = AES_CCM_128_Packet; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -//! .expect("a 16-byte symmetric cipher key"); -//! let nonce = [0x02u8; CCM_NONCE_LEN]; -//! let message = b"a short packet"; -//! -//! let mut ciphertext = vec![0u8; message.len()]; -//! let (n, tag) = AESEnc::encrypt_detached_out(&key, &nonce, &[], message, &mut ciphertext).expect("encryption"); -//! assert_eq!(n, message.len(), "CCM never expands the payload"); -//! -//! let mut plaintext = vec![0u8; message.len()]; -//! AESDec::decrypt_detached_out(&key, &nonce, &[], &ciphertext, &tag, &mut plaintext).expect("decryption"); -//! assert_eq!(&plaintext[..], message); +//! assert!(key.decrypt_with_aad(&nonce, aad, &tampered).is_err()); +//! assert!(key.decrypt_with_aad(&nonce, b"other header", &sealed).is_err()); //! ``` //! -//! ## Streaming API +//! ## In place //! -//! CCM authenticates the payload length before any payload, so it cannot stream indefinitely -//! (SP 800-38C Sec 3). It can still process data that arrives in pieces, provided the total length -//! is declared up front to `new`; each piece is then encrypted in place, and the tag comes from -//! `do_encrypt_final`: +//! A message already in a buffer of its own is sealed and opened there, with no second buffer: //! //! ``` -//! use bouncycastle_aes::{AES_CCM_128_Packet, CCM_NONCE_LEN, CCM_TAG_LEN}; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::CCM; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_core::symmetric_key::{AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial}; //! -//! // Define ourselves convenience types. -//! type AESEnc = AES_CCM_128_Packet; -//! type AESDec = AES_CCM_128_Packet; -//! -//! let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); -//! let nonce = [0x03u8; CCM_NONCE_LEN]; -//! let aad = b"header"; -//! let plaintext = [0x5Au8; 50]; -//! -//! // Encrypt in 7-byte pieces, each in place. The total length is declared up front. -//! let mut encryptor = AESEnc::new(&key, &nonce, aad, plaintext.len()).expect("encrypt init"); -//! let mut ciphertext = plaintext; -//! for piece in ciphertext.chunks_mut(7) { -//! encryptor.do_encrypt_inplace(piece).expect("encryption"); -//! } -//! // The tag is computed over everything, so it is the last thing out. -//! let tag = encryptor.do_encrypt_final().expect("the tag"); -//! -//! // Decrypt in 19-byte pieces: the boundaries need not match the encryptor's. The bytes -//! // written are not authenticated until `do_decrypt_final` accepts the tag. -//! let mut decryptor = AESDec::new(&key, &nonce, aad, ciphertext.len()).expect("decrypt init"); -//! let mut recovered = ciphertext; -//! for piece in recovered.chunks_mut(19) { -//! decryptor.do_decrypt_inplace(piece).expect("decryption"); -//! } -//! decryptor.do_decrypt_final(&tag).expect("a valid tag"); -//! assert_eq!(recovered, plaintext); +//! // 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 @@ -218,40 +164,45 @@ //! //! | Type | AES-128 | AES-192 | AES-256 | //! |---|---|---|---| -//! | [`AES_CCM_128_Packet`] and friends, either direction | 264 B | 296 B | 328 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 trait pair adds the -//! `AAD_LEN`-byte AAD buffer and, on the decrypting side, the held-back tag. Peak stack over a +//! | [`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. The -//! `AES_CCM_128` one-shots call the `AES_CCM_128_Packet` ones, so they cost only the DRBG the -//! nonce is drawn from on top: +//! `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 | -//! | `AES_CCM_128_Packet` one-shot, two arrays (message, ciphertext) | 35 888 B | -//! | `AES_CCM_128_Packet` streaming, one array encrypted in place | 19 088 B | -//! | `AES_CCM_128` streaming encrypt, two arrays | 37 304 B | -//! | `AES_CCM_128` streaming decrypt, two arrays | 36 144 B | -//! | `AES_CCM_128` one-shot, two arrays | 36 208 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, the nonce that -//! [`AES_CCM_128_Packet`] and friends take must never repeat under one key. +//! All security considerations from [`bouncycastle_cipher::modes::ccm`] apply. Above all, a nonce +//! the caller supplies must never repeat under one key. use crate::AES_BLOCK_LEN; use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; use bouncycastle_cipher::Direction; -use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; +use bouncycastle_cipher::modes::{CcmPacketDecryptor, CcmPacketEncryptor}; // Imports needed for docs #[allow(unused_imports)] +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 @@ -264,70 +215,54 @@ pub const CCM_NONCE_LEN: usize = 12; /// permits. See the module docs on Sec B.2. pub const CCM_TAG_LEN: usize = 16; -/// AES-128 in CCM mode for Sec 3's packet environment, with a `NONCE_LEN`-byte nonce and a -/// `TAG_LEN`-byte tag: the AAD and payload lengths are supplied per message, read off the slices -/// by the one-shots or declared up front to `new` or `new_with_lengths`, along with a -/// caller-supplied nonce. For the generic AEAD traits see [`AES_CCM_128`]. -/// -/// `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. -#[allow(non_camel_case_types)] -pub type AES_CCM_128_Packet = - CcmPacket; - -/// AES-192 in CCM mode with a `NONCE_LEN`-byte nonce and a `TAG_LEN`-byte tag. See [`AES_CCM_128_Packet`]. -#[allow(non_camel_case_types)] -pub type AES_CCM_192_Packet = - CcmPacket; - -/// AES-256 in CCM mode with a `NONCE_LEN`-byte nonce and a `TAG_LEN`-byte tag. See [`AES_CCM_128_Packet`]. -#[allow(non_camel_case_types)] -pub type AES_CCM_256_Packet = - CcmPacket; - /// AES-128 in CCM mode, as an [`AEADCipherEncryptor`] or [`AEADCipherDecryptor`] by `Dir`, for /// 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, and `TAG_LEN` is as for -/// [`AES_CCM_128_Packet`]. See [`CcmEncryptor`] for the rules. +/// 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 [`CcmPacketEncryptor`] +/// for the rules. #[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..189f45184 100644 --- a/crypto/aes/src/cfb.rs +++ b/crypto/aes/src/cfb.rs @@ -3,9 +3,11 @@ //! 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. +//! 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, @@ -15,68 +17,59 @@ //! //! ## One-shot API //! -//! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: -//! //! ``` -//! use bouncycastle_aes::AES_CFB_256; +//! use bouncycastle_aes::AES256Key; +//! use bouncycastle_cipher::modes::CFB; //! 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; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +//! 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"); //! -//! // 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. +//! // Encryption works in place. The IV is drawn from `rng` and returned. //! let mut data = plaintext; -//! let (_, iv) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (_, iv) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); //! -//! AESDec::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); +//! key.decrypt_inplace(&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: +//! 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::AES_CFB_128; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::CFB; //! 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; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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]; //! //! // Encrypt in 7-byte pieces, each in place. -//! let (mut encryptor, iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); +//! 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 = AESDec::do_decrypt_init(&key, &iv).expect("decrypt init"); +//! 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"); @@ -96,17 +89,17 @@ 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. #[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..eb55d952e 100644 --- a/crypto/aes/src/cfb8.rs +++ b/crypto/aes/src/cfb8.rs @@ -2,10 +2,12 @@ //! //! 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. +//! 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 @@ -16,68 +18,59 @@ //! //! ## One-shot API //! -//! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: -//! //! ``` -//! use bouncycastle_aes::AES_CFB8_256; +//! use bouncycastle_aes::AES256Key; +//! use bouncycastle_cipher::modes::CFB8; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! // 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) +//! 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"); //! -//! // 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. +//! // Encryption works in place. The IV is drawn from `rng` and returned. //! let mut data = plaintext; -//! let (_, iv) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (_, iv) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); //! -//! AESDec::decrypt_inplace(&key, &iv, &mut data).expect("decryption"); +//! key.decrypt_inplace(&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: +//! 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::AES_CFB8_128; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::CFB8; //! 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; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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]; //! //! // Encrypt in 3-byte pieces, each in place. -//! let (mut encryptor, iv) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); +//! 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 = AESDec::do_decrypt_init(&key, &iv).expect("decrypt init"); +//! 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"); @@ -91,19 +84,24 @@ //! a message encrypted with one does not decrypt with the other. //! //! ``` -//! use bouncycastle_aes::{AES_CFB8_128, AES_CFB_128}; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::{CFB, CFB8}; //! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; -//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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 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) = AES_CFB8_128::::encrypt_inplace(&key, &mut data).unwrap(); +//! let (_, iv) = cfb8_key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); //! //! // Decrypting CFB8 output as CFB128 does not recover the plaintext. -//! AES_CFB_128::::decrypt_inplace(&key, &iv, &mut data).unwrap(); +//! cfb_key.decrypt_inplace(&iv, &mut data).expect("decryption"); //! assert_ne!(data, plaintext); //! ``` //! @@ -119,17 +117,17 @@ 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. #[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 new file mode 100644 index 000000000..9ec8451ad --- /dev/null +++ b/crypto/aes/src/cmac.rs @@ -0,0 +1,59 @@ +//! 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 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 + +use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal}; +use bouncycastle_cipher::modes::Cmac; + +/// AES-128 in CMAC. +#[allow(non_camel_case_types)] +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<'k> = Cmac<'k, AES192Internal, 16>; + +/// AES-256 in CMAC. See [`AES_CMAC_128`]. +#[allow(non_camel_case_types)] +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..45843f66e 100644 --- a/crypto/aes/src/ctr.rs +++ b/crypto/aes/src/ctr.rs @@ -3,85 +3,79 @@ //! 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. +//! 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 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. +//! 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 //! -//! Basic usage can be obtained via the [`StreamCipherEncryptor`] and [`StreamCipherDecryptor`] API: -//! //! ``` -//! use bouncycastle_aes::AES_CTR_256; +//! use bouncycastle_aes::AES256Key; +//! use bouncycastle_cipher::modes::CTR; //! 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; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! let key = KeyMaterial256::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) +//! 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"); //! -//! // 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. +//! // Encryption works in place. The nonce is drawn from `rng` and returned. //! let mut data = plaintext; -//! let (_, nonce) = AESEnc::encrypt_inplace(&key, &mut data).expect("encryption"); +//! let (_, nonce) = key.encrypt_inplace(&mut rng, &mut data).expect("encryption"); //! -//! AESDec::decrypt_inplace(&key, &nonce, &mut data).expect("decryption"); +//! key.decrypt_inplace(&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: +//! 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::AES_CTR_128; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::CTR; //! 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; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, SymmetricDecryptionKey, SymmetricEncryptionKey}; +//! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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]; //! //! // Encrypt in 7-byte pieces, each in place. -//! let (mut encryptor, nonce) = AESEnc::do_encrypt_init(&key).expect("encrypt init"); +//! 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 = AESDec::do_decrypt_init(&key, &nonce).expect("decrypt init"); +//! 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"); @@ -101,7 +95,7 @@ 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. @@ -109,12 +103,12 @@ pub const CTR_NONCE_LEN: usize = 12; /// AES-128 in CTR mode with a 12-byte nonce. #[allow(non_camel_case_types)] -pub type AES_CTR_128 = Ctr; +pub type AES_CTR_128<'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 294dd56b5..17ba6e3af 100644 --- a/crypto/aes/src/gcm.rs +++ b/crypto/aes/src/gcm.rs @@ -6,108 +6,104 @@ //! 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. +//! `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 [`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. +//! (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 //! -//! Basic usage can be obtained via the [`AEADCipherEncryptor`] and [`AEADCipherDecryptor`] API, -//! which returns the tag separately from the ciphertext: +//! The detached one-shots return the tag separately from the ciphertext: //! //! ``` -//! use bouncycastle_aes::AES_GCM_256; +//! use bouncycastle_aes::AES256Key; +//! use bouncycastle_cipher::modes::GCM; //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! // 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) +//! 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 generated for you and returned; there is no API for supplying one. -//! let (nonce, ciphertext, tag) = AESEnc::encrypt_detached(&key, aad, message).expect("encryption"); +//! // 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 = AESDec::decrypt_detached(&key, &nonce, aad, &ciphertext, &tag).expect("decryption"); +//! 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!(AESDec::decrypt_detached(&key, &nonce, aad, &tampered, &tag).is_err()); -//! assert!(AESDec::decrypt_detached(&key, &nonce, b"other header", &ciphertext, &tag).is_err()); +//! 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 [`SymmetricCipherEncryptor`] and [`SymmetricCipherDecryptor`] API the tag is -//! appended to the ciphertext, so the output is 16 bytes longer than the input: +//! 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::AES_GCM_128; +//! use bouncycastle_aes::AES128Key; +//! use bouncycastle_cipher::modes::GCM; //! 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; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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"); //! 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 (nonce, ciphertext) = key.encrypt_with_aad(&mut rng, &[], message).expect("encryption"); +//! assert_eq!(ciphertext.len(), 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); +//! let plaintext = key.decrypt_with_aad(&nonce, &[], &ciphertext).expect("decryption"); +//! assert_eq!(plaintext, 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 +//! 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::AES_GCM_128; +//! 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_cipher::{Decrypting, Encrypting}; -//! -//! // Define ourselves convenience types. -//! type AESEnc = AES_GCM_128; -//! type AESDec = AES_GCM_128; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! 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"); //! 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"); +//! 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) { @@ -123,7 +119,7 @@ //! // 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"); +//! 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) { @@ -148,6 +144,10 @@ use bouncycastle_cipher::modes::Gcm; use bouncycastle_cipher::modes::GCM_NONCE_LEN; #[allow(unused_imports)] use bouncycastle_cipher::{Decrypting, Encrypting}; + +// 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, @@ -156,12 +156,12 @@ use bouncycastle_core::traits::{ /// AES-128 in GCM with a 128-bit tag. #[allow(non_camel_case_types)] -pub type AES_GCM_128 = Gcm; +pub type AES_GCM_128<'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; +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 new file mode 100644 index 000000000..feb60a4cb --- /dev/null +++ b/crypto/aes/src/gmac.rs @@ -0,0 +1,108 @@ +//! 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. +//! +//! 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 + +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::{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. +#[allow(non_camel_case_types)] +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<'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<'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<'k>( + &'k self, + rng: &mut dyn RNG, + ) -> Result< + (Gmac<'k, AESInternal

, Encrypting, TAG_LEN>, [u8; GCM_NONCE_LEN]), + SymmetricCipherError, + > { + 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<'k>( + &'k self, + nonce: &[u8; GCM_NONCE_LEN], + ) -> 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<'k>( + &'k self, + nonce: &[u8; GCM_NONCE_LEN], + ) -> 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 451015a8a..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; @@ -319,9 +330,11 @@ impl ModeNames for AES128Internal { const CCM_ALG_NAME: &'static str = "AES_CCM_128"; const CFB_ALG_NAME: &'static str = "AES_CFB_128"; const CFB8_ALG_NAME: &'static str = "AES_CFB8_128"; + const CMAC_ALG_NAME: &'static str = "AES_CMAC_128"; const CTR_ALG_NAME: &'static str = "AES_CTR_128"; const ECB_ALG_NAME: &'static str = "AES_ECB_128"; const GCM_ALG_NAME: &'static str = "AES_GCM_128"; + const GMAC_ALG_NAME: &'static str = "AES_GMAC_128"; } impl ModeNames for AES192Internal { @@ -329,9 +342,11 @@ impl ModeNames for AES192Internal { const CCM_ALG_NAME: &'static str = "AES_CCM_192"; const CFB_ALG_NAME: &'static str = "AES_CFB_192"; const CFB8_ALG_NAME: &'static str = "AES_CFB8_192"; + const CMAC_ALG_NAME: &'static str = "AES_CMAC_192"; const CTR_ALG_NAME: &'static str = "AES_CTR_192"; const ECB_ALG_NAME: &'static str = "AES_ECB_192"; const GCM_ALG_NAME: &'static str = "AES_GCM_192"; + const GMAC_ALG_NAME: &'static str = "AES_GMAC_192"; } impl ModeNames for AES256Internal { @@ -339,7 +354,9 @@ impl ModeNames for AES256Internal { const CCM_ALG_NAME: &'static str = "AES_CCM_256"; const CFB_ALG_NAME: &'static str = "AES_CFB_256"; const CFB8_ALG_NAME: &'static str = "AES_CFB8_256"; + const CMAC_ALG_NAME: &'static str = "AES_CMAC_256"; const CTR_ALG_NAME: &'static str = "AES_CTR_256"; const ECB_ALG_NAME: &'static str = "AES_ECB_256"; const GCM_ALG_NAME: &'static str = "AES_GCM_256"; + const GMAC_ALG_NAME: &'static str = "AES_GMAC_256"; } 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 new file mode 100644 index 000000000..4a867e10c --- /dev/null +++ b/crypto/aes/src/key.rs @@ -0,0 +1,519 @@ +//! 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), 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 [`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. + +use crate::hazmat::{AES128Internal, AES192Internal, AES256Internal, AESInternal}; +use crate::{AES128Params, AES192Params, AES256Params, AESParams}; +use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; +use bouncycastle_core::hazmat::{ + 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::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, AeadDecryptionKey, AeadEncryptionKey, + BlockDecryptionKey, BlockEncryptionKey, FromKeyMaterial, MacGenerationKey, MacVerificationKey, + SymmetricDecryptionKey, SymmetricEncryptionKey, +}; +use bouncycastle_core::traits::RNG; + +/// An AES key for the mode `M`, parameterised by key length. Use the aliases [`AES128Key`], +/// [`AES192Key`] and [`AES256Key`] rather than naming this directly. +/// +/// 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`. +pub struct AESKey>> { + pub(crate) state: M::KeyState, +} + +/// An AES-128 key for the mode `M`: 16 bytes of material, expanded to a 10-round schedule. +#[allow(non_camel_case_types)] +pub type AES128Key = AESKey; +/// An AES-192 key for the mode `M`: 24 bytes of material, expanded to a 12-round schedule. +#[allow(non_camel_case_types)] +pub type AES192Key = AESKey; +/// An AES-256 key for the mode `M`: 32 bytes of material, expanded to a 14-round schedule. +#[allow(non_camel_case_types)] +pub type AES256Key = AESKey; + +// Written out rather than derived so that `M`, a marker type, need not be `Clone`. +impl>> Clone for AESKey { + fn clone(&self) -> Self { + Self { state: self.state.clone() } + } +} + +// 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( + key: &dyn KeyMaterialTrait, + expected: KeyType, +) -> Result<(), SymmetricCipherError> { + if key.key_type() != expected { + return Err(KeyMaterialError::InvalidKeyType( + "The key is not of the KeyType this mode requires.", + ) + .into()); + } + Ok(()) +} + +/// The permutation accepts only a `SymmetricCipherKey`, so a MAC mode's key -- already checked to +/// be the `MACKey` the mode asks for -- is handed to it as a copy retagged at the same strength, +/// used for nothing else. The strength check in the permutation's constructor still applies. +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) +} + +// The constructors are implemented per key length rather than once over `P`, because stable Rust +// cannot name `KeyMaterial<{ P::KEY_LEN }>` in a generic signature; the fixed-capacity argument +// is what makes a wrong-length key a compile error rather than a runtime one. + +impl> FromKeyMaterial<16> for AES128Key { + const KEY_TYPE: KeyType = M::KEY_TYPE; + + /// Validates and expands a 16-byte key. + /// + /// # Errors + /// * [`KeyMaterialError::InvalidKeyType`] if the key is not of [`Self::KEY_TYPE`]: the + /// `SymmetricCipherKey` a cipher mode asks for, or the `MACKey` a MAC mode does. + /// * [`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<16>) -> Result { + check_key_type(key, M::KEY_TYPE)?; + let perm = match M::KEY_TYPE { + KeyType::SymmetricCipherKey => AES128Internal::new(key)?, + _ => AES128Internal::new(&as_cipher_key(key)?)?, + }; + Ok(Self { state: M::key_state(perm) }) + } +} + +impl> FromKeyMaterial<24> for AES192Key { + const KEY_TYPE: KeyType = M::KEY_TYPE; + + /// Validates and expands a 24-byte key; the errors are those of the 16-byte form, at 24 + /// bytes and 192 bits. + fn from_key_material(key: &KeyMaterial<24>) -> Result { + check_key_type(key, M::KEY_TYPE)?; + let perm = match M::KEY_TYPE { + KeyType::SymmetricCipherKey => AES192Internal::new(key)?, + _ => AES192Internal::new(&as_cipher_key(key)?)?, + }; + Ok(Self { state: M::key_state(perm) }) + } +} + +impl> FromKeyMaterial<32> for AES256Key { + const KEY_TYPE: KeyType = M::KEY_TYPE; + + /// Validates and expands a 32-byte key; the errors are those of the 16-byte form, at 32 + /// bytes and 256 bits. + fn from_key_material(key: &KeyMaterial<32>) -> Result { + check_key_type(key, M::KEY_TYPE)?; + let perm = match M::KEY_TYPE { + KeyType::SymmetricCipherKey => AES256Internal::new(key)?, + _ => AES256Internal::new(&as_cipher_key(key)?)?, + }; + Ok(Self { state: M::key_state(perm) }) + } +} + +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. + +impl< + P, + M, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> AeadEncryptionKey for AESKey +where + P: AESParams, + M: AeadEncryptMode, KEY_LEN, NONCE_LEN, TAG_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; 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) + } + + // Forwarded rather than left to the trait's provided body so that a mode can override it: + // CCM's streaming worker is fixed-frame, and its in-place one-shot goes another way. + 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 AESKey +where + P: AESParams, + M: AeadDecryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, +{ + 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 AESKey +where + P: AESParams, + M: AeadDeclaredEncryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, +{ + 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) + } + + // Forwarded so that the mode's own in-place path is the one that runs. + 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 AESKey +where + P: AESParams, + M: AeadDeclaredDecryptMode, KEY_LEN, NONCE_LEN, TAG_LEN, FINAL_LEN>, +{ + 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 + 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<'k> + = M::Worker<'k> + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + M::mac_init(&self.state) + } +} + +impl>> MacVerificationKey for AESKey { + type Verifier<'k> + = M::Worker<'k> + where + Self: 'k; + + 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 1c72cf6b6..20a95af36 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -18,8 +18,14 @@ //! * [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 +//! +//! 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 [`key`]. //! //! 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. @@ -172,9 +178,12 @@ pub mod cbc; pub mod ccm; pub mod cfb; pub mod cfb8; +pub mod cmac; pub mod ctr; pub mod gcm; +pub mod gmac; pub mod hazmat; +pub mod key; mod round; mod sbox; mod schedule; @@ -256,11 +265,11 @@ impl AESParams for AES256Params { } pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; -pub use ccm::{ - AES_CCM_128, AES_CCM_128_Packet, AES_CCM_192, AES_CCM_192_Packet, AES_CCM_256, - AES_CCM_256_Packet, CCM_NONCE_LEN, CCM_TAG_LEN, -}; +pub use ccm::{AES_CCM_128, AES_CCM_192, AES_CCM_256, CCM_NONCE_LEN, CCM_TAG_LEN}; pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; +pub use cmac::{AES_CMAC_128, AES_CMAC_192, AES_CMAC_256}; pub use ctr::{AES_CTR_128, AES_CTR_192, AES_CTR_256, CTR_NONCE_LEN}; pub use gcm::{AES_GCM_128, AES_GCM_192, AES_GCM_256}; +pub use gmac::{AES_GMAC_128, AES_GMAC_192, AES_GMAC_256}; +pub use key::{AES128Key, AES192Key, AES256Key, AESKey}; diff --git a/crypto/aes/tests/alg_name_tests.rs b/crypto/aes/tests/alg_name_tests.rs new file mode 100644 index 000000000..481a4bd3a --- /dev/null +++ b/crypto/aes/tests/alg_name_tests.rs @@ -0,0 +1,223 @@ +//! 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. + +use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256, AES128Internal}; +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, AES128Key, CCM_NONCE_LEN, CCM_TAG_LEN, +}; +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`. +fn check(name: &str, strength: SecurityStrength) { + assert_eq!(Enc::ALG_NAME, name); + assert_eq!(Dec::ALG_NAME, name); + assert_eq!(Enc::MAX_SECURITY_STRENGTH, strength, "{name}"); + assert_eq!(Dec::MAX_SECURITY_STRENGTH, strength, "{name}"); +} + +#[test] +fn cbc() { + 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<'_, Decrypting, NoPadding>>( + "AES_CBC_128", _128bit, + ); + check::, AES_CBC_128<'_, Decrypting, PKCS7>>( + "AES_CBC_128", _128bit, + ); + check::, AES_CBC_192<'_, Decrypting, NoPadding>>( + "AES_CBC_192", _192bit, + ); + check::, AES_CBC_192<'_, Decrypting, PKCS7>>( + "AES_CBC_192", _192bit, + ); + check::, AES_CBC_256<'_, Decrypting, NoPadding>>( + "AES_CBC_256", _256bit, + ); + check::, AES_CBC_256<'_, Decrypting, PKCS7>>( + "AES_CBC_256", _256bit, + ); +} + +#[test] +fn ecb() { + use SecurityStrength::*; + check::, AES_ECB_128<'_, Decrypting, NoPadding>>( + "AES_ECB_128", _128bit, + ); + check::, AES_ECB_128<'_, Decrypting, PKCS7>>( + "AES_ECB_128", _128bit, + ); + check::, AES_ECB_192<'_, Decrypting, NoPadding>>( + "AES_ECB_192", _192bit, + ); + check::, AES_ECB_192<'_, Decrypting, PKCS7>>( + "AES_ECB_192", _192bit, + ); + check::, AES_ECB_256<'_, Decrypting, NoPadding>>( + "AES_ECB_256", _256bit, + ); + check::, AES_ECB_256<'_, Decrypting, PKCS7>>( + "AES_ECB_256", _256bit, + ); +} + +#[test] +fn cfb_cfb8_ctr() { + use SecurityStrength::*; + 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<'_, 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<'_, 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<'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<'_, Decrypting>>("AES_GCM_128", _128bit); + check::, AES_GCM_192<'_, Decrypting>>("AES_GCM_192", _192bit); + check::, AES_GCM_256<'_, Decrypting>>("AES_GCM_256", _256bit); +} + +#[test] +fn ccm() { + use SecurityStrength::*; + 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<'_, Decrypting, 7, 4, 16, 32>>( + "AES_CCM_128", _128bit, + ); + check::, AES_CCM_128<'_, Decrypting, N, T, 16, 32>>( + "AES_CCM_128", _128bit, + ); + check::, AES_CCM_192<'_, Decrypting, N, T, 16, 32>>( + "AES_CCM_192", _192bit, + ); + check::, AES_CCM_256<'_, Decrypting, N, T, 16, 32>>( + "AES_CCM_256", _256bit, + ); +} + +#[test] +fn gmac() { + use SecurityStrength::*; + 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] +fn cmac() { + use SecurityStrength::*; + assert_eq!( + (AES_CMAC_128::ALG_NAME, AES_CMAC_128::MAX_SECURITY_STRENGTH), + ("AES_CMAC_128", _128bit) + ); + assert_eq!( + (AES_CMAC_192::ALG_NAME, AES_CMAC_192::MAX_SECURITY_STRENGTH), + ("AES_CMAC_192", _192bit) + ); + assert_eq!( + (AES_CMAC_256::ALG_NAME, AES_CMAC_256::MAX_SECURITY_STRENGTH), + ("AES_CMAC_256", _256bit) + ); +} + +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 +/// itself by asserting the layouts coincide with the fully spelled-out types. ECB's projection +/// carries `INIT_DATA_LEN = 0` where CBC's carries 16. +#[test] +fn the_aliases_name_the_expected_types() { + assert_eq!( + size_of::>(), + size_of::, PKCS7, 16, 16>>( + ) + ); + assert_eq!( + 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::>() + ); + + assert_eq!( + size_of::>(), + size_of::, PKCS7, 0, 16>>( + ) + ); + assert_eq!( + size_of::>(), + size_of::, PKCS7, 0, 16>>( + ) + ); + + assert_eq!( + size_of::>(), + size_of::>() + ); + assert_eq!( + 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/alias_tests.rs b/crypto/aes/tests/alias_tests.rs deleted file mode 100644 index d0c2f5dd7..000000000 --- a/crypto/aes/tests/alias_tests.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! What each public AES alias is: the algorithm it reports and the type it resolves to. -//! -//! Each 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. The direction and the padding are -//! type parameters of one algorithm, not different algorithms, so every combination of them must -//! report the same name. So must the `AES_CCM_*_Packet` aliases, which are a second API onto the -//! same mode rather than another algorithm. - -use bouncycastle_aes::hazmat::{AES_ECB_128, AES_ECB_192, AES_ECB_256, AES128Internal}; -use bouncycastle_aes::{ - AES_CBC_128, AES_CBC_192, AES_CBC_256, AES_CCM_128, AES_CCM_128_Packet, AES_CCM_192, - AES_CCM_192_Packet, AES_CCM_256, AES_CCM_256_Packet, AES_CFB_128, AES_CFB_192, AES_CFB_256, - AES_CFB8_128, AES_CFB8_192, AES_CFB8_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, AES_GCM_128, - AES_GCM_192, AES_GCM_256, CCM_NONCE_LEN, CCM_TAG_LEN, -}; -use bouncycastle_cipher::modes::hazmat::ecb::Ecb; -use bouncycastle_cipher::modes::{Cbc, Gcm}; -use bouncycastle_cipher::padding::{ - NoPadding, PKCS7, PaddedBlockCipherDecryptor, PaddedBlockCipherEncryptor, -}; -use bouncycastle_cipher::{Decrypting, Encrypting}; -use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::Algorithm; -use core::mem::size_of; - -/// Both directions of one algorithm report `name` and `strength`. -fn check(name: &str, strength: SecurityStrength) { - assert_eq!(Enc::ALG_NAME, name); - assert_eq!(Dec::ALG_NAME, name); - assert_eq!(Enc::MAX_SECURITY_STRENGTH, strength, "{name}"); - assert_eq!(Dec::MAX_SECURITY_STRENGTH, strength, "{name}"); -} - -#[test] -fn cbc() { - use SecurityStrength::*; - check::, AES_CBC_128>( - "AES_CBC_128", _128bit, - ); - check::, AES_CBC_128>("AES_CBC_128", _128bit); - check::, AES_CBC_192>( - "AES_CBC_192", _192bit, - ); - check::, AES_CBC_192>("AES_CBC_192", _192bit); - check::, AES_CBC_256>( - "AES_CBC_256", _256bit, - ); - check::, AES_CBC_256>("AES_CBC_256", _256bit); -} - -#[test] -fn ecb() { - use SecurityStrength::*; - check::, AES_ECB_128>( - "AES_ECB_128", _128bit, - ); - check::, AES_ECB_128>("AES_ECB_128", _128bit); - check::, AES_ECB_192>( - "AES_ECB_192", _192bit, - ); - check::, AES_ECB_192>("AES_ECB_192", _192bit); - check::, AES_ECB_256>( - "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_CFB8_128>("AES_CFB8_128", _128bit); - check::, AES_CFB8_192>("AES_CFB8_192", _192bit); - check::, AES_CFB8_256>("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); -} - -#[test] -fn gcm() { - 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); -} - -#[test] -fn ccm() { - use SecurityStrength::*; - const N: usize = CCM_NONCE_LEN; - const T: usize = CCM_TAG_LEN; - check::, AES_CCM_128_Packet>( - "AES_CCM_128", _128bit, - ); - check::, AES_CCM_192_Packet>( - "AES_CCM_192", _192bit, - ); - check::, AES_CCM_256_Packet>( - "AES_CCM_256", _256bit, - ); - // The nonce and tag lengths are parameters too, not part of the name. - check::, AES_CCM_128_Packet>( - "AES_CCM_128", _128bit, - ); - - check::, AES_CCM_128>( - "AES_CCM_128", _128bit, - ); - check::, AES_CCM_192>( - "AES_CCM_192", _192bit, - ); - check::, AES_CCM_256>( - "AES_CCM_256", _256bit, - ); -} - -/// The aliases must resolve to exactly the types they claim to, at both directions. -/// -/// 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 -/// itself by asserting the layouts coincide with the fully spelled-out types. ECB's projection -/// carries `INIT_DATA_LEN = 0` where CBC's carries 16. -#[test] -fn the_aliases_name_the_expected_types() { - assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>, - >() - ); - assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 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::>() - ); - - assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherEncryptor, PKCS7, 16, 0, 16>, - >() - ); - assert_eq!( - size_of::>(), - size_of::< - PaddedBlockCipherDecryptor, PKCS7, 16, 0, 16>, - >() - ); - - assert_eq!( - size_of::>(), - size_of::>() - ); - assert_eq!( - size_of::>(), - size_of::>() - ); -} diff --git a/crypto/aes/tests/cbc_bc-test-data.rs b/crypto/aes/tests/cbc_bc-test-data.rs index c3bf389bd..ca2ed7fb4 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; @@ -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 9ca9a2c27..054536074 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; @@ -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,16 +89,22 @@ 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) - .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); + 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); assert_eq!(ct, expected_ct, "tcId {tc_id}: ciphertext mismatch"); @@ -102,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), @@ -129,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], @@ -139,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 e10ed819d..bd5e705c6 100644 --- a/crypto/aes/tests/ccm_bc-test-data.rs +++ b/crypto/aes/tests/ccm_bc-test-data.rs @@ -9,7 +9,7 @@ //! //! The set has **no `tag` field anywhere**. An encrypt group's answer `ct` is the ciphertext with //! the tag appended, and a decrypt group's input `ct` is the same, which is exactly SP 800-38C -//! Sec 6.1 step 8's own output string. So the cases go through [`CcmPacket::encrypt_out`] / [`CcmPacket::decrypt_out`], +//! Sec 6.1 step 8's own output string. So the cases go through the mode's in-place one-shots with the tag split off, //! the inline pair, and the group's `payloadLen` / `tagLen` are only needed to pick `TAG_LEN` and //! to check the answer's length. //! @@ -46,11 +46,11 @@ //! set is `testType: "AFT"`, so nothing is skipped for that reason. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::CcmPacket; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_cipher::modes::{CCM, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::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; @@ -73,12 +73,8 @@ enum Decrypted { TagCheckFailed, } -/// Runs one encrypt case: `CcmPacket::encrypt_out` must produce the response file's `ct`, which is -/// `ciphertext || tag`. -/// -/// Also re-runs it through the length-declared streaming API in several chunkings, since these are -/// the only real vectors available for that path and the one-shot is a single call over the whole -/// payload. +/// Runs one encrypt case through the mode's in-place one-shot, and returns the response file's +/// layout, `ciphertext || tag`. fn encrypt_case( key: &KeyMaterial, nonce: &[u8; NONCE_LEN], @@ -86,33 +82,14 @@ fn encrypt_case( plaintext: &[u8], ) -> Vec where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { - let mut inline = vec![0u8; plaintext.len() + TAG_LEN]; - let written = CcmPacket::::encrypt_out( - key, nonce, aad, plaintext, &mut inline, - ) - .expect("CCM encryption of a valid ACVP case"); - assert_eq!(written, inline.len(), "the inline layout writes ciphertext || tag"); - - // The same answer must come out of the streaming API, in any chunking of both phases. - for chunk in [1usize, 5, 16] { - let mut ccm = CcmPacket::::new( - key, - nonce, - aad, - plaintext.len(), - ) - .expect("streaming init"); - let mut streamed = plaintext.to_vec(); - for piece in streamed.chunks_mut(chunk) { - ccm.do_encrypt_inplace(piece).expect("update"); - } - let tag = ccm.do_encrypt_final().expect("final"); - assert_eq!(&streamed[..], &inline[..plaintext.len()], "streamed in {chunk}-byte chunks"); - assert_eq!(&tag[..], &inline[plaintext.len()..], "streamed tag, {chunk}-byte chunks"); - } - + let perm = P::new(key).expect("an ACVP key"); + let mut inline = plaintext.to_vec(); + let tag = + CCM::::encrypt_detached_in_place_nonce(&perm, nonce, aad, &mut inline) + .expect("CCM encryption of a valid ACVP case"); + inline.extend_from_slice(&tag); inline } @@ -124,16 +101,15 @@ fn decrypt_case( ct_and_tag: &[u8], ) -> Decrypted where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { - let mut plaintext = vec![0u8; ct_and_tag.len().saturating_sub(TAG_LEN)]; - match CcmPacket::::decrypt_out( - key, nonce, aad, ct_and_tag, &mut plaintext, + let perm = P::new(key).expect("an ACVP key"); + let (ct, tag) = ct_and_tag.split_last_chunk::().expect("a tag on every vector"); + let mut plaintext = ct.to_vec(); + match CCM::::decrypt_detached_in_place( + &perm, nonce, aad, &mut plaintext, tag, ) { - Ok(n) => { - plaintext.truncate(n); - Decrypted::Plaintext(plaintext) - } + Ok(()) => Decrypted::Plaintext(plaintext), Err(SymmetricCipherError::AEADTagCheckFailed) => { assert!( plaintext.iter().all(|b| *b == 0), diff --git a/crypto/aes/tests/ccm_key_tests.rs b/crypto/aes/tests/ccm_key_tests.rs new file mode 100644 index 000000000..868408407 --- /dev/null +++ b/crypto/aes/tests/ccm_key_tests.rs @@ -0,0 +1,247 @@ +//! 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::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::{ + AeadDeclaredDecryptionKey, AeadDeclaredEncryptionKey, FromKeyMaterial, +}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; +use bouncycastle_core_test_framework::FixedSeedRNG; + +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 rng() -> FixedSeedRNG<12> { + FixedSeedRNG::<12>::new([0x24u8; 12]) +} + +const AAD: &[u8] = b"header, sent in the clear"; +const LENS: [usize; 9] = [0, 1, 15, 16, 63, 64, 65, 200, 1000]; + +fn message(len: usize) -> Vec { + (0..len).map(|i| (i * 31 % 251) as u8).collect() +} + +/// 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]) { + 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) +} + +/// Every form on the key -- in place, `_out`, allocating, drawing and supplied-nonce, and the +/// streams -- takes any length and agrees with the pair byte for byte. +#[test] +fn every_form_takes_any_length_and_agrees_with_the_pair() { + let km = material::<16>(); + let key = AES128Key::>::from_key_material(&km).unwrap(); + + for len in LENS { + let msg = message(len); + let (nonce, ct, tag) = key.encrypt_detached(&mut rng(), AAD, &msg).unwrap(); + assert_eq!(reference(&km, &nonce, &msg), (ct.clone(), tag), "len {len}"); + + let mut buf = msg.clone(); + assert_eq!(key.encrypt_detached_in_place_nonce(&nonce, AAD, &mut buf).unwrap(), tag); + assert_eq!(buf, ct, "len {len}: in place"); + key.decrypt_detached_in_place(&nonce, AAD, &mut buf, &tag).unwrap(); + assert_eq!(buf, msg, "len {len}: in place round trip"); + + let inline = key.encrypt_with_aad_nonce(&nonce, AAD, &msg).unwrap(); + assert_eq!((&inline[..len], &inline[len..]), (&ct[..], &tag[..]), "len {len}: inline"); + assert_eq!(key.decrypt_with_aad(&nonce, AAD, &inline).unwrap(), msg); + assert_eq!(key.decrypt_detached(&nonce, AAD, &ct, &tag).unwrap(), msg); + + // The streams, chunked, agree too: the lengths were declared, so nothing is held back. + let mut enc = key.create_encryptor_nonce(&nonce, AAD.len(), len).unwrap(); + enc.do_update_aad(&AAD[..5]).unwrap(); + enc.do_update_aad(&AAD[5..]).unwrap(); + let mut streamed = vec![0u8; len]; + let mut written = 0; + for piece in msg.chunks(7) { + assert_eq!(enc.do_encrypt_out_len(piece.len()), piece.len()); + written += enc.do_encrypt_out(piece, &mut streamed[written..]).unwrap(); + } + let mut last = [0u8; 16]; + let (n, streamed_tag) = enc.do_encrypt_final_detached_out(&mut last).unwrap(); + assert_eq!((n, streamed, streamed_tag), (0, ct.clone(), tag), "len {len}: streamed"); + + let mut dec = key.create_decryptor(&nonce, AAD.len(), len).unwrap(); + dec.do_update_aad(AAD).unwrap(); + let mut opened = vec![0u8; len]; + let mut written = 0; + for piece in inline.chunks(5) { + written += dec.do_decrypt_out(piece, &mut opened[written..]).unwrap(); + } + assert_eq!(written, len, "len {len}: the payload is released, the tag held"); + assert_eq!(dec.do_decrypt_final().unwrap().1, 0); + assert_eq!(opened, msg, "len {len}: streamed round trip"); + } +} + +/// The streams hold to the declared lengths: more AAD or payload than declared is refused, a +/// final before the payload is complete is refused, and the inline final needs a whole tag. +#[test] +fn the_streams_hold_to_the_declared_lengths() { + let key = AES128Key::>::from_key_material(&material::<16>()).unwrap(); + let nonce = [0x24u8; 12]; + + let mut enc = key.create_encryptor_nonce(&nonce, 4, 8).unwrap(); + assert!(matches!(enc.do_update_aad(&[0u8; 5]), Err(SymmetricCipherError::StateError(_)))); + enc.do_update_aad(&[0u8; 4]).unwrap(); + let mut out = [0u8; 9]; + assert!(matches!( + enc.do_encrypt_out(&[0u8; 9], &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + enc.do_encrypt_out(&[0u8; 4], &mut out).unwrap(); + assert!(matches!(enc.do_encrypt_final(), Err(SymmetricCipherError::StateError(_)))); + + let mut dec = key.create_decryptor(&nonce, 0, 8).unwrap(); + let mut out = [0u8; 8]; + assert!(matches!( + dec.do_decrypt_out(&[0u8; 8 + 16 + 1], &mut out), + Err(SymmetricCipherError::StateError(_)) + )); + dec.do_decrypt_out(&[0u8; 8 + 15], &mut out).unwrap(); + assert!(matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::DecryptionFailed))); + let mut dec = key.create_decryptor(&nonce, 0, 8).unwrap(); + dec.do_decrypt_out(&[0u8; 8 + 1], &mut out).unwrap(); + let mut last = [0u8; 16]; + assert!(matches!( + dec.do_decrypt_final_detached_out(&[0u8; 16], &mut last), + Err(SymmetricCipherError::DecryptionFailed) + )); +} + +/// A.1's payload limit is refused at construction, and an inline ciphertext too short for a tag +/// is malformed before anything is built. +#[test] +fn lengths_the_mode_cannot_encode_are_refused_up_front() { + let key = AES128Key::>::from_key_material(&material::<16>()).unwrap(); + let nonce = [0x24u8; 13]; + assert!(key.create_encryptor_nonce(&nonce, 0, 65535).is_ok()); + assert!(matches!( + key.create_encryptor_nonce(&nonce, 0, 65536), + Err(SymmetricCipherError::GenericError(_)) + )); + let mut out = [0u8; 8]; + assert!(matches!( + key.decrypt_with_aad_out(&nonce, &[], &[0u8; 7], &mut out), + Err(SymmetricCipherError::DecryptionFailed) + )); +} + +/// A failed tag check leaves zeros in the caller's buffer, since the plaintext was written there +/// before the tag could be checked. +#[test] +fn a_bad_tag_or_aad_zeroizes_the_buffer() { + let key = AES192Key::>::from_key_material(&material::<24>()).unwrap(); + let msg = message(200); + let mut buf = msg.clone(); + let (nonce, mut tag) = key.encrypt_detached_in_place(&mut rng(), AAD, &mut buf).unwrap(); + let ct = buf.clone(); + assert!(matches!( + key.decrypt_detached_in_place(&nonce, b"other aad", &mut buf, &tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); + assert!(buf.iter().all(|&b| b == 0)); + tag[0] ^= 1; + let mut buf = ct; + assert!(matches!( + key.decrypt_detached_in_place(&nonce, AAD, &mut buf, &tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); + assert!(buf.iter().all(|&b| b == 0)); +} + +/// A nonce below the 12-byte floor for drawn nonces is reachable through the supplied-nonce +/// forms at any A.1 length, while the drawing forms are never instantiated for it. +#[test] +fn a_short_nonce_is_reachable_through_the_supplied_nonce_forms() { + let km = material::<16>(); + let key = AES128Key::>::from_key_material(&km).unwrap(); + let nonce = [0xA5u8; 8]; + let msg = message(100); + let mut buf = msg.clone(); + let tag = key.encrypt_detached_in_place_nonce(&nonce, AAD, &mut buf).unwrap(); + let (ct, tag_p) = { + 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)); + key.decrypt_detached_in_place(&nonce, AAD, &mut buf, &tag).unwrap(); + assert_eq!(buf, msg); +} + +/// 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::{CcmDecryptor, CcmEncryptor}; + use core::mem::size_of; + 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 c84b1d88a..1c12afd6a 100644 --- a/crypto/aes/tests/ccm_wycheproof.rs +++ b/crypto/aes/tests/ccm_wycheproof.rs @@ -19,12 +19,12 @@ //! # Ciphertext and tag are separate fields, unlike the ACVP set //! //! Wycheproof's AEAD schema carries `ct` and `tag` as distinct fields (the `aead_test_schema_v1` -//! schema), so these cases go through [`CcmPacket::encrypt_detached_out`] / [`CcmPacket::decrypt_detached_out`], not +//! schema), so these cases go through the mode's detached in-place one-shots, not //! the inline pair `ccm_bc-test-data.rs` uses. //! //! # Most of the parameter space cannot be dispatched to at all, by design //! -//! `CcmPacket`'s `NONCE_LEN` and `TAG_LEN` are const generics restricted to A.1's sets -- +//! The mode's `NONCE_LEN` and `TAG_LEN` are const generics restricted to A.1's sets -- //! `NONCE_LEN` in `7..=13` bytes, `TAG_LEN` in `{4, 6, 8, 10, 12, 14, 16}` bytes -- so there is no //! instantiation to dispatch a group whose `ivSize`/`tagSize` falls outside them to at all; unlike //! a runtime check, this is not something a case can "fail", because it is a compile-time property @@ -34,12 +34,11 @@ //! asserted at the end so a change in the vector file's shape is visible. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::CcmPacket; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_cipher::modes::{CCM, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::ElectronicCodeBook; -use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::hazmat::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, do_hazardous_operations, +}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; @@ -61,14 +60,14 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { key } -/// Runs one case at a fully-instantiated `(KEY_LEN, NONCE_LEN, TAG_LEN, P)`. +/// Runs one case at a fully-instantiated `(KEY_LEN, NONCE_LEN, TAG_LEN, P)`, through the mode's +/// in-place one-shots. /// -/// For a `result: "valid"` case, `msg` must encrypt to exactly `expected_ct`/`expected_tag` -/// ([`CcmPacket::encrypt_detached_out`]), and `expected_ct`/`expected_tag` must decrypt back to `msg` -/// ([`CcmPacket::decrypt_detached_out`]). For `result: "invalid"`, only the decrypt direction is checked -- -/// re-encrypting `msg` has no reason to reproduce a deliberately corrupted `ct`/`tag` -- and it -/// must fail the tag check rather than return a payload, leaving the output buffer zeroized as -/// [`CcmPacket::decrypt_detached_out`] documents. +/// For a `result: "valid"` case, `msg` must encrypt to exactly `expected_ct`/`expected_tag`, and +/// `expected_ct`/`expected_tag` must decrypt back to `msg`. For `result: "invalid"`, only the +/// decrypt direction is checked -- re-encrypting `msg` has no reason to reproduce a deliberately +/// corrupted `ct`/`tag` -- and it must fail the tag check rather than return a payload, leaving +/// the buffer zeroized as the mode's in-place decrypt documents. #[allow(clippy::too_many_arguments)] fn run_case( tc_id: u64, @@ -80,35 +79,31 @@ fn run_case + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { - let key = cipher_key::(key_bytes); + type Mode = CCM; + let perm = P::new(&cipher_key::(key_bytes)).expect("a wycheproof key"); let nonce: [u8; NONCE_LEN] = nonce_bytes.try_into().unwrap_or_else(|_| panic!("tcId {tc_id}: bad nonce length")); 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 (written, got_tag) = - CcmPacket::::encrypt_detached_out( - &key, &nonce, aad, msg, &mut ct, - ) - .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); - assert_eq!(written, msg.len(), "tcId {tc_id}: encrypt_detached writes exactly msg.len()"); + let mut ct = msg.to_vec(); + let got_tag = Mode::::encrypt_detached_in_place_nonce( + &perm, &nonce, aad, &mut ct, + ) + .unwrap_or_else(|e| panic!("tcId {tc_id}: valid case failed to encrypt: {e:?}")); assert_eq!(ct, expected_ct, "tcId {tc_id}: ciphertext mismatch"); assert_eq!(got_tag, tag, "tcId {tc_id}: tag mismatch"); } - // 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 CcmPacket::::decrypt_detached_out( - &key, &nonce, aad, expected_ct, &tag, &mut plaintext, + let mut plaintext = expected_ct.to_vec(); + match Mode::::decrypt_detached_in_place( + &perm, &nonce, aad, &mut plaintext, &tag, ) { - Ok(n) => { + Ok(()) => { assert!(valid, "tcId {tc_id}: an invalid vector decrypted and verified anyway"); - plaintext.truncate(n); assert_eq!(plaintext, msg, "tcId {tc_id}: decrypted plaintext mismatch"); } Err(SymmetricCipherError::AEADTagCheckFailed) => { diff --git a/crypto/aes/tests/cfb8_bc-test-data.rs b/crypto/aes/tests/cfb8_bc-test-data.rs index 3b2ece09f..9dedbd90f 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; 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 52621ec3e..a01c027e9 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; 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 new file mode 100644 index 000000000..df2a90df9 --- /dev/null +++ b/crypto/aes/tests/cmac_bc-test-data.rs @@ -0,0 +1,90 @@ +//! Known-answer tests against the NIST ACVP `CMAC-AES` vectors from the `bc-test-data` repo. +//! +//! Requires `bc-test-data` to be cloned alongside this repository, i.e. at `../bc-test-data` +//! relative to the root of this git project. If it is missing, the test prints a warning and +//! passes without checking anything. +//! +//! 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::{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; + +#[path = "common/cmac_helpers.rs"] +mod cmac_helpers; +use cmac_helpers::mac_key; + +const TEST_DATA_DIR: &str = "crypto/aes_tdes_vectors/CMAC"; +const REQUEST_FILE: &str = "CMAC-AES.4014544.req.json"; +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 => 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}"), + } +} + +/// 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 => 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}"), + } +} + +#[test] +fn acvp_cmac_aes() { + let Some(req) = bc_test_data_json(TEST_DATA_DIR, REQUEST_FILE) else { return }; + let Some(rsp) = bc_test_data_json(TEST_DATA_DIR, RESPONSE_FILE) else { return }; + + let mut answers: BTreeMap = BTreeMap::new(); + for group in rsp[1]["testGroups"].as_array().unwrap() { + for test in group["tests"].as_array().unwrap() { + answers.insert(test["tcId"].as_u64().unwrap(), test.clone()); + } + } + + let (mut generated, mut verified, mut rejected) = (0usize, 0usize, 0usize); + for group in req[1]["testGroups"].as_array().unwrap() { + let mac_len = (group["macLen"].as_u64().unwrap() / 8) as usize; + for test in group["tests"].as_array().unwrap() { + let tc_id = test["tcId"].as_u64().unwrap(); + let answer = &answers[&tc_id]; + let key = hex_field(test, "key", tc_id); + let msg = hex_field(test, "message", tc_id); + let full = aes_cmac(&key, &msg); + match group["direction"].as_str().unwrap() { + "gen" => { + assert_eq!( + full[..mac_len], + hex_field(answer, "mac", tc_id)[..], + "tcId {tc_id}" + ); + generated += 1; + } + "ver" => { + let passed = answer["testPassed"].as_bool().unwrap(); + let tag = hex_field(test, "mac", tc_id); + assert_eq!(full[..mac_len] == tag[..], passed, "tcId {tc_id}"); + if mac_len == 16 { + assert_eq!(aes_cmac_verify(&key, &msg, &tag), passed, "tcId {tc_id}"); + } + if passed { verified += 1 } else { rejected += 1 } + } + other => panic!("unexpected direction {other}"), + } + } + } + println!("ACVP CMAC-AES: {generated} gen, {verified} ver passed, {rejected} ver rejected"); + assert_eq!(generated + verified + rejected, 756, "expected all 756 ACVP CMAC-AES cases to run"); + assert!(verified > 0 && rejected > 0); +} diff --git a/crypto/aes/tests/cmac_key_tests.rs b/crypto/aes/tests/cmac_key_tests.rs new file mode 100644 index 000000000..4fbcaaad6 --- /dev/null +++ b/crypto/aes/tests/cmac_key_tests.rs @@ -0,0 +1,157 @@ +//! `AES*Key`: the key object holding the subkeys, and the workers it hands out producing +//! 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}; +use bouncycastle_core::traits::MAC; +use bouncycastle_core_test_framework::FixedSeedRNG; +use bouncycastle_hex as hex; + +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 +/// answer, so the retagged copy it hands the permutation is the right key. +#[test] +fn the_key_object_gives_the_csrc_answer() { + let material = KeyMaterial::<16>::from_bytes_as_type( + &hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(), + KeyType::MACKey, + ) + .unwrap(); + let msg = hex::decode("6bc1bee22e409f96e93d7e117393172aae2d8a57").unwrap(); + let key = AES128Key::::from_key_material(&material).unwrap(); + assert_eq!(key.mac(&msg), hex::decode("7d85449ea6ea19c823a7bf78837dfade").unwrap()); +} + +#[test] +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]; + assert_eq!( + AES128Key::::from_key_material(&mac_key::<16>(0x0b)).unwrap().mac(msg), + >::mac_init(&state_16).mac(msg) + ); + assert_eq!( + AES192Key::::from_key_material(&mac_key::<24>(0x0c)).unwrap().mac(msg), + >::mac_init(&state_24).mac(msg) + ); + assert_eq!( + AES256Key::::from_key_material(&mac_key::<32>(0x0d)).unwrap().mac(msg), + >::mac_init(&state_32).mac(msg) + ); + } +} + +#[test] +fn several_workers_from_one_key_are_independent_and_the_key_is_unchanged() { + let key = AES128Key::::from_key_material(&mac_key::<16>(0x0b)).unwrap(); + let before = key.mac(MSG); + + let mut a = key.create_generator(); + let mut b = key.create_generator(); + a.do_update(b"message one"); + b.do_update(b"message two"); + let mut tag_b = [0u8; 16]; + let nb = b.do_final_out(&mut tag_b).unwrap(); // finished in the other order + let mut tag_a = [0u8; 16]; + let na = a.do_final_out(&mut tag_a).unwrap(); + assert_eq!((na, nb), (16, 16)); + assert_ne!(tag_a, tag_b); + assert_eq!(tag_a[..], key.mac(b"message one")[..]); + assert_eq!(tag_b[..], key.mac(b"message two")[..]); + + let mut v = key.create_verifier(); + v.do_update(MSG); + assert!(v.do_verify_final(&before)); + assert!(key.verify(MSG, &before)); + assert!(!key.verify(b"not the message", &before)); + assert_eq!(key.mac(MSG), before); +} + +#[test] +fn a_cipher_key_is_refused_at_construction() { + let cipher_key = + KeyMaterial::<16>::from_bytes_as_type(&[0x0b; 16], KeyType::SymmetricCipherKey).unwrap(); + match AES128Key::::from_key_material(&cipher_key) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + other => panic!("expected InvalidKeyType, got {:?}", other.err()), + } + // And a MAC key is refused by a cipher mode, the other way round. + match AES128Key::::from_key_material(&mac_key::<16>(0x0b)) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + other => panic!("expected InvalidKeyType, got {:?}", other.err()), + } +} + +#[test] +fn a_key_below_its_own_strength_is_refused_at_construction() { + let mut weak = mac_key::<32>(0x0d); + weak.set_security_strength(SecurityStrength::_128bit).unwrap(); + match AES256Key::::from_key_material(&weak) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => {} + other => panic!("expected SecurityStrength, got {:?}", other.err()), + } +} + +#[test] +fn generate_draws_a_mac_key() { + let mut rng = FixedSeedRNG::<32>::new([0x24u8; 32]); + let key = AES256Key::::generate(&mut rng).unwrap(); + let tag = key.mac(MSG); + assert!(key.verify(MSG, &tag)); + assert_eq!(AES128Key::::KEY_TYPE, KeyType::MACKey); +} + +/// The sizes the docs quote. +#[test] +fn sizes() { + use core::mem::size_of; + assert_eq!( + size_of::>(), + size_of::>() + ); + assert_eq!( + [size_of::>(), size_of::>(), size_of::>()], + [208, 240, 272] + ); + assert_eq!( + [size_of::(), size_of::(), size_of::()], + [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 new file mode 100644 index 000000000..97cc2f9d6 --- /dev/null +++ b/crypto/aes/tests/cmac_tests.rs @@ -0,0 +1,159 @@ +//! `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::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; + +const STATE_LEN: usize = AES_CMAC_128::SUSPENDED_STATE_LEN; + +/// `KeyType::MACKey` material from hex, including the all-zero key some vector sets use. +fn mac_key(key_hex: &str) -> KeyMaterial { + let bytes = hex::decode(key_hex).unwrap(); + let mut key = KeyMaterial::::from_bytes_as_type(&bytes, KeyType::MACKey).unwrap(); + if key.key_type() != KeyType::MACKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::MACKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .unwrap(); + } + key +} + +// NIST CSRC "CMAC Mode for Authentication" examples (AES_CMAC.pdf, linked from SP 800-38B +// Appendix D): the same four messages under each key length. Examples 1-4 exercise, in order, the +// empty message (K2, n = 1), one complete block (K1), a partial final block (K2) and four complete +// blocks (K1). +const MSG: &str = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51\ + 30c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; +const MLENS: [usize; 4] = [0, 16, 20, 64]; +const KEY_128: &str = "2b7e151628aed2a6abf7158809cf4f3c"; +const KEY_192: &str = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"; +const KEY_256: &str = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"; +const TAGS_128: [&str; 4] = [ + "bb1d6929e95937287fa37d129b756746", + "070a16b46b4d4144f79bdd9dd04a287c", + "7d85449ea6ea19c823a7bf78837dfade", + "51f0bebf7e3b9d92fc49741779363cfe", +]; +const TAGS_192: [&str; 4] = [ + "d17ddf46adaacde531cac483de7a9367", + "9e99a7bf31e710900662f65e617c5184", + "3d75c194ed96070444a9fa7ec740ecf8", + "a1d5df0eed790f794d77589659f39a11", +]; +const TAGS_256: [&str; 4] = [ + "028962f61b7bf89efc6b551f4667d983", + "28a7023f452e8f82bd4bf28d8c37c35c", + "156727dc0878944a023c1fe03bad6d93", + "e1992190549f6ed5696a2c056c315410", +]; + +#[test] +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_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_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_key(&key_256, &msg[..*mlen], &hex::decode(tag).unwrap()); + } +} + +/// Every two-way split of the 64-byte message, plus byte-at-a-time, matches the one-shot: the +/// held-back final block must come out the same wherever the call boundaries fall. +#[test] +fn streaming_split_points() { + 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 = key.mac(&msg[..mlen]); + for split in 0..=mlen { + 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 = 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!( + 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 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 = 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!(key.mac_out(&msg[..20], &mut out).unwrap(), 8); + assert_eq!(out[..], 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 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); +} + +/// Suspending after every prefix of the message and resuming under the same key gives the +/// one-shot tag. +#[test] +fn suspend_resume() { + 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 = 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.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 new file mode 100644 index 000000000..082623548 --- /dev/null +++ b/crypto/aes/tests/cmac_wycheproof.rs @@ -0,0 +1,78 @@ +//! Known-answer tests against Wycheproof's `aes_cmac_test.json`. +//! +//! Requires the Wycheproof repository (https://github.com/C2SP/wycheproof) to be cloned alongside +//! 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 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::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{CMAC, CMAC_MIN_TAG_LEN}; +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}; + +#[path = "common/cmac_helpers.rs"] +mod cmac_helpers; +use cmac_helpers::mac_key; + +/// One case at one key length, through both routes. +fn run_case(tc_id: u64, key: &[u8], msg: &[u8], tag: &[u8], valid: bool) +where + K: FromKeyMaterial + MacGenerationKey + MacVerificationKey, + for<'k> ::Generator<'k>: MAC, +{ + let material = mac_key::(key); + 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!(key.create_generator().verify(msg, tag), valid, "tcId {tc_id}"); + + 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'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"); + } +} + +#[test] +fn wycheproof_aes_cmac() { + let Some(set) = wycheproof_json("aes_cmac_test.json") else { return }; + + let (mut valid, mut invalid, mut bad_key) = (0usize, 0usize, 0usize); + for group in set["testGroups"].as_array().unwrap() { + assert_eq!(group["tagSize"].as_u64(), Some(128)); + for test in group["tests"].as_array().unwrap() { + let tc_id = test["tcId"].as_u64().unwrap(); + let key = hex_field(test, "key", tc_id); + let msg = hex_field(test, "msg", tc_id); + let tag = hex_field(test, "tag", tc_id); + let expect_valid = match test["result"].as_str().unwrap() { + "valid" => true, + "invalid" => false, + other => panic!("tcId {tc_id}: unexpected result {other}"), + }; + + match key.len() { + 16 => run_case::<16, AES128Key>(tc_id, &key, &msg, &tag, expect_valid), + 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*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}"); + bad_key += 1; + continue; + } + } + if expect_valid { valid += 1 } else { invalid += 1 } + } + } + println!( + "Wycheproof AES-CMAC: {valid} valid, {invalid} invalid tags, {bad_key} invalid key sizes" + ); + assert_eq!(valid + invalid + bad_key, 311, "expected all 311 Wycheproof AES-CMAC cases to run"); +} diff --git a/crypto/aes/tests/common/acvp_gcm_helpers.rs b/crypto/aes/tests/common/acvp_gcm_helpers.rs index 4dc660446..76badc75a 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_core_test_framework::FixedSeedRNG; /// The nonce length these vectors use; every group in the ACVP AES-GCM/GMAC sets has `ivLen = 96`. @@ -41,46 +38,42 @@ 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 +92,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 +120,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/common/cmac_helpers.rs b/crypto/aes/tests/common/cmac_helpers.rs new file mode 100644 index 000000000..78ce49d40 --- /dev/null +++ b/crypto/aes/tests/common/cmac_helpers.rs @@ -0,0 +1,23 @@ +//! Shared plumbing for the CMAC vector suites: building `KeyType::MACKey` material from raw key +//! bytes, including the all-zero keys the sets include, which `KeyMaterial` tags `Zeroized` and +//! will not promote outside a `do_hazardous_operations` closure. + +#![allow(dead_code)] + +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; + +pub fn mac_key(bytes: &[u8]) -> KeyMaterial { + assert_eq!(bytes.len(), N, "key length should match the parameter set"); + let mut key = KeyMaterial::::from_bytes_as_type(bytes, KeyType::MACKey) + .expect("key bytes fit the buffer"); + if key.key_type() != KeyType::MACKey { + do_hazardous_operations(&mut key, |k| { + k.set_key_type(KeyType::MACKey)?; + k.set_security_strength(SecurityStrength::from_bytes(N)) + }) + .expect("promoting an all-zero test key"); + } + key +} diff --git a/crypto/aes/tests/common/gmac_helpers.rs b/crypto/aes/tests/common/gmac_helpers.rs new file mode 100644 index 000000000..f113f93c8 --- /dev/null +++ b/crypto/aes/tests/common/gmac_helpers.rs @@ -0,0 +1,57 @@ +//! The GMAC known-answer suites' second route: each case through `AES*Key>`, beside the +//! `Gcm` route `acvp_gcm_helpers.rs` runs. A valid case is generated under the vector's nonce, by +//! the pinned-RNG and the supplied-nonce constructors alike, and verified; an invalid one must +//! fail verification. + +#![allow(dead_code)] + +#[path = "cmac_helpers.rs"] +mod cmac_helpers; + +use bouncycastle_aes::{AES128Key, AES192Key, AES256Key}; +use bouncycastle_cipher::modes::{GCM_NONCE_LEN, GMAC}; +use bouncycastle_core::symmetric_key::FromKeyMaterial; +use bouncycastle_core_test_framework::FixedSeedRNG; +use cmac_helpers::mac_key; + +pub fn run_key_object_case( + key_bytes: &[u8], + nonce: [u8; GCM_NONCE_LEN], + msg: &[u8], + tag: &[u8], + valid: bool, +) { + // One body per (key length, tag length): the key type and the tag array are both in the type. + macro_rules! run { + ($key:ty, $klen:literal, $tlen:literal) => {{ + let key = <$key>::from_key_material(&mac_key::<$klen>(key_bytes)).expect("a MAC key"); + let tag: [u8; $tlen] = tag.try_into().expect("tag length matches TAG_LEN"); + assert_eq!(key.create_verifier(&nonce).verify(msg, &tag), valid, "key object verify"); + if valid { + let (gen_, got) = + key.create_generator(&mut FixedSeedRNG::::new(nonce)).unwrap(); + assert_eq!(got, nonce, "the pinned RNG should give the vector's IV"); + assert_eq!(gen_.mac(msg), tag, "key object generate"); + assert_eq!(key.create_generator_nonce(&nonce).mac(msg), tag, "supplied nonce"); + } + }}; + } + macro_rules! by_tag { + ($key:ident, $klen:literal) => { + match tag.len() { + 12 => run!($key>, $klen, 12), + 13 => run!($key>, $klen, 13), + 14 => run!($key>, $klen, 14), + 15 => run!($key>, $klen, 15), + 16 => run!($key>, $klen, 16), + other => panic!("unsupported tag length {other} bytes"), + } + }; + } + match key_bytes.len() { + 16 => by_tag!(AES128Key, 16), + 24 => by_tag!(AES192Key, 24), + 32 => by_tag!(AES256Key, 32), + other => panic!("unexpected AES key length {other}"), + } +} diff --git a/crypto/aes/tests/ctr_bc-test-data.rs b/crypto/aes/tests/ctr_bc-test-data.rs index a0cb85326..b6d6fbdd1 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; 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 2f680da89..a1deacc9c 100644 --- a/crypto/aes/tests/ctr_bc_java_tests.rs +++ b/crypto/aes/tests/ctr_bc_java_tests.rs @@ -36,32 +36,33 @@ //! 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; /// The AES-128 key used for every vector in this file: SP 800-38A Appendix F's first key. const KEY: &str = "2b7e151628aed2a6abf7158809cf4f3c"; -fn key() -> KeyMaterial<16> { +/// 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 +152,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::<15>::new(nonce), - ) - .unwrap(); + let key = key::<15>(); + let (mut enc, _) = key.create_encryptor(&mut FixedSeedRNG::<15>::new(nonce)).unwrap(); // BC Java encrypts 4096 bytes under this IV without complaint. let mut data = vec![0u8; 4096]; diff --git a/crypto/aes/tests/ctr_vector_tests.rs b/crypto/aes/tests/ctr_vector_tests.rs index d98452531..8fb04e8a3 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -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/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 7c6703485..fabaa3f76 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -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::<12>::new(iv), - &aad, - &pt, - &mut data, - ) - .expect("encrypt"); + let (_, got_iv, tag) = key + .encrypt_detached_out(&mut FixedSeedRNG::<12>::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 2a02a1523..d200f67a2 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::FixedSeedRNG; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; @@ -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_bc-test-data.rs b/crypto/aes/tests/gmac_bc-test-data.rs index a68907cc9..84555967a 100644 --- a/crypto/aes/tests/gmac_bc-test-data.rs +++ b/crypto/aes/tests/gmac_bc-test-data.rs @@ -1,5 +1,7 @@ //! Known-answer tests against the NIST ACVP `ACVP-AES-GMAC` vectors from the `bc-test-data` repo. //! +//! Every case also runs through `AES*Key` (`common/gmac_helpers.rs`). +//! //! Same joiner and shape as `gcm_bc-test-data.rs` (see its module docs for the `bc-test-data` //! requirement and what is and is not covered against `bc-test-data`), over the GMAC set //! (`ACVP-AES-GMAC.4014543`, 270 cases): `payloadLen` is 0 throughout -- SP 800-38D Sec 5.2, GMAC is @@ -8,9 +10,12 @@ #[path = "common/acvp_gcm_helpers.rs"] mod acvp_gcm_helpers; +#[path = "common/gmac_helpers.rs"] +mod gmac_helpers; use acvp_gcm_helpers::{GCM_NONCE_LEN, run_decrypt_case, run_encrypt_case}; use bouncycastle_core_test_framework::test_data_loaders::{Value, bc_test_data_json, hex_field}; +use gmac_helpers::run_key_object_case; use std::collections::BTreeMap; const TEST_DATA_DIR: &str = "crypto/aes_tdes_vectors/GCM"; @@ -66,6 +71,7 @@ fn acvp_aes_gmac_known_answer_tests() { // A GMAC "ciphertext" is always empty. run_encrypt_case(&key_bytes, iv, &aad, &[], tag_len, &[], &tag); run_decrypt_case(&key_bytes, iv, &aad, &[], &tag, Some(&[])); + run_key_object_case(&key_bytes, iv, &aad, &tag, true); encrypt_checked += 1; } "decrypt" => { @@ -77,9 +83,11 @@ fn acvp_aes_gmac_known_answer_tests() { // to report either, since GMAC's plaintext is always empty). if answer.get("testPassed").and_then(Value::as_bool) == Some(false) { run_decrypt_case(&key_bytes, iv, &aad, &[], &tag, None); + run_key_object_case(&key_bytes, iv, &aad, &tag, false); decrypt_failed_checked += 1; } else { run_decrypt_case(&key_bytes, iv, &aad, &[], &tag, Some(&[])); + run_key_object_case(&key_bytes, iv, &aad, &tag, true); } } other => panic!("unexpected direction {other}"), diff --git a/crypto/aes/tests/gmac_key_tests.rs b/crypto/aes/tests/gmac_key_tests.rs new file mode 100644 index 000000000..3a0523c8d --- /dev/null +++ b/crypto/aes/tests/gmac_key_tests.rs @@ -0,0 +1,153 @@ +//! `AES*Key`: the key object holding `H`, and the pair it hands out producing exactly what +//! `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, 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::{AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core_test_framework::FixedSeedRNG; + +const NONCE: [u8; GCM_NONCE_LEN] = [0x5A; GCM_NONCE_LEN]; +const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; + +fn material(fill: u8, key_type: KeyType) -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&[fill; N], key_type).expect("key material") +} + +fn rng() -> FixedSeedRNG { + FixedSeedRNG::new(NONCE) +} + +/// 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 { + ($key:ty, $alias:ident, $klen:literal, $fill:literal) => {{ + let mac_key = material::<$klen>($fill, KeyType::MACKey); + let key = <$key>::from_key_material(&mac_key).unwrap(); + let (gen_, nonce) = key.create_generator(&mut rng()).unwrap(); + assert_eq!(nonce, NONCE); + let tag = gen_.mac(MSG); + assert_eq!( + key.create_generator_nonce(&NONCE).mac(MSG), + tag, + "{} supplied nonce", + $klen + ); + assert!(key.create_verifier(&NONCE).verify(MSG, &tag)); + assert!(!key.create_verifier(&NONCE).verify(b"another message", &tag)); + tag + }}; + } + let tag128 = agree!(AES128Key, AES_GMAC_128, 16, 0x11); + agree!(AES192Key, AES_GMAC_192, 24, 0x22); + 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) = + 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)); +} + +/// A 96-bit-tag key gives the 128-bit tag's prefix. +#[test] +fn a_shorter_tag_is_the_prefix() { + let k = material::<16>(0x44, KeyType::MACKey); + let full = + AES128Key::::from_key_material(&k).unwrap().create_generator_nonce(&NONCE).mac(MSG); + let short = AES128Key::>::from_key_material(&k).unwrap(); + assert_eq!(short.create_generator_nonce(&NONCE).mac(MSG)[..], full[..12]); + assert!(short.create_verifier(&NONCE).verify(MSG, full[..12].try_into().unwrap())); +} + +/// Workers from one key are independent of each other and leave the key as it was. +#[test] +fn several_workers_from_one_key_are_independent_and_the_key_is_unchanged() { + let key = AES256Key::::from_key_material(&material::<32>(0x55, KeyType::MACKey)).unwrap(); + let expected = key.create_generator_nonce(&NONCE).mac(MSG); + + let mut a = key.create_generator_nonce(&NONCE); + let mut b = key.create_generator_nonce(&NONCE); + let mut v = key.create_verifier(&NONCE); + a.do_update(&MSG[..10]); + b.do_update(&MSG[..20]); + v.do_update(&MSG[..30]); + a.do_update(&MSG[10..]); + v.do_update(&MSG[30..]); + b.do_update(&MSG[20..]); + assert_eq!(b.do_final(), expected); + assert!(v.do_verify_final(&expected)); + assert_eq!(a.do_final(), expected); + assert_eq!(key.create_generator_nonce(&NONCE).mac(MSG), expected); +} + +/// Two draws from one RNG are two nonces, and so two tags. +#[test] +fn a_fresh_nonce_per_generator() { + let key = AES128Key::::from_key_material(&material::<16>(0x66, KeyType::MACKey)).unwrap(); + let mut rng = FixedSeedRNG::<64>::new(core::array::from_fn(|i| i as u8)); + 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(MSG), g2.mac(MSG)); +} + +/// A GMAC key is `MACKey` material: a cipher key is refused, and the same bytes tagged `MACKey` +/// are refused by the GCM key, so one key cannot be both. +#[test] +fn a_cipher_key_is_refused_and_a_mac_key_is_not_a_gcm_key() { + let cipher_key = material::<16>(0x77, KeyType::SymmetricCipherKey); + assert!(matches!( + AES128Key::::from_key_material(&cipher_key), + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + let mac_key = material::<16>(0x77, KeyType::MACKey); + assert!(matches!( + AES128Key::::from_key_material(&mac_key), + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + assert_eq!(AES128Key::::KEY_TYPE, KeyType::MACKey); +} + +#[test] +fn a_key_below_its_own_strength_is_refused_at_construction() { + let mut weak = material::<32>(0x88, KeyType::MACKey); + weak.set_security_strength(SecurityStrength::_128bit).unwrap(); + assert!(matches!( + AES256Key::::from_key_material(&weak), + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); +} + +#[test] +fn generate_draws_a_mac_key() { + let mut rng = FixedSeedRNG::<32>::new([0x24u8; 32]); + let key = AES256Key::::generate(&mut rng).unwrap(); + let tag = key.create_generator_nonce(&NONCE).mac(MSG); + assert!(key.create_verifier(&NONCE).verify(MSG, &tag)); +} + +/// The sizes the docs quote: a GMAC key is a GCM key, and a worker is a `Gcm`. +#[test] +fn sizes() { + use core::mem::size_of; + assert_eq!(size_of::>(), size_of::>()); + assert_eq!( + [size_of::>(), size_of::>(), size_of::>()], + [192, 224, 256] + ); + assert_eq!(size_of::>(), size_of::>()); + assert_eq!(size_of::>(), size_of::>()); + assert_eq!( + size_of::>(), + size_of::>() + ); +} diff --git a/crypto/aes/tests/gmac_wycheproof.rs b/crypto/aes/tests/gmac_wycheproof.rs index db2207882..f98a519a9 100644 --- a/crypto/aes/tests/gmac_wycheproof.rs +++ b/crypto/aes/tests/gmac_wycheproof.rs @@ -6,7 +6,8 @@ //! suites in this crate. //! //! GMAC is GCM with an empty plaintext (SP 800-38D Sec 5.2): the vector's `msg` is the AAD, and the -//! tag is the whole output. Cases run through the same helpers as `gmac_bc-test-data.rs`. A `valid` +//! tag is the whole output. Cases run through the same helpers as `gmac_bc-test-data.rs`, and +//! through `AES*Key` (`common/gmac_helpers.rs`). A `valid` //! case must produce exactly `tag`, which must then verify; an `invalid` case must fail the tag //! check. //! @@ -15,9 +16,12 @@ #[path = "common/acvp_gcm_helpers.rs"] mod acvp_gcm_helpers; +#[path = "common/gmac_helpers.rs"] +mod gmac_helpers; use acvp_gcm_helpers::{GCM_NONCE_LEN, run_decrypt_case, run_encrypt_case}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; +use gmac_helpers::run_key_object_case; #[test] fn wycheproof_aes_gmac() { @@ -48,10 +52,12 @@ fn wycheproof_aes_gmac() { "valid" => { run_encrypt_case(&key, iv, &aad, &[], tag.len(), &[], &tag); run_decrypt_case(&key, iv, &aad, &[], &tag, Some(&[])); + run_key_object_case(&key, iv, &aad, &tag, true); valid_count += 1; } "invalid" => { run_decrypt_case(&key, iv, &aad, &[], &tag, None); + run_key_object_case(&key, iv, &aad, &tag, false); invalid_count += 1; } other => panic!("tcId {tc_id}: unexpected result {other}"), diff --git a/crypto/aes/tests/key_tests.rs b/crypto/aes/tests/key_tests.rs new file mode 100644 index 000000000..52a8188de --- /dev/null +++ b/crypto/aes/tests/key_tests.rs @@ -0,0 +1,415 @@ +//! The mode-bound AES key objects: validation at construction, and `key.create_encryptor(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::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}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; +use bouncycastle_core_test_framework::FixedSeedRNG; + +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 rng() -> FixedSeedRNG<12> { + FixedSeedRNG::<12>::new([0x24u8; 12]) +} + +const AAD: &[u8] = b"header, sent in the clear"; +const MSG: &[u8] = b"a message of no particular length, long enough to span a few blocks anyway"; + +/// Streams `MSG` through a key-side GCM encryptor for a key of `N` bytes. +fn encrypt_via_key(key: &K) -> ([u8; 12], Vec, [u8; 16]) +where + K: AeadEncryptionKey, +{ + let (mut enc, nonce) = key.create_encryptor(&mut rng()).expect("nonce draw"); + 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_out(&mut [0u8; 16]).unwrap(); + (nonce, ct, tag) +} + +/// Streams a ciphertext through a key-side GCM decryptor, returning the plaintext once the tag +/// has been checked. +fn decrypt_via_key( + key: &K, + nonce: &[u8; 12], + ct: &[u8], + tag: &[u8; 16], +) -> Result, SymmetricCipherError> +where + K: AeadDecryptionKey, +{ + // Decryption setup is infallible: no Result to unwrap. + let mut dec = key.create_decryptor(nonce); + dec.do_update_aad(AAD)?; + let mut pt = vec![0u8; ct.len() + 16]; + let n = dec.do_decrypt_out(ct, &mut pt)?; + let mut last = [0u8; 16]; + let m = dec.do_decrypt_final_detached_out(tag, &mut last)?; + Ok([&pt[..n], &last[..m]].concat()) +} + +#[test] +fn a_key_of_the_wrong_type_is_refused_at_construction() { + let seed = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::Seed).expect("a seed"); + match AES256Key::::from_key_material(&seed) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) => {} + other => panic!("expected InvalidKeyType, got {:?}", other.err()), + } +} + +#[test] +fn a_key_below_its_own_strength_is_refused_at_construction() { + let mut weak = material::<32>(); + weak.set_security_strength(SecurityStrength::_128bit).expect("downgrading is allowed"); + match AES256Key::::from_key_material(&weak) { + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => {} + other => panic!("expected SecurityStrength, got {:?}", other.err()), + } +} + +#[test] +fn the_key_type_a_mode_requires_is_the_constructor_constant() { + assert_eq!( as FromKeyMaterial<16>>::KEY_TYPE, KeyType::SymmetricCipherKey); + assert_eq!(> as FromKeyMaterial<32>>::KEY_TYPE, KeyType::SymmetricCipherKey); +} + +/// 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 worker built from the bare key +/// state through the mode trait the key forwards to. +#[test] +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_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_state, nonce_key); + + let mut ct_state = vec![0u8; MSG.len()]; + let mut ct_key = vec![0u8; MSG.len()]; + enc_state.do_update_aad(AAD).unwrap(); + enc_key.do_update_aad(AAD).unwrap(); + enc_state.do_encrypt_out(MSG, &mut ct_state).unwrap(); + enc_key.do_encrypt_out(MSG, &mut ct_key).unwrap(); + let (_, _, tag_state) = enc_state.do_encrypt_final_detached().unwrap(); + let (_, _, tag_key) = enc_key.do_encrypt_final_detached().unwrap(); + + assert_eq!(ct_state, ct_key); + assert_eq!(tag_state, tag_key); +} + +#[test] +fn create_decryptor_round_trips_and_detects_tampering() { + let key = AES256Key::::from_key_material(&material::<32>()).expect("a valid key"); + let (nonce, ct, tag) = encrypt_via_key::<32, _>(&key); + + let pt = decrypt_via_key::<32, _>(&key, &nonce, &ct, &tag).expect("a valid tag"); + assert_eq!(pt, MSG); + + let mut bad_tag = tag; + bad_tag[0] ^= 1; + assert!(matches!( + decrypt_via_key::<32, _>(&key, &nonce, &ct, &bad_tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); +} + +/// The one-shots on the role traits are written over `create_encryptor` and the streaming +/// methods, so they must match the message streamed by hand byte for byte, in both layouts. +#[test] +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<'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_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, 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!(key.decrypt_with_aad(&nonce_k, AAD, &ct_key).unwrap(), MSG); + + // Detached tag. + let (nonce_k, ct_k, tag_k) = key.encrypt_detached(&mut rng(), AAD, MSG).unwrap(); + 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())]; + let m = key.decrypt_detached_out(&nonce_k, AAD, &ct_k, &tag_k, &mut pt).unwrap(); + assert_eq!(&pt[..m], MSG); + assert_eq!(key.decrypt_detached(&nonce_k, AAD, &ct_k, &tag_k).unwrap(), MSG); + + // A failed check leaves zeros, not plaintext, in the caller's buffer. + let mut bad_tag = tag_k; + bad_tag[3] ^= 0x80; + let mut pt = vec![0xEEu8; ct_k.len()]; + assert!(matches!( + key.decrypt_detached_out(&nonce_k, AAD, &ct_k, &bad_tag, &mut pt), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); + assert!(pt.iter().all(|&b| b == 0)); + + // Too small an output buffer is reported before anything is drawn or encrypted. + let mut small = [0u8; 4]; + assert!(matches!( + key.encrypt_with_aad_out(&mut rng(), AAD, MSG, &mut small), + Err(SymmetricCipherError::OutputBufferTooSmall(_)) + )); +} + +/// 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 the_key_and_the_key_state_route_interoperate_at_every_key_length() { + fn check(key: &K) + where + P: ElectronicCodeBook + ModeNames + Clone, + K: AeadEncryptionKey + AeadDecryptionKey, + { + 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 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, AES128Internal, _>( + &AES128Key::::from_key_material(&material::<16>()).unwrap(), + ); + check::<24, AES192Internal, _>( + &AES192Key::::from_key_material(&material::<24>()).unwrap(), + ); + check::<32, AES256Internal, _>( + &AES256Key::::from_key_material(&material::<32>()).unwrap(), + ); +} + +/// The tag length is in the key type: `AES128Key>` is a different key type over the same +/// material, and its tag is the truncation of the full one (SP 800-38D step 6, MSB_t). +#[test] +fn a_shorter_tag_is_a_different_key_type_over_the_same_material() { + let km = material::<16>(); + let key12 = AES128Key::>::from_key_material(&km).expect("a valid key"); + let key16 = AES128Key::::from_key_material(&km).expect("a valid key"); + + let (mut enc, nonce) = key12.create_encryptor(&mut rng()).unwrap(); + let mut ct = vec![0u8; MSG.len()]; + enc.do_encrypt_out(MSG, &mut ct).unwrap(); + let (_, _, tag): (_, _, [u8; 12]) = enc.do_encrypt_final_detached().unwrap(); + + let (mut enc16, _) = key16.create_encryptor(&mut rng()).unwrap(); + let mut ct16 = vec![0u8; MSG.len()]; + enc16.do_encrypt_out(MSG, &mut ct16).unwrap(); + let (_, _, tag16) = enc16.do_encrypt_final_detached().unwrap(); + assert_eq!(ct, ct16); + assert_eq!(tag, tag16[..12]); + + let mut dec = key12.create_decryptor(&nonce); + let mut pt = vec![0u8; ct.len() + 12]; + let n = dec.do_decrypt_out(&ct, &mut pt).unwrap(); + let mut last = [0u8; 12]; + let m = dec.do_decrypt_final_detached_out(&tag, &mut last).expect("a valid 96-bit tag"); + assert_eq!([&pt[..n], &last[..m]].concat(), MSG); +} + +#[test] +fn generate_draws_a_usable_key_at_every_length() { + let mut r = FixedSeedRNG::<32>::new([0x5Au8; 32]); + let k128 = AES128Key::::generate(&mut r).expect("128"); + let k192 = AES192Key::::generate(&mut r).expect("192"); + let k256 = AES256Key::::generate(&mut r).expect("256"); + + let (nonce, ct, tag) = encrypt_via_key::<16, _>(&k128); + assert_ne!(ct, MSG); + assert_eq!(decrypt_via_key::<16, _>(&k128, &nonce, &ct, &tag).unwrap(), MSG); + + let (nonce, ct, tag) = encrypt_via_key::<24, _>(&k192); + assert_eq!(decrypt_via_key::<24, _>(&k192, &nonce, &ct, &tag).unwrap(), MSG); + + let (nonce, ct, tag) = encrypt_via_key::<32, _>(&k256); + assert_eq!(decrypt_via_key::<32, _>(&k256, &nonce, &ct, &tag).unwrap(), MSG); + + // A generated key is a different key from the fixed material's: the fixed key's decryptor + // rejects the generated key's ciphertext. + let fixed = AES256Key::::from_key_material(&material::<32>()).unwrap(); + assert!(matches!( + decrypt_via_key::<32, _>(&fixed, &nonce, &ct, &tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); +} + +/// The generated material carries the RNG's strength, so an RNG below the key's strength is +/// refused by the same check `from_key_material` applies to supplied material. +#[test] +fn generate_refuses_an_rng_below_the_key_strength() { + let mut weak = FixedSeedRNG::<32>::new([0x5Au8; 32]); + weak.set_security_strength(SecurityStrength::_128bit); + assert!(AES128Key::::generate(&mut weak).is_ok()); + assert!(matches!( + AES256Key::::generate(&mut weak), + Err(SymmetricCipherError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); +} + +/// A key may be cloned (to hand to another thread, say); the clone is the same key. +#[test] +fn a_cloned_key_is_the_same_key() { + let key = AES128Key::::from_key_material(&material::<16>()).unwrap(); + let copy = key.clone(); + let (nonce, ct, tag) = encrypt_via_key::<16, _>(&key); + assert_eq!(decrypt_via_key::<16, _>(©, &nonce, &ct, &tag).unwrap(), MSG); + 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]; + +fn message(len: usize) -> Vec { + (0..len).map(|i| (i * 31 % 251) as u8).collect() +} + +/// The provided in-place one-shots, through GCM: the same nonce, ciphertext and tag as the +/// `_out` forms from the same RNG, at every length, and a round trip back. +#[test] +fn gcm_in_place_one_shots_match_the_out_forms_at_every_length() { + let key = AES128Key::::from_key_material(&material::<16>()).unwrap(); + for len in IN_PLACE_LENS { + let msg = message(len); + let (nonce_o, ct_o, tag_o) = key.encrypt_detached(&mut rng(), AAD, &msg).unwrap(); + + let mut buf = msg.clone(); + let (nonce, tag) = key.encrypt_detached_in_place(&mut rng(), AAD, &mut buf).unwrap(); + assert_eq!((nonce, &buf, tag), (nonce_o, &ct_o, tag_o), "len {len}"); + + key.decrypt_detached_in_place(&nonce, AAD, &mut buf, &tag).unwrap(); + assert_eq!(buf, msg, "len {len}"); + } +} + +/// A failed tag check leaves zeros in the caller's buffer, since the plaintext was written there +/// before the tag could be checked. +#[test] +fn in_place_decryption_zeroizes_the_buffer_on_a_bad_tag() { + let msg = message(200); + + let gcm = AES256Key::::from_key_material(&material::<32>()).unwrap(); + let mut buf = msg.clone(); + let (nonce, mut tag) = gcm.encrypt_detached_in_place(&mut rng(), AAD, &mut buf).unwrap(); + tag[0] ^= 1; + assert!(matches!( + gcm.decrypt_detached_in_place(&nonce, AAD, &mut buf, &tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); + assert!(buf.iter().all(|&b| b == 0)); +} + +/// Every `_nonce` form, handed the nonce the drawing form drew, produces the drawing form's +/// bytes. +#[test] +fn supplied_nonce_forms_match_the_drawing_forms() { + fn check(key: &K, msg: &[u8]) + where + K: AeadEncryptionKey<16, 12, 16, 16> + AeadDecryptionKey<16, 12, 16, 16>, + { + let (nonce, ct, tag) = key.encrypt_detached(&mut rng(), AAD, msg).unwrap(); + let inline = key.encrypt_with_aad(&mut rng(), AAD, msg).unwrap().1; + + assert_eq!(key.encrypt_detached_nonce(&nonce, AAD, msg).unwrap(), (ct.clone(), tag)); + assert_eq!(key.encrypt_with_aad_nonce(&nonce, AAD, msg).unwrap(), inline); + + let mut out = vec![0u8; msg.len() + 16]; + let (n, tag_n) = key.encrypt_detached_nonce_out(&nonce, AAD, msg, &mut out).unwrap(); + assert_eq!((n, &out[..n], tag_n), (ct.len(), &ct[..], tag)); + let n = key.encrypt_with_aad_nonce_out(&nonce, AAD, msg, &mut out).unwrap(); + assert_eq!(&out[..n], &inline[..]); + + let mut buf = msg.to_vec(); + assert_eq!(key.encrypt_detached_in_place_nonce(&nonce, AAD, &mut buf).unwrap(), tag); + assert_eq!(buf, ct); + + let mut enc = key.create_encryptor_nonce(&nonce); + enc.do_update_aad(AAD).unwrap(); + let mut streamed = vec![0u8; msg.len()]; + 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); + } + + check(&AES128Key::::from_key_material(&material::<16>()).unwrap(), &message(200)); +} diff --git a/crypto/aes/tests/mode_key_tests.rs b/crypto/aes/tests/mode_key_tests.rs new file mode 100644 index 000000000..c3dd8749e --- /dev/null +++ b/crypto/aes/tests/mode_key_tests.rs @@ -0,0 +1,182 @@ +//! 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::FixedSeedRNG; +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::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::<0>::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::<16>::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::<16>::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 f3a566e88..86b1681ef 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -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::<16>::new(iv), - ) - .unwrap(); + let key_object = AES128Key::>::from_key_material(&key).unwrap(); + let (mut enc, _) = key_object.create_encryptor(&mut FixedSeedRNG::<16>::new(iv)).unwrap(); let mut cbc = block(PLAINTEXTS[0]); enc.do_encrypt_blocks_inplace(cbc.as_chunks_mut().0).unwrap(); assert_eq!(cbc, block(CIPHERTEXTS_128[0]), "F.2.1 block #1"); diff --git a/crypto/aes/tests/sp800_38a_cfb8_tests.rs b/crypto/aes/tests/sp800_38a_cfb8_tests.rs index 194ca6141..a384fd2ef 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -135,22 +134,21 @@ 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 +160,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 +182,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 0242b8e84..26db521f7 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_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; @@ -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::<16>::new(iv), - ) - .unwrap(); + let (mut enc, got_iv) = key.create_encryptor(&mut FixedSeedRNG::<16>::new(iv)).unwrap(); assert_eq!(got_iv, iv); let mut c1 = block(PLAINTEXTS[0]); diff --git a/crypto/aes/tests/sp800_38a_ecb_tests.rs b/crypto/aes/tests/sp800_38a_ecb_tests.rs index 3f49390cc..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 89952f604..210d450bb 100644 --- a/crypto/aes/tests/sp800_38c_tests.rs +++ b/crypto/aes/tests/sp800_38c_tests.rs @@ -18,28 +18,54 @@ //! direction is checked by round-tripping each vector's own `C` back to its `P`. use bouncycastle_aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle_cipher::modes::ModeNames; -use bouncycastle_cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; -use bouncycastle_cipher::{Decrypting, Encrypting}; +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::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, ElectronicCodeBook, +}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +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::FixedSeedRNG; 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; /// 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), @@ -47,15 +73,18 @@ fn buffer_len_error(r: Result) -> Option { } } -/// Drives one Appendix C example through every entry point, in both layouts and both directions. +/// Drives one Appendix C example through every entry point: the mode's in-place one-shots, the +/// 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, - P: bouncycastle_core::hazmat::ElectronicCodeBook + ModeNames, + const AAD_LEN: usize, + const DATA_LEN: usize, >( name: &str, key_hex: &str, @@ -67,15 +96,17 @@ fn check_vector< // it opts out. sweep: bool, ) { - type Enc = - CcmPacket; - type Dec = - CcmPacket; + type Mode = CCM; - let k = key::(key_hex); + 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"); assert_eq!( @@ -84,70 +115,55 @@ fn check_vector< "{name}: the appendix's C must be Plen + Tlen octets" ); let (want_ct, want_tag) = c.split_at(plaintext.len()); + let want_tag: &[u8; TAG_LEN] = want_tag.try_into().expect("TAG_LEN bytes"); + + // --- Sec 6.1 and 6.2 in place, detached tag --- + let mut buf = plaintext.clone(); + let tag = + Mode::::encrypt_detached_in_place_nonce(&perm, &nonce, aad, &mut buf) + .expect("encryption"); + assert_eq!(buf, want_ct, "{name}: ciphertext"); + assert_eq!(&tag, want_tag, "{name}: tag"); + Mode::::decrypt_detached_in_place(&perm, &nonce, aad, &mut buf, want_tag) + .expect("decryption"); + assert_eq!(buf, plaintext, "{name}: detached round trip"); - // --- Sec 6.1, detached tag --- - let mut ct = vec![0u8; plaintext.len()]; - let (written, tag) = Enc::::encrypt_detached_out( - &k, &nonce, aad, &plaintext, &mut ct, - ) - .expect("encryption"); - assert_eq!(written, plaintext.len(), "{name}: CCM never expands the payload"); - assert_eq!(ct, want_ct, "{name}: ciphertext"); - assert_eq!(tag, want_tag, "{name}: tag"); - - // --- Sec 6.1, the appendix's own inline `ciphertext || tag` layout --- + // --- 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_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"); - - // --- Sec 6.2, both layouts --- let mut recovered = vec![0u8; plaintext.len()]; - let n = Dec::::decrypt_detached_out( - &k, - &nonce, - aad, - want_ct, - want_tag.try_into().expect("TAG_LEN bytes"), - &mut recovered, - ) - .expect("decryption"); - assert_eq!(n, plaintext.len()); - assert_eq!(recovered, plaintext, "{name}: detached round trip"); - - let mut recovered = vec![0u8; plaintext.len()]; - let n = Dec::::decrypt_out(&k, &nonce, aad, &c, &mut recovered) - .expect("decryption"); + let n = 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"); - // --- Every ciphertext chunking through the streaming API gives the same answer --- + // --- Every ciphertext chunking through the streams gives the same answer --- if sweep { - // Sec 3 says CCM is not a streaming mode, and `CcmPacket` handles that by taking the payload length - // up front; given that, the chunking must be invisible, exactly as for the other modes. + // 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 ccm = - Enc::::new(&k, &nonce, aad, plaintext.len()) - .expect("streaming init"); - let mut streamed = plaintext.clone(); - for piece in streamed.chunks_mut(chunk) { - ccm.do_encrypt_inplace(piece).expect("update"); + 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; + for piece in plaintext.chunks(chunk) { + written += enc.do_encrypt_out(piece, &mut streamed[written..]).expect("update"); } - let streamed_tag = ccm.do_encrypt_final().expect("final"); + let (_, _, streamed_tag) = enc.do_encrypt_final_detached().expect("final"); 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 ccm = - Dec::::new(&k, &nonce, aad, plaintext.len()) - .expect("streaming init"); - for piece in streamed.chunks_mut(chunk) { - ccm.do_decrypt_inplace(piece).expect("update"); + assert_eq!(&streamed_tag, want_tag, "{name}: tag, streamed in {chunk}-byte chunks"); + + 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; + for piece in streamed.chunks(chunk) { + written += dec.do_decrypt_out(piece, &mut opened[written..]).expect("update"); } - ccm.do_decrypt_final(want_tag.try_into().expect("TAG_LEN bytes")).expect("tag check"); - assert_eq!(streamed, plaintext, "{name}: plaintext, streamed in {chunk}-byte chunks"); + dec.do_decrypt_final_detached(want_tag).expect("tag check"); + assert_eq!(opened, plaintext, "{name}: plaintext, streamed in {chunk}-byte chunks"); } } } @@ -157,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, AES128Internal>( + check_vector::<7, 4, 8, 4>( "C.1", APPENDIX_C_KEY, "10111213141516", @@ -175,7 +191,7 @@ fn appendix_c1() { /// "minimum number of '0' bits, possibly none" is none. #[test] fn appendix_c2() { - check_vector::<16, 8, 6, AES128Internal>( + check_vector::<8, 6, 16, 16>( "C.2", APPENDIX_C_KEY, "1011121314151617", @@ -193,7 +209,7 @@ fn appendix_c2() { /// the payload spans two counter blocks. #[test] fn appendix_c3() { - check_vector::<16, 12, 8, AES128Internal>( + check_vector::<12, 8, 20, 24>( "C.3", APPENDIX_C_KEY, "101112131415161718191a1b", @@ -225,7 +241,7 @@ fn appendix_c4() { } assert_eq!(aad.len(), 65536, "Alen = 524288 bits"); - check_vector::<16, 13, 14, AES128Internal>( + check_vector::<13, 14, 65536, 32>( "C.4", APPENDIX_C_KEY, "101112131415161718191a1b1c", @@ -253,75 +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 must agree with the run-time-length [`CcmPacket`] byte for byte -- they are two -/// routes to the same Sec 6.1 -- and it must be driven with a caller-chosen nonce to check that, -/// which is what `do_encrypt_init_rng` and a fixed-output RNG provide. C.3's payload is 24 bytes -/// and its AAD 20, so that is the frame. +/// The fixed-frame pair's streams reproduce Appendix C.3 byte for byte, driven with the +/// 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::<12>::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::<12>::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"); @@ -476,18 +457,15 @@ fn an_empty_update_does_not_close_the_aad_phase() { let (tag, tag_len) = enc.do_encrypt_final().expect("final"); assert_eq!(tag_len, 16); - // The AAD really was absorbed: the direct API with the same AAD must agree, and the + // 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 = CcmPacket::::encrypt_out( - &k, &nonce, aad, message, &mut expected, - ) - .expect("direct"); + 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"); @@ -507,15 +485,13 @@ 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::<12>::new(nonce_seed)).expect("init"); + key.create_encryptor(&mut FixedSeedRNG::<12>::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); @@ -523,7 +499,7 @@ fn exact_lengths_are_accepted_whole_and_split() { sealed[32..].copy_from_slice(&tag); let (mut enc, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::<12>::new(nonce_seed)).expect("init"); + key.create_encryptor(&mut FixedSeedRNG::<12>::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]; @@ -533,14 +509,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]; @@ -560,12 +536,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, @@ -573,7 +547,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"); @@ -581,13 +555,10 @@ fn the_aad_capacity_and_the_payload_length_are_independent() { let mut direct = [0u8; 64]; let (_, direct_tag) = - CcmPacket::::encrypt_detached_out( - &k, &nonce, aad, &message, &mut direct, - ) - .expect("direct"); + 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"); @@ -601,19 +572,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!( @@ -626,7 +595,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 []), @@ -636,7 +605,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!( @@ -646,7 +615,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); @@ -654,93 +623,127 @@ fn the_decryptor_releases_the_payload_and_holds_back_only_the_tag() { assert_eq!(out, message); } -/// The one-shots are [`CcmPacket`]'s own under a generated nonce, so `DATA_LEN` and `AAD_LEN` do not -/// bind them: at the frame's length they agree with the direct API 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_direct_api() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - type Direct = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); +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::<12>::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 = vec![0u8; msg_len]; - let (_, direct_tag) = - Direct::::encrypt_detached_out(&k, &nonce, &aad, &msg, &mut direct) - .expect("direct"); + let mut direct = msg.clone(); + let direct_tag = + 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" @@ -748,10 +751,16 @@ fn trait_one_shots_take_any_length_and_agree_with_the_direct_api() { 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 @@ -766,9 +775,7 @@ fn trait_one_shots_take_any_length_and_agree_with_the_direct_api() { /// succeeding on the right amount, so a matcher that passed for the wrong reason would show. #[test] fn every_final_refuses_a_payload_of_the_wrong_length() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 16, 8>(); let aad = b"header"; let frame = [0x5Au8; 8]; let nonce_seed = [0x24u8; 12]; @@ -776,7 +783,7 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { // An encryptor fed `supplied` of the 8 declared bytes. let enc = |supplied: usize| { let (mut enc, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::<12>::new(nonce_seed)).expect("init"); + key.create_encryptor(&mut FixedSeedRNG::<12>::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"); @@ -829,7 +836,7 @@ fn every_final_refuses_a_payload_of_the_wrong_length() { let mut ct = [0u8; 8]; { let (mut e, _) = - Enc::do_encrypt_init_rng(&k, &mut FixedSeedRNG::<12>::new(nonce_seed)).expect("init"); + key.create_encryptor(&mut FixedSeedRNG::<12>::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"); @@ -840,7 +847,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"); @@ -912,19 +919,16 @@ 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. /// -/// All three inline entry points -- the inherent one-shot, the fixed-frame decryptor's -/// `do_decrypt_final` and its `decrypt_with_aad_out` -- must report the same malformed input with -/// the same variant, [`SymmetricCipherError::DecryptionFailed`], which is what +/// 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 /// used. #[test] fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { - type Enc = CcmPacket; - type Dec = CcmPacket; // The empty frame, so that every byte of a short `C` is a (missing) tag byte. - type StreamDec = CcmDecryptor; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 48, 0>(); let nonce = [0u8; 12]; let mut out = [0u8; 16]; let mut nothing = [0u8; 0]; @@ -933,19 +937,12 @@ fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { let short = vec![0u8; len]; assert!( matches!( - Dec::decrypt_out(&k, &nonce, &[], &short, &mut out), - Err(SymmetricCipherError::DecryptionFailed) - ), - "a {len}-byte C cannot carry a 16-byte tag (CcmPacket::decrypt_out)" - ); - assert!( - matches!( - StreamDec::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 = StreamDec::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)), @@ -953,35 +950,35 @@ fn an_inline_ciphertext_shorter_than_the_tag_is_rejected() { ); } - // Exactly TAG_LEN: an empty payload plus its tag, which must verify, on all three. + // Exactly TAG_LEN: an empty payload plus its tag, which must verify, on both. let mut inline = [0u8; 16]; - let n = Enc::encrypt_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_out(&k, &nonce, &[], &inline, &mut out).expect("decryption"), 0); - assert_eq!( - StreamDec::decrypt_with_aad_out(&k, &nonce, &[], &inline, &mut out).expect("decryption"), - 0 - ); - let mut dec = StreamDec::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 are [`CcmPacket`]'s own 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); @@ -997,17 +994,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"); @@ -1017,132 +1008,115 @@ 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 = CcmPacket; - type Dec = CcmPacket; - 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_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_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_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_out(&k, &nonce, &[], &ct, &mut too_small)), Some(24)); + assert_eq!( + 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. +/// 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 = CcmPacket; - type Dec = CcmPacket; - let wrong = - KeyMaterial::<16>::from_bytes_as_type(&[0x11; 16], KeyType::MACKey).expect("a MAC key"); - let mut out = [0u8; 16]; - assert!(matches!( - Enc::encrypt_detached_out(&wrong, &[0u8; 12], &[], &[], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Enc::new(&wrong, &[0u8; 12], &[], 0), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::decrypt_out(&wrong, &[0u8; 12], &[], &[0u8; 16], &mut out), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); - assert!(matches!( - Dec::new(&wrong, &[0u8; 12], &[], 0), - Err(SymmetricCipherError::KeyMaterialError(_)) - )); +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 -/// than a runtime one. This is what the `Dir` parameter buys over a runtime flag, and without a -/// test the guarantee could quietly regress into an inherent method on the shared impl block. +/// than a runtime one. This is what the two types buy over a runtime flag, and without a test the +/// guarantee could quietly regress into a shared impl block. /// -/// Both of these are checked as `compile_fail` doctests on [`CcmPacket`] itself; this test is the +/// Both of these are checked as `compile_fail` doctests on the pair itself; this test is the /// positive half -- that the *right* direction's methods do exist on each -- which a /// `compile_fail` cannot express. #[test] fn each_direction_has_its_own_methods() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); + let key = frame_key::<12, 16, 8, 4>(); let nonce = [0x55u8; 12]; - let mut enc = Enc::new(&k, &nonce, b"aad", 4).expect("encrypt init"); - let mut data = [1u8, 2, 3, 4]; - enc.do_encrypt_inplace(&mut data).expect("encrypt update"); - let tag = enc.do_encrypt_final().expect("encrypt final"); - - let mut dec = Dec::new(&k, &nonce, b"aad", 4).expect("decrypt init"); - dec.do_decrypt_inplace(&mut data).expect("decrypt update"); - dec.do_decrypt_final(&tag).expect("decrypt final"); - assert_eq!(data, [1u8, 2, 3, 4]); + 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 = 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"); + dec.do_decrypt_final_detached(&tag).expect("decrypt final"); + assert_eq!(back, [1u8, 2, 3, 4]); } // ---- memory ------------------------------------------------------------------------------ -/// Pins the sizes: `CcmPacket` is 264/296/328 B for AES-128/192/256, independent of -/// `NONCE_LEN`/`TAG_LEN`, and the fixed-frame pair is `CcmPacket` 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; - assert_eq!(size_of::>(), 264); - assert_eq!(size_of::>(), 296); - assert_eq!(size_of::>(), 328); - - // Independent of NONCE_LEN and TAG_LEN: the nonce lives inside the counter template and the - // tag is assembled at finalization, not held. - assert_eq!( - size_of::>(), - size_of::>() - ); - assert_eq!( - size_of::>(), - size_of::>() - ); - - // The direction marker is free, and does not change the layout. + // 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::>()); assert_eq!( - size_of::>(), - size_of::>() + enc_64, + 96 + 64 + 16, + "the `Ccm` state, the AAD buffer, its length and the phase flag" ); - - // The fixed-frame pair holds no payload: a 4 KiB frame costs exactly what a 16-byte one does. - let enc_64 = size_of::>(); - assert_eq!(enc_64, size_of::>()); - assert_eq!(enc_64, 264 + 64 + 16, "CcmPacket, the AAD buffer, its length and the phase flag"); assert_eq!( - size_of::>() - enc_64, + size_of::>() - enc_64, 4096 - 64, "the value grows by exactly the AAD capacity" ); + // 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 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); } @@ -1151,27 +1125,35 @@ fn sizes_match_the_documented_memory_table() { /// A.1's `p < 2^8q`. With `n = 13`, `q = 2`, so the limit is 65535 and 65536 must be refused. #[test] fn payload_longer_than_the_q_limit_is_refused() { - let k = key::<16>(APPENDIX_C_KEY); + 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(&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!( - CcmPacket::::new(&k, &nonce, &[], 65535) - .is_ok(), - "2^16 - 1 is the largest payload q = 2 can encode" + Mode::encrypt_detached_in_place_nonce(&perm, &nonce, &[], &mut buf[..65535]).is_ok(), + "2^16 - 1 is accepted" ); assert!( matches!( - CcmPacket::::new(&k, &nonce, &[], 65536), + Mode::encrypt_detached_in_place_nonce(&perm, &nonce, &[], &mut buf), Err(SymmetricCipherError::GenericError(_)) ), "2^16 does not fit [p]_16" ); } -// ---- progressive AAD: new_with_lengths + do_update_aad ------------------------------------ +// ---- progressive AAD through the pair's do_update_aad ------------------------------------ -/// Runs one Appendix C example through [`CcmPacket::new_with_lengths`], feeding the AAD in `chunk`-byte -/// pieces, in both directions, and checks the result against the example's `C`. -fn check_progressive_aad( +/// Runs one Appendix C example through the pair's streams, feeding the AAD in `chunk`-byte +/// pieces, in both directions, and checks the result against the example's `C`. The AAD capacity +/// is the example's AAD length, so C.4's is a 64 KiB buffer. +fn check_progressive_aad< + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +>( label: &str, nonce: &str, aad: &[u8], @@ -1179,43 +1161,31 @@ fn check_progressive_aad( c: &str, chunk: usize, ) { - 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 = - CcmPacket::::new_with_lengths( - &k, - &nonce, - aad.len(), - plaintext.len(), - ) - .unwrap(); + let mut enc = key.create_encryptor_nonce(&nonce); for piece in aad.chunks(chunk) { enc.do_update_aad(piece).unwrap(); } - let mut data = plaintext.clone(); - enc.do_encrypt_inplace(&mut data).unwrap(); - let tag = enc.do_encrypt_final().unwrap(); + let mut data = vec![0u8; DATA_LEN]; + enc.do_encrypt_out(&plaintext, &mut data).unwrap(); + let (_, _, tag) = enc.do_encrypt_final_detached().unwrap(); assert_eq!(data, want_ct, "{label}: ciphertext, AAD in {chunk}-byte pieces"); - assert_eq!(&tag[..], want_tag, "{label}: tag, AAD in {chunk}-byte pieces"); - - let mut dec = - CcmPacket::::new_with_lengths( - &k, - &nonce, - aad.len(), - plaintext.len(), - ) - .unwrap(); + assert_eq!(&tag, want_tag, "{label}: tag, AAD in {chunk}-byte pieces"); + + let mut dec = key.create_decryptor(&nonce); for piece in aad.chunks(chunk) { dec.do_update_aad(piece).unwrap(); } - dec.do_decrypt_inplace(&mut data).unwrap(); - dec.do_decrypt_final(&tag).unwrap(); - assert_eq!(data, plaintext, "{label}: decryption, AAD in {chunk}-byte pieces"); + let mut opened = vec![0u8; DATA_LEN]; + dec.do_decrypt_out(&data, &mut opened).unwrap(); + dec.do_decrypt_final_detached(&tag).unwrap(); + assert_eq!(opened, plaintext, "{label}: decryption, AAD in {chunk}-byte pieces"); } /// Supplying the AAD in pieces gives Appendix C's answers, whatever the chunking: C.3's 20-byte @@ -1224,7 +1194,7 @@ fn check_progressive_aad( #[test] fn progressive_aad_matches_appendix_c() { for chunk in [1, 3, 15, 16, 17, 20] { - check_progressive_aad::<12, 8>( + check_progressive_aad::<12, 8, 20, 24>( "C.3", "101112131415161718191a1b", &hex::decode("000102030405060708090a0b0c0d0e0f10111213").unwrap(), @@ -1238,7 +1208,7 @@ fn progressive_aad_matches_appendix_c() { aad.extend(0u8..=255u8); } for chunk in [1, 7, 256, 1000, 65536] { - check_progressive_aad::<13, 14>( + check_progressive_aad::<13, 14, 65536, 32>( "C.4", "101112131415161718191a1b1c", &aad, @@ -1250,110 +1220,37 @@ fn progressive_aad_matches_appendix_c() { } } -/// The declared AAD length is encoded in front of the AAD, so, as for the payload, any other -/// amount is refused -- more at the update, less at the final -- and the payload may not start -/// until the AAD is complete. A refused call consumes nothing. -#[test] -fn progressive_aad_enforces_the_declared_length_and_order() { - type Enc = CcmPacket; - type Dec = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); - let nonce = [0x24u8; 12]; - let aad = b"0123456789"; - let message = *b"payload"; - - let reference = { - let mut ccm = Enc::new(&k, &nonce, aad, message.len()).unwrap(); - let mut data = message; - ccm.do_encrypt_inplace(&mut data).unwrap(); - (data, ccm.do_encrypt_final().unwrap()) - }; - - // More than declared: refused, and the refused call absorbs nothing. - let mut ccm = Enc::new_with_lengths(&k, &nonce, aad.len(), message.len()).unwrap(); - ccm.do_update_aad(&aad[..4]).unwrap(); - assert!(matches!(ccm.do_update_aad(&[0u8; 7]), Err(SymmetricCipherError::StateError(_)))); - - // Payload before the AAD is complete: refused, and the data is left untouched. - let mut data = message; - assert!(matches!(ccm.do_encrypt_inplace(&mut data), Err(SymmetricCipherError::StateError(_)))); - assert_eq!(data, message, "a refused update must not touch the data"); - // An empty payload update is a no-op, not a refusal. - ccm.do_encrypt_inplace(&mut []).expect("an empty update is a no-op"); - - // Completing the AAD after both refusals gives the same answer as supplying it whole. - ccm.do_update_aad(&aad[4..]).unwrap(); - ccm.do_encrypt_inplace(&mut data).unwrap(); - assert_eq!((data, ccm.do_encrypt_final().unwrap()), reference); - - // Less than declared: refused at the final, in both directions. - let mut ccm = Enc::new_with_lengths(&k, &nonce, aad.len(), 0).unwrap(); - ccm.do_update_aad(&aad[..9]).unwrap(); - assert!(matches!(ccm.do_encrypt_final(), Err(SymmetricCipherError::StateError(_)))); - let mut dec = Dec::new_with_lengths(&k, &nonce, aad.len(), 0).unwrap(); - dec.do_update_aad(&aad[..9]).unwrap(); - assert!(matches!(dec.do_decrypt_final(&[0u8; 16]), Err(SymmetricCipherError::StateError(_)))); - - // The decryptor refuses payload before the AAD is complete too. - let mut dec = Dec::new_with_lengths(&k, &nonce, aad.len(), message.len()).unwrap(); - let mut ct = reference.0; - assert!(matches!(dec.do_decrypt_inplace(&mut ct), Err(SymmetricCipherError::StateError(_)))); - assert_eq!(ct, reference.0, "a refused update must not touch the data"); - dec.do_update_aad(aad).unwrap(); - dec.do_decrypt_inplace(&mut ct).unwrap(); - dec.do_decrypt_final(&reference.1).expect("tag check"); - assert_eq!(ct, message); - - // `new` declares exactly the AAD it is given, so any more afterwards is refused. - let mut ccm = Enc::new(&k, &nonce, aad, message.len()).unwrap(); - assert!(matches!(ccm.do_update_aad(b"x"), Err(SymmetricCipherError::StateError(_)))); - ccm.do_update_aad(&[]).expect("an empty AAD update is always a no-op"); - - // A declared AAD length of zero is the no-AAD flow: the payload may start at once. - let mut ccm = Enc::new_with_lengths(&k, &nonce, 0, message.len()).unwrap(); - let mut data = message; - ccm.do_encrypt_inplace(&mut data).unwrap(); - let no_aad = ccm.do_encrypt_final().unwrap(); - let mut ccm = Enc::new(&k, &nonce, &[], message.len()).unwrap(); - let mut data2 = message; - ccm.do_encrypt_inplace(&mut data2).unwrap(); - assert_eq!((data, no_aad), (data2, ccm.do_encrypt_final().unwrap())); -} - -/// The supplied-nonce forms are [`CcmPacket`]'s own one-shots and streaming constructor under the -/// caller's nonce, so they agree with the direct API 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. +/// 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 key: IEEE 802.11 CCMP's 13-byte nonce and 8-byte +/// tag. #[test] -fn supplied_nonce_forms_agree_with_the_direct_api_at_any_a1_nonce_length() { - type Enc = CcmEncryptor; - type Dec = CcmDecryptor; - type Direct = CcmPacket; - let k = key::<16>(APPENDIX_C_KEY); +fn supplied_nonce_forms_agree_with_the_in_place_one_shot_at_any_a1_nonce_length() { + 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 = [0u8; 48]; - let (_, direct_tag) = - Direct::::encrypt_detached_out(&k, &nonce, &aad, &frame, &mut direct) - .expect("direct"); + let mut direct = frame; + let direct_tag = + CCM::<13, 8>::encrypt_detached_in_place_nonce(&perm, &nonce, &aad, &mut direct) + .expect("in place"); 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); @@ -1363,16 +1260,13 @@ fn supplied_nonce_forms_agree_with_the_direct_api_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 644970f56..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_Packet, AES_CCM_192_Packet, - AES_CCM_256_Packet, AES_CFB_128, AES_CFB_192, AES_CFB_256, AES_CFB8_128, AES_CFB8_192, - AES_CFB8_256, AES_CTR_128, AES_CTR_192, AES_CTR_256, AES_GCM_128, AES_GCM_192, AES_GCM_256, + AES_CBC_128, AES_CBC_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, 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,66 +59,80 @@ 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::new(&key::<$k>(), &[0x24u8; 12], b"header", 32).unwrap(); - ccm.do_encrypt_inplace(&mut [0x11u8; 7]).unwrap(); - round_trip::<{ CcmEnc::SUSPENDED_STATE_LEN }, $k, _>(ccm, |mut e| { - let mut data = [0x22u8; 25]; - e.do_encrypt_inplace(&mut data).unwrap(); - [data.as_slice(), &e.do_encrypt_final().unwrap()].concat() + 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 }, _>(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(); + [out.as_slice(), &tag[..n]].concat() }); }}; } @@ -110,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_Packet + 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_Packet + 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_Packet + 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 07aad22cf..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, Hash, 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); }) }); @@ -54,7 +48,7 @@ fn bench_hash256(c: &mut Criterion) { group.bench_function(format!("{DATA_LEN} bytes -- ::hash_out()"), |b| { b.iter(|| { - AsconHash256::new().hash_out(black_box(&data), &mut digest); + AsconHash256::hash_out(black_box(&data), &mut digest); black_box(&digest); }) }); 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/ascon_cxof128.rs b/crypto/ascon/src/ascon_cxof128.rs index 2abac8952..0f1a76e51 100644 --- a/crypto/ascon/src/ascon_cxof128.rs +++ b/crypto/ascon/src/ascon_cxof128.rs @@ -146,16 +146,6 @@ impl Hash for AsconCXof128 { NOMINAL_OUTPUT_LEN } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { // A caller-visible AsconCXof128 is always in the absorbing phase: // into_squeezer() consumes it before output can begin. @@ -222,10 +212,6 @@ impl Hash for AsconCXof128 { let _ = partial_byte; Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::_128bit - } } impl XOF for AsconCXof128 { @@ -381,3 +367,19 @@ impl Suspendable for AsconCXof128Squeezer { Ok(Self { xof: AsconCXof128 { sponge } }) } } + +impl AsconCXof128 { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/ascon/src/ascon_hash256.rs b/crypto/ascon/src/ascon_hash256.rs index e8583c4f1..5717f774a 100644 --- a/crypto/ascon/src/ascon_hash256.rs +++ b/crypto/ascon/src/ascon_hash256.rs @@ -4,7 +4,7 @@ use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, HashOneShot, Suspendable}; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; @@ -65,23 +65,6 @@ impl Hash for AsconHash256 { DIGEST_BYTES } - fn hash(mut self, data: &[u8]) -> Vec { - self.sponge.absorb(data); - let mut out = [0u8; DIGEST_BYTES]; - self.squeeze_into(&mut out); - out.to_vec() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.sponge.absorb(data); - output.fill(0); - let mut out = [0u8; DIGEST_BYTES]; - self.squeeze_into(&mut out); - let n = core::cmp::min(output.len(), DIGEST_BYTES); - output[..n].copy_from_slice(&out[..n]); - n - } - fn do_update(&mut self, data: &[u8]) { self.sponge.absorb(data); } @@ -117,10 +100,6 @@ impl Hash for AsconHash256 { ) -> Result { Err(HashError::InvalidInput("Ascon-Hash256 does not support partial byte input")) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::_128bit - } } /// Length in bytes of the serialized state of [`AsconHash256`]. @@ -176,3 +155,6 @@ impl Suspendable for AsconHash256 { Ok(AsconHash256 { sponge: Sponge::from_parts(s, buf, buf_pos, false) }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for AsconHash256 {} diff --git a/crypto/ascon/src/ascon_xof128.rs b/crypto/ascon/src/ascon_xof128.rs index 806a4c199..0d27ff2b1 100644 --- a/crypto/ascon/src/ascon_xof128.rs +++ b/crypto/ascon/src/ascon_xof128.rs @@ -7,7 +7,7 @@ use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{Algorithm, Hash, HashOneShot, Suspendable, XOF, XOFSqueezer}; use bouncycastle_utils::secret::Secret; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; @@ -99,16 +99,6 @@ impl Hash for AsconXof128 { NOMINAL_OUTPUT_LEN } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { // A caller-visible AsconXof128 is always in the absorbing phase: // into_squeezer() consumes it before output can begin. @@ -175,10 +165,6 @@ impl Hash for AsconXof128 { let _ = partial_byte; Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::_128bit - } } impl XOF for AsconXof128 { @@ -331,3 +317,6 @@ impl Suspendable for AsconXof128Squeezer { Ok(Self { xof: AsconXof128 { sponge } }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for AsconXof128 {} diff --git a/crypto/ascon/src/lib.rs b/crypto/ascon/src/lib.rs index e8fbfa5c5..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. @@ -27,11 +27,11 @@ //! Hashing (one-shot and streaming): //! ``` //! use bouncycastle_ascon::AsconHash256; -//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_core::traits::{Hash, HashOneShot}; //! use bouncycastle_core::traits::XOF; //! //! // One-shot: -//! let digest = AsconHash256::new().hash(b"hello world"); +//! let digest = AsconHash256::hash(b"hello world"); //! assert_eq!(digest.len(), 32); //! //! // Streaming: @@ -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 1fbb42d1e..63e3bdf22 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,18 +504,15 @@ fn aead128_encryptor_decryptor_trait_framework() { fn aead128_tagged_and_direct_layouts_agree() { use bouncycastle_core_test_framework::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); let pinned = [0x11u8; 16]; // detached tag, streamed - let (mut direct_enc, direct_nonce) = Ascon_AEAD128::::do_encrypt_init_rng( - &km, - &mut FixedSeedRNG::<16>::new(pinned), - ) - .unwrap(); + let (mut direct_enc, direct_nonce) = + km.create_encryptor(&mut FixedSeedRNG::<16>::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(); @@ -505,11 +523,8 @@ fn aead128_tagged_and_direct_layouts_agree() { direct_inline.extend_from_slice(&direct_tag); // inline tag, streamed - let (mut tagged_enc, tagged_nonce) = Ascon_AEAD128::::do_encrypt_init_rng( - &km, - &mut FixedSeedRNG::<16>::new(pinned), - ) - .unwrap(); + let (mut tagged_enc, tagged_nonce) = + km.create_encryptor(&mut FixedSeedRNG::<16>::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(); @@ -524,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(); @@ -554,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(); @@ -565,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_core_test_framework::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; @@ -593,18 +594,14 @@ 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::<16>::new(kat_nonce), - &pt, - &mut ct, - ) - .unwrap(); + let (n, nonce) = km + .encrypt_with_aad_out(&mut FixedSeedRNG::<16>::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"); @@ -622,19 +619,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]); @@ -644,7 +642,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) )); @@ -654,7 +652,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) )); @@ -663,7 +661,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) )); @@ -671,22 +669,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"); @@ -697,7 +695,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) )); @@ -706,13 +704,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 8499386cf..797648f04 100644 --- a/crypto/ascon/tests/ascon_bc-test-data.rs +++ b/crypto/ascon/tests/ascon_bc-test-data.rs @@ -8,16 +8,17 @@ //! These full sweeps (1025–1089 cases each) complement the small embedded vector sets in the //! per-primitive test files. -use bouncycastle_ascon::Ascon_AEAD128; use bouncycastle_ascon::AsconCXof128; use bouncycastle_ascon::AsconHash256; use bouncycastle_ascon::AsconXof128; +use bouncycastle_ascon::{Ascon_AEAD128, 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, Hash, SymmetricCipherDecryptor, + AEADCipherDecryptor, AEADCipherEncryptor, HashOneShot, SymmetricCipherDecryptor, SymmetricCipherEncryptor, XOF, }; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; @@ -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()); @@ -192,7 +193,7 @@ fn ascon_hash256_kat() { let expected = decode_hex(field(case, &["MD"])); assert_eq!( - AsconHash256::new().hash(&msg), + AsconHash256::hash(&msg), expected, "Hash256 mismatch (Count {})", field(case, &["Count"]) 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/ascon/tests/hash256_tests.rs b/crypto/ascon/tests/hash256_tests.rs index e93101c76..0fc25f4ad 100644 --- a/crypto/ascon/tests/hash256_tests.rs +++ b/crypto/ascon/tests/hash256_tests.rs @@ -4,7 +4,7 @@ //! streaming-equivalence, one-shot/trait-API, metadata, and unsupported-partial-op tests. use bouncycastle_ascon::AsconHash256; -use bouncycastle_core::traits::{Hash, HashAlgParams}; +use bouncycastle_core::traits::{Hash, HashAlgParams, HashOneShot}; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_hex as hex; @@ -38,7 +38,7 @@ fn hash256_embedded_kat() { for (msg_hex, md_hex) in HASH_KAT { let msg = dh(msg_hex); let expected = dh(md_hex); - assert_eq!(AsconHash256::new().hash(&msg).as_slice(), expected.as_slice(), "msg={msg_hex}"); + assert_eq!(AsconHash256::hash(&msg).as_slice(), expected.as_slice(), "msg={msg_hex}"); // AsconHash256 has no do_final_partial_bits support, so that part of the framework // is disabled; everything else (hash/hash_out/do_update+do_final(_out), truncation, @@ -52,10 +52,10 @@ fn hash256_embedded_kat() { fn hash256_streaming_matches_one_shot() { let msg = pattern(100); let mut expected = [0u8; 32]; - assert_eq!(AsconHash256::new().hash_out(&msg, &mut expected), 32); + assert_eq!(AsconHash256::hash_out(&msg, &mut expected), 32); // One-shot APIs agree. - assert_eq!(AsconHash256::new().hash(&msg), expected); + assert_eq!(AsconHash256::hash(&msg), expected); let mut buf = [0u8; 32]; let mut h = AsconHash256::new(); h.do_update(&msg); @@ -94,7 +94,7 @@ fn hash256_metadata_accessors() { #[test] fn hash256_do_final_out_truncates_to_buffer() { let msg = pattern(50); - let expected = AsconHash256::new().hash(&msg); + let expected = AsconHash256::hash(&msg); let mut h = AsconHash256::new(); h.do_update(&msg); @@ -106,10 +106,10 @@ fn hash256_do_final_out_truncates_to_buffer() { #[test] fn hash256_hash_out_zeroizes_past_output_len() { let msg = pattern(50); - let expected = AsconHash256::new().hash(&msg); + let expected = AsconHash256::hash(&msg); let mut o = [0xEEu8; 64]; - assert_eq!(AsconHash256::new().hash_out(&msg, &mut o), 32); + assert_eq!(AsconHash256::hash_out(&msg, &mut o), 32); assert_eq!(&o[..32], &expected[..]); assert_eq!(&o[32..], &[0u8; 32]); } @@ -128,7 +128,7 @@ fn hash256_suspendable_state() { use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let data: Vec = (0..37u8).collect(); - let expected = AsconHash256::new().hash(&data); + let expected = AsconHash256::hash(&data); // Suspend mid-absorb, resume, finish, and confirm the digest matches an uninterrupted run. let mut h = AsconHash256::new(); diff --git a/crypto/ascon/tests/xof128_tests.rs b/crypto/ascon/tests/xof128_tests.rs index fd8ffc366..85d915e55 100644 --- a/crypto/ascon/tests/xof128_tests.rs +++ b/crypto/ascon/tests/xof128_tests.rs @@ -6,7 +6,7 @@ use bouncycastle_ascon::{AsconXof128, AsconXof128Squeezer}; use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{Hash, HashOneShot, Suspendable, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::xof::TestFrameworkXOF; use bouncycastle_hex as hex; @@ -111,7 +111,7 @@ fn xof128_hash_view_metadata() { assert_eq!(x.block_bitlen(), 64); assert_eq!(x.output_len(), 32); - assert_eq!(x.hash(b"").len(), 32); + assert_eq!(AsconXof128::hash(b"").len(), 32); } #[test] diff --git a/crypto/blake2/Cargo.toml b/crypto/blake2/Cargo.toml new file mode 100644 index 000000000..ad177ed97 --- /dev/null +++ b/crypto/blake2/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bouncycastle-blake2" +version.workspace = true +edition.workspace = true + +[dependencies] +bouncycastle-core.workspace = true +bouncycastle-utils.workspace = true + +[dev-dependencies] +criterion.workspace = true +bouncycastle-core-test-framework.workspace = true +bouncycastle-hex.workspace = true +bouncycastle-rng.workspace = true + +[[bench]] +name = "blake2_benches" +harness = false diff --git a/crypto/blake2/benches/blake2_benches.rs b/crypto/blake2/benches/blake2_benches.rs new file mode 100644 index 000000000..58eb20c63 --- /dev/null +++ b/crypto/blake2/benches/blake2_benches.rs @@ -0,0 +1,56 @@ +//! Criterion benches for BLAKE2b-512 and BLAKE2s-256: 16 KiB of input per iteration. +//! +//! The keyed (MAC) types share the hash engine and differ only by one extra key block, so they are +//! not benchmarked separately (QUALITY_AND_STYLE.md "Performance Benchmarks"). + +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +use bouncycastle_blake2::{BLAKE2b_512, BLAKE2s_256}; +use bouncycastle_core::traits::{Hash, RNG}; +use bouncycastle_rng as rng; + +fn bench_blake2b_512(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE2b_512::new().output_len()]; + + let mut group = c.benchmark_group("blake2b-512"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE2b_512::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +fn bench_blake2s_256(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE2s_256::new().output_len()]; + + let mut group = c.benchmark_group("blake2s-256"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE2s_256::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +criterion_group!(benches, bench_blake2b_512, bench_blake2s_256); +criterion_main!(benches); diff --git a/crypto/blake2/src/blake2b.rs b/crypto/blake2/src/blake2b.rs new file mode 100644 index 000000000..933af7ddf --- /dev/null +++ b/crypto/blake2/src/blake2b.rs @@ -0,0 +1,727 @@ +//! BLAKE2b (RFC 7693) with a 64-byte digest: the unkeyed hash [`BLAKE2b_512`] and the keyed MAC +//! key [`BLAKE2b_512_MACKey`] with its worker [`BLAKE2b_512_MAC`]. +//! +//! This file and `blake2s.rs` are deliberately structurally identical -- RFC 7693 s. 2.1 describes +//! one algorithm with two parameter columns -- so that a reviewer can diff them. Keep them in step. + +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +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::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{ + Hash, HashOneShot, MAC, MIN_MAC_OUTPUT_LEN, RNG, Suspendable, SuspendableKeyed, +}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// RFC 7693 s. 2.1: "Block bytes | bb = 128". +const BB: usize = 128; + +/// RFC 7693 s. 2.1: "Hash bytes | 1 <= nn <= 64". This crate implements only nn = 64. +const NN: usize = 64; + +/// RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 64". +const MAX_KK: usize = 64; + +/// RFC 7693 s. 2.1: "Rounds in F | r = 12". +const R: usize = 12; + +/// RFC 7693 s. 2.1: "G Rotation constants = (32, 24, 16, 63)". +const R1: u32 = 32; +const R2: u32 = 24; +const R3: u32 = 16; +const R4: u32 = 63; + +/// RFC 7693 s. 2.6: IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), w = 64; values as in Appendix C.2 +/// `blake2b_iv` ("BLAKE2b IV is the same as SHA-512 IV"). +const IV: [u64; 8] = [ + 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, +]; + +/// RFC 7693 s. 2.7: message schedule SIGMA[0..9]. "For BLAKE2b, the two extra permutations for +/// rounds 10 and 11 are SIGMA[10..11] = SIGMA[0..1]", which F realises as `SIGMA[i mod 10]`. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// RFC 7693 s. 3.1: mixing function G. +#[inline] +fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + // v[a] := (v[a] + v[b] + x) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + // v[d] := (v[d] ^ v[a]) >>> R1 + v[d] = (v[d] ^ v[a]).rotate_right(R1); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R2 + v[b] = (v[b] ^ v[c]).rotate_right(R2); + // v[a] := (v[a] + v[b] + y) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + // v[d] := (v[d] ^ v[a]) >>> R3 + v[d] = (v[d] ^ v[a]).rotate_right(R3); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R4 + v[b] = (v[b] ^ v[c]).rotate_right(R4); +} + +/// RFC 7693 s. 3.2: compression function F( h[0..7], m[0..15], t, f ), updating `h` in place. +/// +/// The message words `m` are read from `block` little-endian (s. 2.4). The transient work vector +/// `v` and words `m` are plain stack locals and are not zeroized; see the crate's Security +/// Considerations. +fn compress(h: &mut [u64; 8], block: &[u8; BB], t: u128, f: bool) { + // s. 2.4: "Byte (octet) streams are interpreted as words in little-endian order". + let mut m = [0u64; 16]; + let (words, _) = block.as_chunks::<8>(); + for (mi, bytes) in m.iter_mut().zip(words) { + *mi = u64::from_le_bytes(*bytes); + } + + // // Initialize local work vector v[0..15] + let mut v = [0u64; 16]; + // v[0..7] := h[0..7] // First half from state. + v[..8].copy_from_slice(h); + // v[8..15] := IV[0..7] // Second half from IV. + v[8..].copy_from_slice(&IV); + + // v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + v[12] ^= t as u64; + // v[13] := v[13] ^ (t >> w) // High word. + // Mutants note: the high word of t is zero unless 2**64 or more bytes have been processed + // (16 EiB), far beyond what a test can hash, so `^=` -> `|=` and `>>` -> `<<` here (both a + // no-op on a zero high word) survive as equivalent on every testable input. + v[13] ^= (t >> 64) as u64; + + // IF f = TRUE THEN // last block flag? + if f { + // v[14] := v[14] ^ 0xFF..FF // Invert all bits. + v[14] ^= u64::MAX; + } + + // // Cryptographic mixing + // FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + for i in 0..R { + // s[0..15] := SIGMA[i mod 10][0..15] + let s = &SIGMA[i % 10]; + + // v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + // v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + // v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + // v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + // v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + // v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + // v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + // v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + // FOR i = 0 TO 7 DO // XOR the two halves. + for i in 0..8 { + // h[i] := h[i] ^ v[i] ^ v[i + 8] + h[i] ^= v[i] ^ v[i + 8]; + } +} + +/// RFC 7693 s. 3.3: the initial state for key length `kk`. +/// +/// h[0..7] := IV[0..7]; h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn (the parameter block p[0] of +/// s. 2.5, "p[0] = 0x0101kknn"). +fn initial_h(kk: usize) -> Secret<[u64; 8]> { + let mut h = Secret::<[u64; 8]>::new(); + // h[0..7] := IV[0..7] // Initialization Vector. + h.copy_from_slice(&IV); + // // Parameter block p[0] + // h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn + // Mutants note: 0x01010000, kk << 8 and nn occupy disjoint bytes of p[0] (s. 2.5 + // "0x0101kknn", with kk and nn below 256), so `^` -> `|` between them is an equivalent mutant. + h[0] ^= 0x01010000 ^ ((kk as u64) << 8) ^ (NN as u64); + h +} + +/// The BLAKE2b engine shared by the hash and the MAC: RFC 7693 s. 3.3 processed incrementally, in +/// the shape of the Appendix C reference code. +/// +/// Invariant: the buffer is compressed only when it is full *and* more input arrives, so the final +/// block d[dd-1] is always still in `buf` when [`Self::finalize`] runs and can be flagged +/// `f = TRUE`. +#[derive(Clone)] +struct Blake2bCore { + /// The chaining state h[0..7]. + h: Secret<[u64; 8]>, + /// s. 2.2: "Message byte offset at the end of the current block" -- the total of the bytes in + /// every block compressed so far (key block included). s. 3.2 calls it a "2w-bit offset + /// counter", which for w = 64 is a u128. + t: u128, + /// The current, not yet compressed, block. In keyed mode this is the padded key block `d[0]` + /// exactly when `t == 0`. + buf: Secret<[u8; BB]>, + /// Number of valid bytes in `buf`, in `0..=BB`. + buf_len: usize, + /// The key length kk of the parameter block (0 for the unkeyed hash). After the first + /// compression nothing else in the state shows which parameter block produced it, so this is + /// what lets resume refuse a hash state as a MAC and the reverse. + kk: u8, +} + +impl Blake2bCore { + /// The unkeyed state (kk = 0). + fn new_unkeyed() -> Self { + Self { h: initial_h(0), t: 0, buf: Secret::new(), buf_len: 0, kk: 0 } + } + + /// The keyed state. `key` must be 1..=64 bytes; the callers validate it. + fn new_keyed(key: &[u8]) -> Self { + debug_assert!(!key.is_empty() && key.len() <= MAX_KK); + let mut core = Self { + h: initial_h(key.len()), + t: 0, + buf: Secret::new(), + buf_len: 0, + kk: key.len() as u8, + }; + core.load_key_block(key); + core + } + + /// Whether `h` equals `other`, compared in constant time. + fn h_ct_eq(&self, other: &Secret<[u64; 8]>) -> bool { + let mut a = Secret::<[u8; 64]>::new(); + let mut b = Secret::<[u8; 64]>::new(); + let (a_words, _) = a.as_chunks_mut::<8>(); + let (b_words, _) = b.as_chunks_mut::<8>(); + for ((x, y), (hx, hy)) in + a_words.iter_mut().zip(b_words).zip(self.h.iter().zip(other.iter())) + { + *x = hx.to_le_bytes(); + *y = hy.to_le_bytes(); + } + ct::ct_eq_bytes(&*a, &*b) + } + + /// s. 3.3: "If a secret key is used (kk > 0), it is padded with zero bytes and set as `d[0]`." + /// `d[0]` is left in the buffer as a full block, which is compressed as soon as any message + /// byte arrives, or as the final block if none does + /// (Appendix C.2 `blake2b_init`: `ctx->c = 128`). + fn load_key_block(&mut self, key: &[u8]) { + self.buf.fill(0); + self.buf[..key.len()].copy_from_slice(key); + self.buf_len = BB; + } + + /// Absorbs `data`, compressing every block that is known not to be the last one. + fn update(&mut self, data: &[u8]) { + let mut data = data; + let fill = BB - self.buf_len; + + // Only if `data` overflows the buffer is the buffered block known not to be final. + if data.len() > fill { + self.buf[self.buf_len..].copy_from_slice(&data[..fill]); + data = &data[fill..]; + // s. 3.3: h := F( h, d[i], (i + 1) * bb, FALSE ) -- t is the byte offset at the end + // of this block. + self.add_to_t(BB); + compress(&mut self.h, &self.buf, self.t, false); + self.buf_len = 0; + + // Whole blocks straight from the input, keeping at least one byte back so that the + // final block always ends up in the buffer: (len - 1) / bb blocks are each followed by + // more input (e.g. 128 bytes -> 0 blocks, 129 -> 1, 256 -> 1). + let num_blocks = data.len().saturating_sub(1) / BB; + let (blocks, _) = data.as_chunks::(); + for block in &blocks[..num_blocks] { + self.add_to_t(BB); + compress(&mut self.h, block, self.t, false); + } + data = &data[num_blocks * BB..]; + } + + // What is left fits: data.len() <= BB - buf_len. + self.buf[self.buf_len..self.buf_len + data.len()].copy_from_slice(data); + self.buf_len += data.len(); + } + + /// `t += n`. s. 2.1 bounds the input at "0 <= ll < 2**128" bytes, which `t` (ll, plus bb in + /// keyed mode) cannot exceed in practice; overflow is asserted in debug builds rather than + /// silently wrapping into a different hash. + fn add_to_t(&mut self, n: usize) { + debug_assert!(self.t.checked_add(n as u128).is_some(), "BLAKE2b message too long"); + self.t = self.t.wrapping_add(n as u128); + } + + /// RFC 7693 s. 3.3 final block, then "RETURN first "nn" bytes from little-endian word array + /// h[]". Zeroizes all of `output`, writes `min(output.len(), nn)` bytes and returns that count. + fn finalize(mut self, output: &mut [u8]) -> usize { + // Final block: IF kk = 0 THEN h := F( h, d[dd - 1], ll, TRUE ) + // ELSE h := F( h, d[dd - 1], ll + bb, TRUE ) + // `t` already counts every compressed block (the key block included), so adding the bytes + // of the last block gives ll, or ll + bb when keyed. The unkeyed empty message is the + // s. 3.3 special case "d[0] consists of all zeros" with t = 0. + self.add_to_t(self.buf_len); + // "The final data block d[dd-1] is also padded with zero to "bb" bytes". + self.buf[self.buf_len..].fill(0); + compress(&mut self.h, &self.buf, self.t, true); + + output.fill(0); + let n = *min(&output.len(), &NN); + // Little-endian words, truncated to n bytes. + for (chunk, word) in output[..n].chunks_mut(8).zip(self.h.iter()) { + chunk.copy_from_slice(&word.to_le_bytes()[..chunk.len()]); + } + n + } + + /// Serializes `h || t || buf || buf_len || kk` (all little-endian) into `out`, which must be + /// zeroed. `buf` is written as its first `buf_len` bytes followed by zeros, or as all zeros + /// when `zero_buf` is set. + fn write_state(&self, out: &mut [u8; CORE_STATE_LEN], zero_buf: bool) { + let (words, _) = out[..64].as_chunks_mut::<8>(); + for (bytes, word) in words.iter_mut().zip(self.h.iter()) { + *bytes = word.to_le_bytes(); + } + out[64..80].copy_from_slice(&self.t.to_le_bytes()); + // Only the valid bytes: past buf_len the buffer still holds stale bytes of earlier + // blocks -- in keyed mode, of the key block d[0] -- which must not leak into the state. + if !zero_buf { + out[80..80 + self.buf_len].copy_from_slice(&self.buf[..self.buf_len]); + } + debug_assert!(self.buf_len <= BB); + out[208] = self.buf_len as u8; + out[209] = self.kk; + } + + /// Inverse of [`Self::write_state`]. Rejects a `buf_len` larger than a block (a full block, + /// `buf_len == bb`, is valid: it is compressed only when more input arrives), a `t` that is + /// not a whole number of blocks, an empty buffer after a compression, and a kk above 64. + fn read_state(input: &[u8; CORE_STATE_LEN]) -> Result { + let buf_len = input[208] as usize; + let kk = input[209]; + if buf_len > BB || kk as usize > MAX_KK { + return Err(SuspendableError::InvalidData); + } + + let mut h = Secret::<[u64; 8]>::new(); + let (words, _) = input[..64].as_chunks::<8>(); + for (hi, bytes) in h.iter_mut().zip(words) { + *hi = u64::from_le_bytes(*bytes); + } + let mut t_bytes = [0u8; 16]; + t_bytes.copy_from_slice(&input[64..80]); + let t = u128::from_le_bytes(t_bytes); + // A compression only ever happens on a full block (s. 3.3 "(i + 1) * bb"), so at rest t is + // a multiple of bb. For the keyed type this also rules out 0 < t < bb: the key block is + // compressed first, with t = bb. + if t % (BB as u128) != 0 { + return Err(SuspendableError::InvalidData); + } + // `update` keeps at least one byte back after every compression, so a compressed state + // always has buffered input. + if t > 0 && buf_len == 0 { + return Err(SuspendableError::InvalidData); + } + let mut buf = Secret::<[u8; BB]>::new(); + buf.copy_from_slice(&input[80..208]); + + Ok(Self { h, t, buf, buf_len, kk }) + } +} + +/// Length of the serialized engine state, without the version header. +const CORE_STATE_LEN: usize = 64 + 16 + BB + 2; + +/// Length in bytes of the serialized state of [`BLAKE2b_512`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// [0 .. 64) h [u64; 8] +/// [64 .. 80) t u128 (RFC 7693 s. 2.2 byte offset) +/// [80 .. 208) buf [u8; 128] +/// [208 .. 209) buf_len u8 (always <= 128) +/// [209 .. 210) kk u8 (key length; 0 for this unkeyed hash) +pub const SUSPENDED_BLAKE2B_512_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// Length in bytes of the serialized state of [`BLAKE2b_512_MAC`]. +/// +/// Same layout as [`SUSPENDED_BLAKE2B_512_STATE_LEN`], except that `buf` is written as zeros while +/// it holds the padded key block (`t == 0`), so the key never appears in the state. The two lengths +/// are equal today; depend on the one for the type you are suspending. +pub const SUSPENDED_BLAKE2B_512_MAC_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The BLAKE2b hash with a 64-byte digest, unkeyed (RFC 7693, kk = 0, nn = 64). +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512 { + core: Blake2bCore, +} + +impl BLAKE2b_512 { + /// Creates a new BLAKE2b-512 instance, ready for use. + pub fn new() -> Self { + Self { core: Blake2bCore::new_unkeyed() } + } +} + +impl Default for BLAKE2b_512 { + fn default() -> Self { + Self::new() + } +} + +impl HashOneShot for BLAKE2b_512 {} + +impl Hash for BLAKE2b_512 { + /// RFC 7693 s. 2.1: "Block bytes | bb = 128", so 1024 bits. + fn block_bitlen(&self) -> usize { + BB * 8 + } + + fn output_len(&self) -> usize { + NN + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; NN]; + self.do_final_out(&mut output); + output + } + + fn do_final_out(self, output: &mut [u8]) -> usize { + self.core.finalize(output) + } + + fn do_final_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + let mut output = vec![0u8; NN]; + self.do_final_partial_bits_out(partial_byte, num_bits, &mut output)?; + Ok(output) + } + + /// BLAKE2 is byte-oriented (RFC 7693 s. 2.1 counts "Input bytes"), so it has no encoding for a + /// message that ends part-way through a byte. + /// + /// # Errors + /// * [`HashError::InvalidLength`] if `num_bits > 7`, as for every [`Hash`]. + /// * [`HashError::InvalidInput`] if `num_bits` is in `1..=7`. + /// + /// `num_bits == 0` means the message ended on a byte boundary and is exactly + /// [`Hash::do_final_out`]; `partial_byte` is ignored. + /// + /// This is the convention of Ascon-XOF128 / CXOF128 in this library. KMACXOF differs: it + /// returns [`HashError::InvalidLength`] for every non-zero `num_bits`. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + if num_bits > 7 { + return Err(HashError::InvalidLength("num_partial_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE2 is byte-oriented (RFC 7693 s. 2.1) and cannot hash a partial final byte", + )); + } + Ok(self.do_final_out(output)) + } +} + +impl Suspendable for BLAKE2b_512 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE2B_512_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2B_512_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + self.core.write_state(&mut out, false); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] for a keyed state (`kk != 0`), a buffered length over + /// one block, a `t` that is not a whole number of blocks, an empty buffer after a + /// compression, or a state taken before any compression whose `h` is not the unkeyed initial + /// state. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2B_512_STATE_LEN], + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2bCore::read_state(&input)?; + // A keyed state finished here would be its MAC computed without the key. + if core.kk != 0 { + return Err(SuspendableError::InvalidData); + } + // Before any compression h is exactly the unkeyed initial state. + if core.t == 0 && !core.h_ct_eq(&initial_h(0)) { + return Err(SuspendableError::InvalidData); + } + Ok(Self { core }) + } +} + +/// A BLAKE2b MAC key: RFC 7693 keyed BLAKE2b (kk = 1..=64, nn = 64), the key object for +/// [`BLAKE2b_512_MAC`]. +/// +/// This is the keyed BLAKE2b of the RFC ("BLAKE2BMAC" in OpenSSL), not HMAC-BLAKE2b. The key holds +/// the state after keyed initialisation (RFC 7693 s. 3.3), so every worker starts from it. +/// +/// # Keys +/// Material must be tagged [`KeyType::MACKey`] (or be all-zero, [`KeyType::Zeroized`]) and be 1 to +/// 64 bytes (RFC 7693 s. 2.1 "0 <= kk <= 64"). Unlike HMAC, a zero-length key is refused: +/// kk = 0 is the unkeyed hash, [`BLAKE2b_512`]. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512_MACKey { + /// The engine after keyed initialisation: h with kk bound into p[0], t = 0, and the padded + /// key block `d[0]` in the buffer. + core: Blake2bCore, +} + +impl BLAKE2b_512_MACKey { + /// Validates material in the order: type, security strength (unless `allow_weak_key`), + /// length, and builds the key. + /// + /// The strength is checked before the length so that an over-long key with no security + /// strength gets the strength error the shared test framework expects of every MAC key. + fn from_checked(key: &dyn KeyMaterialTrait, allow_weak_key: bool) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is tagged Zeroized rather than MACKey, + // and is let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + if !allow_weak_key && key.security_strength() < SecurityStrength::_256bit { + return Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength( + "BLAKE2b_512_MACKey: key has a lower security strength than BLAKE2b-512", + ))); + } + // RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 64". kk = 0 is the unkeyed hash, and there is + // no pre-hashing of long keys as in HMAC. + if key.key_len() == 0 || key.key_len() > MAX_KK { + return Err(MACError::InvalidLength("BLAKE2b key must be 1 to 64 bytes")); + } + Ok(Self { core: Blake2bCore::new_keyed(key.ref_to_bytes()) }) + } +} + +impl FromAnyKeyMaterial for BLAKE2b_512_MACKey { + /// # Errors + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`] unless the + /// material is [`KeyType::MACKey`] or [`KeyType::Zeroized`]. + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::SecurityStrength`] if it is + /// tagged below 256 bits; [`Self::from_key_material_allow_weak`] skips this check. + /// * [`MACError::InvalidLength`] unless it is 1 to 64 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, true) + } + + /// Draws a 64-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped + /// at what the length carries. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 256 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(key.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(MAX_KK)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KMACKey::generate does. + // Mutants note: at by_rng == by_len both arms give the same strength, so `<` -> `<=` + // is an equivalent mutant. + let by_len = SecurityStrength::from_bits(MAX_KK * 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 BLAKE2b_512_MACKey { + type Generator<'k> + = BLAKE2b_512_MAC + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + BLAKE2b_512_MAC { core: self.core.clone() } + } +} + +impl MacVerificationKey for BLAKE2b_512_MACKey { + type Verifier<'k> + = BLAKE2b_512_MAC + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// The keyed BLAKE2b worker handed out by a [`BLAKE2b_512_MACKey`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2b_512_MAC { + core: Blake2bCore, +} + +impl MAC for BLAKE2b_512_MAC { + fn output_len(&self) -> usize { + NN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; NN]; + self.core.finalize(&mut out); + out + } + + /// Zeroizes all of `out`, then writes the first `min(out.len(), 64)` bytes of the tag and + /// returns that count. A truncated tag is the 64-byte tag cut short, not the RFC's BLAKE2b with + /// a smaller nn (which binds nn into the parameter block, s. 2.5, and is a different function). + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "output buffer is shorter than MIN_MAC_OUTPUT_LEN (4 bytes)", + )); + } + Ok(self.core.finalize(out)) + } + + /// Compares in constant time against the full 64-byte tag: a truncated or extended `mac` is + /// not a match. + fn do_verify_final(self, mac: &[u8]) -> bool { + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } +} + +/// The key is never written into the serialized state; resume takes the [`BLAKE2b_512_MACKey`] +/// again. +/// +/// Before any message byte (`t == 0`) the buffer holds the padded key block, so `suspend` writes +/// zeros there and `from_suspended` rebuilds it from the key. Once a message byte arrives the key +/// is folded into `h`, and from then on only the key's length is checked: resuming with a +/// different key of the same length gives the original MAC. +/// +/// # 🚨 Security Considerations 🚨 +/// Although it holds no key bytes, a suspended state with `t > 0` is a MAC oracle for every +/// extension of the absorbed prefix: its chaining value `h` alone finishes the MAC, so anyone +/// holding it can compute valid tags for any message that starts with that prefix. Protect it like +/// the key. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN> for BLAKE2b_512_MAC { + type Key = BLAKE2b_512_MACKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + // t == 0 exactly when buf still holds the key block d[0]. + self.core.write_state(&mut out, self.core.t == 0); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the state's key length differs from `key`'s (which + /// includes an unkeyed hash state), the buffered length exceeds one block, `t` is not a + /// whole number of blocks, the buffer is empty after a compression, or a state taken before + /// any input (`t == 0`) does not match a fresh worker of `key`. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2B_512_MAC_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2bCore::read_state(&input)?; + + // A hash state (kk = 0) would verify a plain digest as a tag. A single-byte compare has no + // early exit to time. + if core.kk != key.core.kk { + return Err(SuspendableError::InvalidData); + } + + if core.t == 0 { + // Before any input the state is exactly the key's: h is the initial state for this + // kk and buf is the full key block. The serialized buffer is zeros, so the key's own + // state is taken as a whole. + if core.buf_len != BB || !core.h_ct_eq(&key.core.h) { + return Err(SuspendableError::InvalidData); + } + return Ok(Self { core: key.core.clone() }); + } + + Ok(Self { core }) + } +} diff --git a/crypto/blake2/src/blake2s.rs b/crypto/blake2/src/blake2s.rs new file mode 100644 index 000000000..2c36cec2d --- /dev/null +++ b/crypto/blake2/src/blake2s.rs @@ -0,0 +1,726 @@ +//! BLAKE2s (RFC 7693) with a 32-byte digest: the unkeyed hash [`BLAKE2s_256`] and the keyed MAC +//! key [`BLAKE2s_256_MACKey`] with its worker [`BLAKE2s_256_MAC`]. +//! +//! This file and `blake2b.rs` are deliberately structurally identical -- RFC 7693 s. 2.1 describes +//! one algorithm with two parameter columns -- so that a reviewer can diff them. Keep them in step. + +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +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::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{ + Hash, HashOneShot, MAC, MIN_MAC_OUTPUT_LEN, RNG, Suspendable, SuspendableKeyed, +}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// RFC 7693 s. 2.1: "Block bytes | bb = 64". +const BB: usize = 64; + +/// RFC 7693 s. 2.1: "Hash bytes | 1 <= nn <= 32". This crate implements only nn = 32. +const NN: usize = 32; + +/// RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 32". +const MAX_KK: usize = 32; + +/// RFC 7693 s. 2.1: "Rounds in F | r = 10". +const R: usize = 10; + +/// RFC 7693 s. 2.1: "G Rotation constants = (16, 12, 8, 7)". +const R1: u32 = 16; +const R2: u32 = 12; +const R3: u32 = 8; +const R4: u32 = 7; + +/// RFC 7693 s. 2.6: IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), w = 32; values as in Appendix D.2 +/// `blake2s_iv` ("BLAKE2s IV is the same as SHA-256 IV"). +const IV: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +/// RFC 7693 s. 2.7: message schedule SIGMA[0..9]. BLAKE2s has r = 10 rounds, so F's +/// `SIGMA[i mod 10]` is just `SIGMA[i]`; the `% 10` is kept to match the spec and blake2b.rs. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// RFC 7693 s. 3.1: mixing function G. +#[inline] +fn g(v: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) { + // v[a] := (v[a] + v[b] + x) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + // v[d] := (v[d] ^ v[a]) >>> R1 + v[d] = (v[d] ^ v[a]).rotate_right(R1); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R2 + v[b] = (v[b] ^ v[c]).rotate_right(R2); + // v[a] := (v[a] + v[b] + y) mod 2**w + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + // v[d] := (v[d] ^ v[a]) >>> R3 + v[d] = (v[d] ^ v[a]).rotate_right(R3); + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = v[c].wrapping_add(v[d]); + // v[b] := (v[b] ^ v[c]) >>> R4 + v[b] = (v[b] ^ v[c]).rotate_right(R4); +} + +/// RFC 7693 s. 3.2: compression function F( h[0..7], m[0..15], t, f ), updating `h` in place. +/// +/// The message words `m` are read from `block` little-endian (s. 2.4). The transient work vector +/// `v` and words `m` are plain stack locals and are not zeroized; see the crate's Security +/// Considerations. +fn compress(h: &mut [u32; 8], block: &[u8; BB], t: u64, f: bool) { + // s. 2.4: "Byte (octet) streams are interpreted as words in little-endian order". + let mut m = [0u32; 16]; + let (words, _) = block.as_chunks::<4>(); + for (mi, bytes) in m.iter_mut().zip(words) { + *mi = u32::from_le_bytes(*bytes); + } + + // // Initialize local work vector v[0..15] + let mut v = [0u32; 16]; + // v[0..7] := h[0..7] // First half from state. + v[..8].copy_from_slice(h); + // v[8..15] := IV[0..7] // Second half from IV. + v[8..].copy_from_slice(&IV); + + // v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + v[12] ^= t as u32; + // v[13] := v[13] ^ (t >> w) // High word. + // Mutants note: the high word of t is zero unless 2**32 or more bytes have been processed + // (4 GiB), far beyond what a test can hash, so `^=` -> `|=` and `>>` -> `<<` here (both a + // no-op on a zero high word) survive as equivalent on every testable input. + v[13] ^= (t >> 32) as u32; + + // IF f = TRUE THEN // last block flag? + if f { + // v[14] := v[14] ^ 0xFF..FF // Invert all bits. + v[14] ^= u32::MAX; + } + + // // Cryptographic mixing + // FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + for i in 0..R { + // s[0..15] := SIGMA[i mod 10][0..15] + let s = &SIGMA[i % 10]; + + // v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + // v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + // v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + // v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + // v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + // v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + // v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + // v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + // FOR i = 0 TO 7 DO // XOR the two halves. + for i in 0..8 { + // h[i] := h[i] ^ v[i] ^ v[i + 8] + h[i] ^= v[i] ^ v[i + 8]; + } +} + +/// RFC 7693 s. 3.3: the initial state for key length `kk`. +/// +/// h[0..7] := IV[0..7]; h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn (the parameter block p[0] of +/// s. 2.5, "p[0] = 0x0101kknn"). +fn initial_h(kk: usize) -> Secret<[u32; 8]> { + let mut h = Secret::<[u32; 8]>::new(); + // h[0..7] := IV[0..7] // Initialization Vector. + h.copy_from_slice(&IV); + // // Parameter block p[0] + // h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn + // Mutants note: 0x01010000, kk << 8 and nn occupy disjoint bytes of p[0] (s. 2.5 + // "0x0101kknn", with kk and nn below 256), so `^` -> `|` between them is an equivalent mutant. + h[0] ^= 0x01010000 ^ ((kk as u32) << 8) ^ (NN as u32); + h +} + +/// The BLAKE2s engine shared by the hash and the MAC: RFC 7693 s. 3.3 processed incrementally, in +/// the shape of the Appendix D reference code. +/// +/// Invariant: the buffer is compressed only when it is full *and* more input arrives, so the final +/// block d[dd-1] is always still in `buf` when [`Self::finalize`] runs and can be flagged +/// `f = TRUE`. +#[derive(Clone)] +struct Blake2sCore { + /// The chaining state h[0..7]. + h: Secret<[u32; 8]>, + /// s. 2.2: "Message byte offset at the end of the current block" -- the total of the bytes in + /// every block compressed so far (key block included). s. 3.2 calls it a "2w-bit offset + /// counter", which for w = 32 is a u64. + t: u64, + /// The current, not yet compressed, block. In keyed mode this is the padded key block `d[0]` + /// exactly when `t == 0`. + buf: Secret<[u8; BB]>, + /// Number of valid bytes in `buf`, in `0..=BB`. + buf_len: usize, + /// The key length kk of the parameter block (0 for the unkeyed hash). After the first + /// compression nothing else in the state shows which parameter block produced it, so this is + /// what lets resume refuse a hash state as a MAC and the reverse. + kk: u8, +} + +impl Blake2sCore { + /// The unkeyed state (kk = 0). + fn new_unkeyed() -> Self { + Self { h: initial_h(0), t: 0, buf: Secret::new(), buf_len: 0, kk: 0 } + } + + /// The keyed state. `key` must be 1..=32 bytes; the callers validate it. + fn new_keyed(key: &[u8]) -> Self { + debug_assert!(!key.is_empty() && key.len() <= MAX_KK); + let mut core = Self { + h: initial_h(key.len()), + t: 0, + buf: Secret::new(), + buf_len: 0, + kk: key.len() as u8, + }; + core.load_key_block(key); + core + } + + /// Whether `h` equals `other`, compared in constant time. + fn h_ct_eq(&self, other: &Secret<[u32; 8]>) -> bool { + let mut a = Secret::<[u8; 32]>::new(); + let mut b = Secret::<[u8; 32]>::new(); + let (a_words, _) = a.as_chunks_mut::<4>(); + let (b_words, _) = b.as_chunks_mut::<4>(); + for ((x, y), (hx, hy)) in + a_words.iter_mut().zip(b_words).zip(self.h.iter().zip(other.iter())) + { + *x = hx.to_le_bytes(); + *y = hy.to_le_bytes(); + } + ct::ct_eq_bytes(&*a, &*b) + } + + /// s. 3.3: "If a secret key is used (kk > 0), it is padded with zero bytes and set as `d[0]`." + /// `d[0]` is left in the buffer as a full block, which is compressed as soon as any message + /// byte arrives, or as the final block if none does + /// (Appendix D.2 `blake2s_init`: `ctx->c = 64`). + fn load_key_block(&mut self, key: &[u8]) { + self.buf.fill(0); + self.buf[..key.len()].copy_from_slice(key); + self.buf_len = BB; + } + + /// Absorbs `data`, compressing every block that is known not to be the last one. + fn update(&mut self, data: &[u8]) { + let mut data = data; + let fill = BB - self.buf_len; + + // Only if `data` overflows the buffer is the buffered block known not to be final. + if data.len() > fill { + self.buf[self.buf_len..].copy_from_slice(&data[..fill]); + data = &data[fill..]; + // s. 3.3: h := F( h, d[i], (i + 1) * bb, FALSE ) -- t is the byte offset at the end + // of this block. + self.add_to_t(BB); + compress(&mut self.h, &self.buf, self.t, false); + self.buf_len = 0; + + // Whole blocks straight from the input, keeping at least one byte back so that the + // final block always ends up in the buffer: (len - 1) / bb blocks are each followed by + // more input (e.g. 64 bytes -> 0 blocks, 65 -> 1, 128 -> 1). + let num_blocks = data.len().saturating_sub(1) / BB; + let (blocks, _) = data.as_chunks::(); + for block in &blocks[..num_blocks] { + self.add_to_t(BB); + compress(&mut self.h, block, self.t, false); + } + data = &data[num_blocks * BB..]; + } + + // What is left fits: data.len() <= BB - buf_len. + self.buf[self.buf_len..self.buf_len + data.len()].copy_from_slice(data); + self.buf_len += data.len(); + } + + /// `t += n`. s. 2.1 bounds the input at "0 <= ll < 2**64" bytes; `t` (ll, plus bb in keyed + /// mode) cannot reach 2**64 in practice, and overflow is asserted in debug builds rather than + /// silently wrapping into a different hash. + fn add_to_t(&mut self, n: usize) { + debug_assert!(self.t.checked_add(n as u64).is_some(), "BLAKE2s message too long"); + self.t = self.t.wrapping_add(n as u64); + } + + /// RFC 7693 s. 3.3 final block, then "RETURN first "nn" bytes from little-endian word array + /// h[]". Zeroizes all of `output`, writes `min(output.len(), nn)` bytes and returns that count. + fn finalize(mut self, output: &mut [u8]) -> usize { + // Final block: IF kk = 0 THEN h := F( h, d[dd - 1], ll, TRUE ) + // ELSE h := F( h, d[dd - 1], ll + bb, TRUE ) + // `t` already counts every compressed block (the key block included), so adding the bytes + // of the last block gives ll, or ll + bb when keyed. The unkeyed empty message is the + // s. 3.3 special case "d[0] consists of all zeros" with t = 0. + self.add_to_t(self.buf_len); + // "The final data block d[dd-1] is also padded with zero to "bb" bytes". + self.buf[self.buf_len..].fill(0); + compress(&mut self.h, &self.buf, self.t, true); + + output.fill(0); + let n = *min(&output.len(), &NN); + // Little-endian words, truncated to n bytes. + for (chunk, word) in output[..n].chunks_mut(4).zip(self.h.iter()) { + chunk.copy_from_slice(&word.to_le_bytes()[..chunk.len()]); + } + n + } + + /// Serializes `h || t || buf || buf_len || kk` (all little-endian) into `out`, which must be + /// zeroed. `buf` is written as its first `buf_len` bytes followed by zeros, or as all zeros + /// when `zero_buf` is set. + fn write_state(&self, out: &mut [u8; CORE_STATE_LEN], zero_buf: bool) { + let (words, _) = out[..32].as_chunks_mut::<4>(); + for (bytes, word) in words.iter_mut().zip(self.h.iter()) { + *bytes = word.to_le_bytes(); + } + out[32..40].copy_from_slice(&self.t.to_le_bytes()); + // Only the valid bytes: past buf_len the buffer still holds stale bytes of earlier + // blocks -- in keyed mode, of the key block d[0] -- which must not leak into the state. + if !zero_buf { + out[40..40 + self.buf_len].copy_from_slice(&self.buf[..self.buf_len]); + } + debug_assert!(self.buf_len <= BB); + out[104] = self.buf_len as u8; + out[105] = self.kk; + } + + /// Inverse of [`Self::write_state`]. Rejects a `buf_len` larger than a block (a full block, + /// `buf_len == bb`, is valid: it is compressed only when more input arrives), a `t` that is + /// not a whole number of blocks, an empty buffer after a compression, and a kk above 32. + fn read_state(input: &[u8; CORE_STATE_LEN]) -> Result { + let buf_len = input[104] as usize; + let kk = input[105]; + if buf_len > BB || kk as usize > MAX_KK { + return Err(SuspendableError::InvalidData); + } + + let mut h = Secret::<[u32; 8]>::new(); + let (words, _) = input[..32].as_chunks::<4>(); + for (hi, bytes) in h.iter_mut().zip(words) { + *hi = u32::from_le_bytes(*bytes); + } + let mut t_bytes = [0u8; 8]; + t_bytes.copy_from_slice(&input[32..40]); + let t = u64::from_le_bytes(t_bytes); + // A compression only ever happens on a full block (s. 3.3 "(i + 1) * bb"), so at rest t is + // a multiple of bb. For the keyed type this also rules out 0 < t < bb: the key block is + // compressed first, with t = bb. + if t % (BB as u64) != 0 { + return Err(SuspendableError::InvalidData); + } + // `update` keeps at least one byte back after every compression, so a compressed state + // always has buffered input. + if t > 0 && buf_len == 0 { + return Err(SuspendableError::InvalidData); + } + let mut buf = Secret::<[u8; BB]>::new(); + buf.copy_from_slice(&input[40..104]); + + Ok(Self { h, t, buf, buf_len, kk }) + } +} + +/// Length of the serialized engine state, without the version header. +const CORE_STATE_LEN: usize = 32 + 8 + BB + 2; + +/// Length in bytes of the serialized state of [`BLAKE2s_256`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// [0 .. 32) h [u32; 8] +/// [32 .. 40) t u64 (RFC 7693 s. 2.2 byte offset) +/// [40 .. 104) buf [u8; 64] +/// [104 .. 105) buf_len u8 (always <= 64) +/// [105 .. 106) kk u8 (key length; 0 for this unkeyed hash) +pub const SUSPENDED_BLAKE2S_256_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// Length in bytes of the serialized state of [`BLAKE2s_256_MAC`]. +/// +/// Same layout as [`SUSPENDED_BLAKE2S_256_STATE_LEN`], except that `buf` is written as zeros while +/// it holds the padded key block (`t == 0`), so the key never appears in the state. The two lengths +/// are equal today; depend on the one for the type you are suspending. +pub const SUSPENDED_BLAKE2S_256_MAC_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The BLAKE2s hash with a 32-byte digest, unkeyed (RFC 7693, kk = 0, nn = 32). +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256 { + core: Blake2sCore, +} + +impl BLAKE2s_256 { + /// Creates a new BLAKE2s-256 instance, ready for use. + pub fn new() -> Self { + Self { core: Blake2sCore::new_unkeyed() } + } +} + +impl Default for BLAKE2s_256 { + fn default() -> Self { + Self::new() + } +} + +impl HashOneShot for BLAKE2s_256 {} + +impl Hash for BLAKE2s_256 { + /// RFC 7693 s. 2.1: "Block bytes | bb = 64", so 512 bits. + fn block_bitlen(&self) -> usize { + BB * 8 + } + + fn output_len(&self) -> usize { + NN + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; NN]; + self.do_final_out(&mut output); + output + } + + fn do_final_out(self, output: &mut [u8]) -> usize { + self.core.finalize(output) + } + + fn do_final_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + let mut output = vec![0u8; NN]; + self.do_final_partial_bits_out(partial_byte, num_bits, &mut output)?; + Ok(output) + } + + /// BLAKE2 is byte-oriented (RFC 7693 s. 2.1 counts "Input bytes"), so it has no encoding for a + /// message that ends part-way through a byte. + /// + /// # Errors + /// * [`HashError::InvalidLength`] if `num_bits > 7`, as for every [`Hash`]. + /// * [`HashError::InvalidInput`] if `num_bits` is in `1..=7`. + /// + /// `num_bits == 0` means the message ended on a byte boundary and is exactly + /// [`Hash::do_final_out`]; `partial_byte` is ignored. + /// + /// This is the convention of Ascon-XOF128 / CXOF128 in this library. KMACXOF differs: it + /// returns [`HashError::InvalidLength`] for every non-zero `num_bits`. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + if num_bits > 7 { + return Err(HashError::InvalidLength("num_partial_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE2 is byte-oriented (RFC 7693 s. 2.1) and cannot hash a partial final byte", + )); + } + Ok(self.do_final_out(output)) + } +} + +impl Suspendable for BLAKE2s_256 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE2S_256_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2S_256_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + self.core.write_state(&mut out, false); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] for a keyed state (`kk != 0`), a buffered length over + /// one block, a `t` that is not a whole number of blocks, an empty buffer after a + /// compression, or a state taken before any compression whose `h` is not the unkeyed initial + /// state. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2S_256_STATE_LEN], + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2sCore::read_state(&input)?; + // A keyed state finished here would be its MAC computed without the key. + if core.kk != 0 { + return Err(SuspendableError::InvalidData); + } + // Before any compression h is exactly the unkeyed initial state. + if core.t == 0 && !core.h_ct_eq(&initial_h(0)) { + return Err(SuspendableError::InvalidData); + } + Ok(Self { core }) + } +} + +/// A BLAKE2s MAC key: RFC 7693 keyed BLAKE2s (kk = 1..=32, nn = 32), the key object for +/// [`BLAKE2s_256_MAC`]. +/// +/// This is the keyed BLAKE2s of the RFC ("BLAKE2SMAC" in OpenSSL), not HMAC-BLAKE2s. The key holds +/// the state after keyed initialisation (RFC 7693 s. 3.3), so every worker starts from it. +/// +/// # Keys +/// Material must be tagged [`KeyType::MACKey`] (or be all-zero, [`KeyType::Zeroized`]) and be 1 to +/// 32 bytes (RFC 7693 s. 2.1 "0 <= kk <= 32"). Unlike HMAC, a zero-length key is refused: +/// kk = 0 is the unkeyed hash, [`BLAKE2s_256`]. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256_MACKey { + /// The engine after keyed initialisation: h with kk bound into p[0], t = 0, and the padded + /// key block `d[0]` in the buffer. + core: Blake2sCore, +} + +impl BLAKE2s_256_MACKey { + /// Validates material in the order: type, security strength (unless `allow_weak_key`), + /// length, and builds the key. + /// + /// The strength is checked before the length so that an over-long key with no security + /// strength gets the strength error the shared test framework expects of every MAC key. + fn from_checked(key: &dyn KeyMaterialTrait, allow_weak_key: bool) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is tagged Zeroized rather than MACKey, + // and is let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + if !allow_weak_key && key.security_strength() < SecurityStrength::_128bit { + return Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength( + "BLAKE2s_256_MACKey: key has a lower security strength than BLAKE2s-256", + ))); + } + // RFC 7693 s. 2.1: "Key bytes | 0 <= kk <= 32". kk = 0 is the unkeyed hash, and there is + // no pre-hashing of long keys as in HMAC. + if key.key_len() == 0 || key.key_len() > MAX_KK { + return Err(MACError::InvalidLength("BLAKE2s key must be 1 to 32 bytes")); + } + Ok(Self { core: Blake2sCore::new_keyed(key.ref_to_bytes()) }) + } +} + +impl FromAnyKeyMaterial for BLAKE2s_256_MACKey { + /// # Errors + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`] unless the + /// material is [`KeyType::MACKey`] or [`KeyType::Zeroized`]. + /// * [`MACError::KeyMaterialError`] with [`KeyMaterialError::SecurityStrength`] if it is + /// tagged below 128 bits; [`Self::from_key_material_allow_weak`] skips this check. + /// * [`MACError::InvalidLength`] unless it is 1 to 32 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_checked(key, true) + } + + /// Draws a 32-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength capped + /// at what the length carries. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 128 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(key.ref_to_bytes_mut()?) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(MAX_KK)?; + key.set_key_type(KeyType::MACKey)?; + // The RNG's strength, capped at what the length carries, as KMACKey::generate does. + // Mutants note: at by_rng == by_len both arms give the same strength, so `<` -> `<=` + // is an equivalent mutant. + let by_len = SecurityStrength::from_bits(MAX_KK * 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 BLAKE2s_256_MACKey { + type Generator<'k> + = BLAKE2s_256_MAC + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + BLAKE2s_256_MAC { core: self.core.clone() } + } +} + +impl MacVerificationKey for BLAKE2s_256_MACKey { + type Verifier<'k> + = BLAKE2s_256_MAC + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// The keyed BLAKE2s worker handed out by a [`BLAKE2s_256_MACKey`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[allow(non_camel_case_types)] +#[derive(Clone)] +pub struct BLAKE2s_256_MAC { + core: Blake2sCore, +} + +impl MAC for BLAKE2s_256_MAC { + fn output_len(&self) -> usize { + NN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; NN]; + self.core.finalize(&mut out); + out + } + + /// Zeroizes all of `out`, then writes the first `min(out.len(), 32)` bytes of the tag and + /// returns that count. A truncated tag is the 32-byte tag cut short, not the RFC's BLAKE2s with + /// a smaller nn (which binds nn into the parameter block, s. 2.5, and is a different function). + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "output buffer is shorter than MIN_MAC_OUTPUT_LEN (4 bytes)", + )); + } + Ok(self.core.finalize(out)) + } + + /// Compares in constant time against the full 32-byte tag: a truncated or extended `mac` is + /// not a match. + fn do_verify_final(self, mac: &[u8]) -> bool { + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } +} + +/// The key is never written into the serialized state; resume takes the [`BLAKE2s_256_MACKey`] +/// again. +/// +/// Before any message byte (`t == 0`) the buffer holds the padded key block, so `suspend` writes +/// zeros there and `from_suspended` rebuilds it from the key. Once a message byte arrives the key +/// is folded into `h`, and from then on only the key's length is checked: resuming with a +/// different key of the same length gives the original MAC. +/// +/// # 🚨 Security Considerations 🚨 +/// Although it holds no key bytes, a suspended state with `t > 0` is a MAC oracle for every +/// extension of the absorbed prefix: its chaining value `h` alone finishes the MAC, so anyone +/// holding it can compute valid tags for any message that starts with that prefix. Protect it like +/// the key. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE2S_256_MAC_STATE_LEN> for BLAKE2s_256_MAC { + type Key = BLAKE2s_256_MACKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN]; + let mut out = [0u8; CORE_STATE_LEN]; + // t == 0 exactly when buf still holds the key block d[0]. + self.core.write_state(&mut out, self.core.t == 0); + // add_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + add_lib_ver(&mut out_to_return).copy_from_slice(&out); + out_to_return + } + + /// # Errors + /// * [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library + /// version. + /// * [`SuspendableError::InvalidData`] if the state's key length differs from `key`'s (which + /// includes an unkeyed hash state), the buffered length exceeds one block, `t` is not a + /// whole number of blocks, the buffer is empty after a compression, or a state taken before + /// any input (`t == 0`) does not match a fresh worker of `key`. + fn from_suspended( + state: [u8; SUSPENDED_BLAKE2S_256_MAC_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // At the moment, we have no not_before version to specify. + // check_lib_ver returns the CORE_STATE_LEN bytes after the 3-byte version header. + let mut input = [0u8; CORE_STATE_LEN]; + input.copy_from_slice(check_lib_ver(&state, None)?); + let core = Blake2sCore::read_state(&input)?; + + // A hash state (kk = 0) would verify a plain digest as a tag. A single-byte compare has no + // early exit to time. + if core.kk != key.core.kk { + return Err(SuspendableError::InvalidData); + } + + if core.t == 0 { + // Before any input the state is exactly the key's: h is the initial state for this + // kk and buf is the full key block. The serialized buffer is zeros, so the key's own + // state is taken as a whole. + if core.buf_len != BB || !core.h_ct_eq(&key.core.h) { + return Err(SuspendableError::InvalidData); + } + return Ok(Self { core: key.core.clone() }); + } + + Ok(Self { core }) + } +} diff --git a/crypto/blake2/src/lib.rs b/crypto/blake2/src/lib.rs new file mode 100644 index 000000000..df99a8470 --- /dev/null +++ b/crypto/blake2/src/lib.rs @@ -0,0 +1,194 @@ +//! BLAKE2b-512 and BLAKE2s-256 (RFC 7693, "The BLAKE2 Cryptographic Hash and Message +//! Authentication Code (MAC)"), unkeyed and in BLAKE2's built-in keyed mode. +//! +//! | Type | RFC 7693 parameters | Use | +//! |---------------------|-------------------------------|--------------------------------------| +//! | [`BLAKE2b_512`] | BLAKE2b, nn = 64, kk = 0 | [`Hash`] | +//! | [`BLAKE2s_256`] | BLAKE2s, nn = 32, kk = 0 | [`Hash`] | +//! | [`BLAKE2b_512_MACKey`] | BLAKE2b, nn = 64, kk = 1..=64 | MAC key; worker [`BLAKE2b_512_MAC`] | +//! | [`BLAKE2s_256_MACKey`] | BLAKE2s, nn = 32, kk = 1..=32 | MAC key; worker [`BLAKE2s_256_MAC`] | +//! +//! Other digest lengths, salt and personalisation, and the tree / parallel / XOF variants are not +//! implemented. +//! +//! # Usage Examples +//! ``` +//! use bouncycastle_core::traits::{Hash, HashOneShot}; +//! use bouncycastle_blake2::{BLAKE2b_512, BLAKE2s_256}; +//! +//! // RFC 7693 Appendix A and B. +//! assert_eq!(BLAKE2b_512::hash(b"abc")[..4], [0xBA, 0x80, 0xA5, 0x3F]); +//! +//! let mut blake2s = BLAKE2s_256::new(); +//! blake2s.do_update(b"a"); +//! blake2s.do_update(b"bc"); +//! assert_eq!(blake2s.do_final()[..4], [0x50, 0x8C, 0x5E, 0x8C]); +//! ``` +//! +//! A MAC is reached through its key object, built from [`KeyMaterial`] tagged +//! [`KeyType::MACKey`]: +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::FromAnyKeyMaterial; +//! use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_blake2::BLAKE2s_256_MACKey; +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE2s_256_MACKey::from_key_material(&material).unwrap(); +//! +//! let tag: Vec = key.mac(b"message"); +//! assert!(key.verify(b"message", &tag)); +//! +//! let mut mac = key.create_verifier(); +//! mac.do_update(b"mess"); +//! mac.do_update(b"age"); +//! assert!(mac.do_verify_final(&tag)); +//! ``` +//! +//! # Memory Usage +//! +//! No heap memory is used by the algorithm itself. Sizes from `core::mem::size_of` on x86_64: +//! +//! | Object | Size (bytes) | +//! |---------------------------------------------------------|--------------| +//! | [`BLAKE2b_512`], [`BLAKE2b_512_MAC`], [`BLAKE2b_512_MACKey`] | 224 | +//! | [`BLAKE2s_256`], [`BLAKE2s_256_MAC`], [`BLAKE2s_256_MACKey`] | 120 | +//! | BLAKE2b-512 suspended state (hash and MAC) | 213 | +//! | BLAKE2s-256 suspended state (hash and MAC) | 109 | +//! +//! The compression function also uses 256 bytes (BLAKE2b) or 128 bytes (BLAKE2s) of stack. +//! +//! # 🚨 Security Considerations 🚨 +//! +//! * BLAKE2b-512 offers 256 bits and BLAKE2s-256 128 bits of collision resistance (RFC 7693 s. 4). +//! * Use the MAC keys for keyed hashing: BLAKE2 "has a built-in keying mechanism" (RFC 7693 s. 1) +//! and its final-block flag (s. 3.2) rules out length extension. A MAC key should be uniformly +//! random. +//! * A truncated output is the full digest cut short, not BLAKE2 with a smaller `nn`, which is a +//! different function. MAC tags are not truncated below [`MIN_MAC_OUTPUT_LEN`] bytes. +//! * Verify tags with `verify` / [`MAC::do_verify_final`], which compare in constant time. +//! * BLAKE2 is byte-oriented: [`Hash::do_final_partial_bits`] refuses 1 to 7 partial bits. +//! * State and buffered input, including the key block, are zeroized on drop; the compression +//! function's stack locals are not. There are no data-dependent branches or table lookups. +//! * A suspended MAC state holds no key bytes, but once input has been absorbed it can finish the +//! MAC of any extension of that input without the key. Protect it like the key. +//! +//! # Suspending and resuming execution +//! +//! The hash types impl [`Suspendable`], and the MAC workers impl [`SuspendableKeyed`], which takes +//! the key object again to resume: +//! +//! ```rust +//! use bouncycastle_blake2::BLAKE2b_512; +//! use bouncycastle_core::traits::{Hash, Suspendable}; +//! +//! let mut blake2b = BLAKE2b_512::new(); +//! blake2b.do_update(b"The quick brown fox"); +//! let serialized_state = blake2b.suspend(); +//! +//! // ... later, possibly on another host. +//! let mut resumed = BLAKE2b_512::from_suspended(serialized_state).unwrap(); +//! resumed.do_update(b" jumps over the lazy dog"); +//! let h: Vec = resumed.do_final(); +//! ``` + +// todo #![no_std] +// waiting for the no_std refactor that removes the `-> Vec` from core::traits::Hash + +#![forbid(unsafe_code)] +#![forbid(missing_docs)] + +mod blake2b; +mod blake2s; + +pub use self::blake2b::{ + BLAKE2b_512, BLAKE2b_512_MAC, BLAKE2b_512_MACKey, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN, + SUSPENDED_BLAKE2B_512_STATE_LEN, +}; +pub use self::blake2s::{ + BLAKE2s_256, BLAKE2s_256_MAC, BLAKE2s_256_MACKey, SUSPENDED_BLAKE2S_256_MAC_STATE_LEN, + SUSPENDED_BLAKE2S_256_STATE_LEN, +}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +#[allow(unused_imports)] +use bouncycastle_core::traits::{Hash, MAC, MIN_MAC_OUTPUT_LEN, Suspendable, SuspendableKeyed}; + +/// Algorithm name string for BLAKE2b-512, as used by the factories and CLI. +pub const BLAKE2B_512_NAME: &str = "BLAKE2b-512"; +/// Algorithm name string for BLAKE2s-256, as used by the factories and CLI. +pub const BLAKE2S_256_NAME: &str = "BLAKE2s-256"; +/// Algorithm name string for keyed BLAKE2b-512, as used by the factories and CLI. +pub const BLAKE2B_512_MAC_NAME: &str = "BLAKE2b-512-MAC"; +/// Algorithm name string for keyed BLAKE2s-256, as used by the factories and CLI. +pub const BLAKE2S_256_MAC_NAME: &str = "BLAKE2s-256-MAC"; + +/// RFC 7693 s. 4: id-blake2b512 has "Collision Security 2**256". +impl Algorithm for BLAKE2b_512 { + const ALG_NAME: &'static str = BLAKE2B_512_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// RFC 7693 s. 4: id-blake2s256 has "Collision Security 2**128". +impl Algorithm for BLAKE2s_256 { + const ALG_NAME: &'static str = BLAKE2S_256_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Keyed BLAKE2b-512: the same strength as the hash. +impl Algorithm for BLAKE2b_512_MAC { + const ALG_NAME: &'static str = BLAKE2B_512_MAC_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// Keyed BLAKE2s-256: the same strength as the hash. +impl Algorithm for BLAKE2s_256_MAC { + const ALG_NAME: &'static str = BLAKE2S_256_MAC_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// RFC 7693 s. 2.1: nn = 64 output bytes, bb = 128 block bytes. +impl HashAlgParams for BLAKE2b_512 { + const OUTPUT_LEN: usize = 64; + const BLOCK_LEN: usize = 128; +} + +/// RFC 7693 s. 2.1: nn = 32 output bytes, bb = 64 block bytes. +impl HashAlgParams for BLAKE2s_256 { + const OUTPUT_LEN: usize = 32; + const BLOCK_LEN: usize = 64; +} + +/// RFC 7693 s. 4: id-blake2b512 OBJECT IDENTIFIER ::= { blake2b 16 }, under +/// hashAlgs = 1.3.6.1.4.1.1722.12.2 and blake2b = { hashAlgs 1 }. +impl AlgorithmOID for BLAKE2b_512 { + const OID: &'static [u32] = &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 1, 16]; + const OID_DER: &'static [u8] = + &[0x06, 0x0B, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x8D, 0x3A, 0x0C, 0x02, 0x01, 0x10]; +} + +/// RFC 7693 s. 4: id-blake2s256 OBJECT IDENTIFIER ::= { blake2s 8 }, under +/// hashAlgs = 1.3.6.1.4.1.1722.12.2 and blake2s = { hashAlgs 2 }. +impl AlgorithmOID for BLAKE2s_256 { + const OID: &'static [u32] = &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 2, 8]; + const OID_DER: &'static [u8] = + &[0x06, 0x0B, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x8D, 0x3A, 0x0C, 0x02, 0x02, 0x08]; +} + +/// RFC 7693 s. 4: "The same OID can be used for both keyed and unkeyed hashing since in the latter +/// case the key simply has zero length." So keyed BLAKE2b-512 shares id-blake2b512. +impl AlgorithmOID for BLAKE2b_512_MAC { + const OID: &'static [u32] = BLAKE2b_512::OID; + const OID_DER: &'static [u8] = BLAKE2b_512::OID_DER; +} + +/// RFC 7693 s. 4, as for [`BLAKE2b_512_MAC`]: keyed BLAKE2s-256 shares id-blake2s256. +impl AlgorithmOID for BLAKE2s_256_MAC { + const OID: &'static [u32] = BLAKE2s_256::OID; + const OID_DER: &'static [u8] = BLAKE2s_256::OID_DER; +} diff --git a/crypto/blake2/tests/bc_test_data.rs b/crypto/blake2/tests/bc_test_data.rs new file mode 100644 index 000000000..c6733630b --- /dev/null +++ b/crypto/blake2/tests/bc_test_data.rs @@ -0,0 +1,149 @@ +//! Test against the bc-test-data repo. +//! Requires that the bc-test-data repository is cloned and available for testing at +//! "../bc-test-data" relative to the root of this git project (or "../../../bc-test-data" relative +//! to this crate). When the repo is absent these tests print a warning and are skipped. +//! +//! `bc-test-data/crypto/Blake2KAT.txt` holds records of five lines: ``, ``, +//! ``, ``, `#`. The blake2b and blake2s records (512 of each: +//! input lengths 0..=255, unkeyed and keyed) are checked here; the blake2bp, blake2sp, blake2xb and +//! blake2xs records are variants this crate does not implement and are skipped. + +#[cfg(test)] +mod bc_test_data { + use bouncycastle_blake2::{BLAKE2b_512, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MACKey}; + use bouncycastle_core::key_material::{KeyMaterial, KeyType}; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{HashOneShot, MAC}; + use bouncycastle_hex as hex; + use std::fs; + use std::path::Path; + use std::sync::Once; + + const TEST_DATA_PATH_RELATIVE: &str = "../../../bc-test-data/crypto"; + const TEST_DATA_PATH: &str = "../bc-test-data/crypto"; + + static TEST_DATA_CHECK: Once = Once::new(); + + fn get_test_data(filename: &str) -> Result { + let found: u8 = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + 1 + } else if Path::new(TEST_DATA_PATH).exists() { + 2 + } else { + 3 + }; + + // just print once + TEST_DATA_CHECK.call_once(|| match found { + 1 => println!("bc-test-data found at: {:?}", TEST_DATA_PATH_RELATIVE), + 2 => println!("bc-test-data found at: {:?}", TEST_DATA_PATH), + _ => println!("WARNING: bc-test-data directory not found; tests will be skipped"), + }); + + let contents = if Path::new(TEST_DATA_PATH_RELATIVE).exists() { + fs::read_to_string(TEST_DATA_PATH_RELATIVE.to_string() + "/" + filename).unwrap() + } else if Path::new(TEST_DATA_PATH).exists() { + fs::read_to_string(TEST_DATA_PATH.to_string() + "/" + filename).unwrap() + } else { + return Err(()); + }; + + Ok(contents) + } + + /// One Blake2KAT.txt record. + struct Record { + alg: String, + input: Vec, + key: Vec, + digest: Vec, + } + + fn decode_hex(value: &str) -> Vec { + let clean = value.trim(); + if clean.is_empty() { Vec::new() } else { hex::decode(clean).expect("valid hex") } + } + + /// Parses the five-line records; panics on a malformed file rather than skipping records. + fn parse(contents: &str) -> Vec { + let lines: Vec<&str> = contents.lines().collect(); + assert_eq!(lines.len() % 5, 0, "Blake2KAT.txt is not a whole number of 5-line records"); + lines + .chunks(5) + .map(|r| { + assert_eq!(r[4].trim(), "#", "record for {} does not end in #", r[0]); + Record { + alg: r[0].trim().to_string(), + input: decode_hex(r[1]), + key: decode_hex(r[2]), + digest: decode_hex(r[3]), + } + }) + .collect() + } + + /// Runs one record through the hash (empty key) or the MAC (non-empty key), one-shot and + /// streamed byte-at-a-time. + fn check( + r: &Record, + ) { + if r.key.is_empty() { + assert_eq!(HASH::hash(&r.input), r.digest, "{} len {}", r.alg, r.input.len()); + let mut hash = HASH::default(); + for byte in &r.input { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), r.digest, "{} streamed len {}", r.alg, r.input.len()); + } else { + let key = KeyMaterial::<64>::from_bytes_as_type(&r.key, KeyType::MACKey).unwrap(); + assert_eq!( + KEY::from_key_material(&key).unwrap().mac(&r.input), + r.digest, + "keyed {} len {}", + r.alg, + r.input.len() + ); + let key = KEY::from_key_material(&key).unwrap(); + let mut mac = key.create_verifier(); + for byte in &r.input { + mac.do_update(core::slice::from_ref(byte)); + } + assert!( + mac.do_verify_final(&r.digest), + "keyed {} streamed len {}", + r.alg, + r.input.len() + ); + } + } + + #[test] + fn blake2_kat() { + let contents = match get_test_data("Blake2KAT.txt") { + Ok(c) => c, + Err(()) => return, + }; + let records = parse(&contents); + + let (mut b, mut s) = (0, 0); + for r in &records { + match r.alg.as_str() { + "blake2b" => { + check::(r); + b += 1; + } + "blake2s" => { + check::(r); + s += 1; + } + _ => {} + } + } + + assert_eq!(b, 512, "blake2b records"); + assert_eq!(s, 512, "blake2s records"); + println!("BLAKE2: {b} blake2b and {s} blake2s KAT records passed"); + } +} diff --git a/crypto/blake2/tests/blake2b_tests.rs b/crypto/blake2/tests/blake2b_tests.rs new file mode 100644 index 000000000..f1e7f143f --- /dev/null +++ b/crypto/blake2/tests/blake2b_tests.rs @@ -0,0 +1,763 @@ +//! Tests for BLAKE2b-512 and keyed BLAKE2b-512 (RFC 7693). +//! +//! Vector sources: RFC 7693 Appendix A; the official BLAKE2 KAT (blake2-kat.json, embedded below); +//! and the bc-test-data sweep in `bc_test_data.rs`. Wycheproof has no BLAKE2 vectors, so the +//! QUALITY_AND_STYLE.md "against wycheproof" requirement is satisfied vacuously. + +#[cfg(test)] +mod blake2b_tests { + use bouncycastle_blake2::*; + use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; + 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::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, AlgorithmOID, Hash, HashAlgParams, HashOneShot, MAC, + MIN_MAC_OUTPUT_LEN, Suspendable, SuspendableKeyed, + }; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::{ + TestFrameworkSuspendableKeyedState, TestFrameworkSuspendableState, + }; + use bouncycastle_hex as hex; + + type H = BLAKE2b_512; + type K = BLAKE2b_512_MACKey; + type M = BLAKE2b_512_MAC; + /// RFC 7693 s. 2.1 block bytes bb. + const BB: usize = 128; + /// Digest bytes nn. + const NN: usize = 64; + /// Largest key, kk. + const KK_MAX: usize = 64; + const STATE_LEN: usize = SUSPENDED_BLAKE2B_512_STATE_LEN; + const MAC_STATE_LEN: usize = SUSPENDED_BLAKE2B_512_MAC_STATE_LEN; + /// Offsets in the suspended state (after the 3-byte version header) of t, buf, buf_len and kk. + const T_OFF: usize = 3 + 64; + const BUF_OFF: usize = 3 + 80; + const BUF_LEN_OFF: usize = 3 + 208; + const KK_OFF: usize = 3 + 209; + + /// RFC 7693 Appendix A: BLAKE2b-512("abc"), copied by script from the RFC text. + const RFC_ABC: &str = "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"; + + /// (input length, unkeyed digest, keyed digest) for blake2b: generated by script from the + /// official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json. The input is + /// the bytes 0, 1, 2, ..., len - 1 and the key is the 64 bytes 0, 1, ..., 63. + const KAT: [(usize, &str, &str); 19] = [ + ( + 0, + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + ), + ( + 1, + "2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c36963e44115fe3eb2a3ac8694c28bcb4f5a0f3276f2e79487d8219057a506e4b", + "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd", + ), + ( + 2, + "1c08798dc641aba9dee435e22519a4729a09b2bfe0ff00ef2dcd8ed6f8a07d15eaf4aee52bbf18ab5608a6190f70b90486c8a7d4873710b1115d3debbb4327b5", + "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965", + ), + ( + 3, + "40a374727302d9a4769c17b5f409ff32f58aa24ff122d7603e4fda1509e919d4107a52c57570a6d94e50967aea573b11f86f473f537565c66f7039830a85d186", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + ), + ( + 7, + "8f945ba700f2530e5c2a7df7d5dce0f83f9efc78c073fe71ae1f88204a4fd1cf70a073f5d1f942ed623aa16e90a871246c90c45b621b3401a5ddbd9df6264165", + "7a8cfe9b90f75f7ecb3acc053aaed6193112b6f6a4aeeb3f65d3de541942deb9e2228152a3c4bbbe72fc3b12629528cfbb09fe630f0474339f54abf453e2ed52", + ), + ( + 8, + "e998e0dc03ec30eb99bb6bfaaf6618acc620320d7220b3af2b23d112d8e9cb1262f3c0d60d183b1ee7f096d12dae42c958418600214d04f5ed6f5e718be35566", + "380beaf6ea7cc9365e270ef0e6f3a64fb902acae51dd5512f84259ad2c91f4bc4108db73192a5bbfb0cbcf71e46c3e21aee1c5e860dc96e8eb0b7b8426e6abe9", + ), + ( + 55, + "659858f0b5c9edab5b94fd732f6e6b17c51cc096104f09beb3afc3aa467c2ecf885c4c6541effa9023d3b5738ae5a14d867e15db06fe1f9d1127b77e1aabb516", + "e1590d585a3d39f7cb599abd479070966409a6846d4377acf4471d065d5db94129cc9be92573b05ed226be1e9b7cb0cabe87918589f80dadd4ef5ef25a93d28e", + ), + ( + 56, + "26cca0126f5d1a813c62e5c71001c046f9c92095704550be5873a495a999ad010a4f79491f24f286500adce1a137bc2084e4949f5b7294cefe51ecaff8e95cba", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e76842d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + ), + ( + 57, + "4147c1f55172788c5567c561feef876f621fff1ce87786b8467637e70dfbcd0dbdb6415cb600954ab9c04c0e457e625b407222c0fe1ae21b2143688ada94dc58", + "30186055c07949948183c850e9a756cc09937e247d9d928e869e20bafc3cd9721719d34e04a0899b92c736084550186886efba2e790d8be6ebf040b209c439a4", + ), + ( + 63, + "d10bf9a15b1c9fc8d41f89bb140bf0be08d2f3666176d13baac4d381358ad074c9d4748c300520eb026daeaea7c5b158892fde4e8ec17dc998dcd507df26eb63", + "bd965bf31e87d70327536f2a341cebc4768eca275fa05ef98f7f1b71a0351298de006fba73fe6733ed01d75801b4a928e54231b38e38c562b2e33ea1284992fa", + ), + ( + 64, + "2fc6e69fa26a89a5ed269092cb9b2a449a4409a7a44011eecad13d7c4b0456602d402fa5844f1a7a758136ce3d5d8d0e8b86921ffff4f692dd95bdc8e5ff0052", + "65676d800617972fbd87e4b9514e1c67402b7a331096d3bfac22f1abb95374abc942f16e9ab0ead33b87c91968a6e509e119ff07787b3ef483e1dcdccf6e3022", + ), + ( + 65, + "fcbe8be7dcb49a32dbdf239459e26308b84dff1ea480df8d104eeff34b46fae98627b450c2267d48c0946a697c5b59531452ac0484f1c84e3a33d0c339bb2e28", + "939fa189699c5d2c81ddd1ffc1fa207c970b6a3685bb29ce1d3e99d42f2f7442da53e95a72907314f4588399a3ff5b0a92beb3f6be2694f9f86ecf2952d5b41c", + ), + ( + 127, + "b6292669ccd38d5f01caae96ba272c76a879a45743afa0725d83b9ebb26665b731f1848c52f11972b6644f554c064fa90780dbbbf3a89d4fc31f67df3e5857ef", + "76d2d819c92bce55fa8e092ab1bf9b9eab237a25267986cacf2b8ee14d214d730dc9a5aa2d7b596e86a1fd8fa0804c77402d2fcd45083688b218b1cdfa0dcbcb", + ), + ( + 128, + "2319e3789c47e2daa5fe807f61bec2a1a6537fa03f19ff32e87eecbfd64b7e0e8ccff439ac333b040f19b0c4ddd11a61e24ac1fe0f10a039806c5dcc0da3d115", + "72065ee4dd91c2d8509fa1fc28a37c7fc9fa7d5b3f8ad3d0d7a25626b57b1b44788d4caf806290425f9890a3a2a35a905ab4b37acfd0da6e4517b2525c9651e4", + ), + ( + 129, + "f59711d44a031d5f97a9413c065d1e614c417ede998590325f49bad2fd444d3e4418be19aec4e11449ac1a57207898bc57d76a1bcf3566292c20c683a5c4648f", + "64475dfe7600d7171bea0b394e27c9b00d8e74dd1e416a79473682ad3dfdbb706631558055cfc8a40e07bd015a4540dcdea15883cbbf31412df1de1cd4152b91", + ), + ( + 191, + "b9d7ca81cc60bb9578e44024e5a0a0be80f27336a6a9f4e53df3999cb191280b090e2ac2d29c5baad9d71415bdc129e69aa2667af6a7fd5e189fccdcee817340", + "e3c0739190ed849c9c962fd9dbb55e207e624fcac1eb417691515499eea8d8267b7e8f1287a63633af5011fde8c4ddf55bfdf722edf88831414f2cfaed59cb9a", + ), + ( + 192, + "a755e113386572c75ced61d719706070b9146048e42a9f8cd35667a088b42f08808abdf77e618abd959afc757379ca2c00bcc1a48390fa2bff618b1e0078a613", + "8d6cf87c08380d2d1506eee46fd4222d21d8c04e585fbfd08269c98f702833a156326a0724656400ee09351d57b440175e2a5de93cc5f80db6daf83576cf75fa", + ), + ( + 193, + "a73c7debed326f1c0db0795ee7d6e3946894b826b1f8101c56c823ba17168312e7f53fc7dbe52c3e11e69852c40485e2ef182477862ea6a34ec136e2dfeea6f4", + "da24bede383666d563eeed37f6319baf20d5c75d1635a6ba5ef4cfa1ac95487e96f8c08af600aab87c986ebad49fc70a58b4890b9c876e091016daf49e1d322e", + ), + ( + 255, + "5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928", + "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461", + ), + ]; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /// The KAT message of length `len`: bytes 0, 1, ..., len - 1. + fn msg(len: usize) -> &'static [u8] { + &DUMMY_SEED[..len] + } + + /// The KAT key material, bytes 0..KK_MAX, as a MAC key. + fn kat_material() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KK_MAX], KeyType::MACKey).unwrap() + } + + /// The KAT key object. + fn kat_key() -> K { + K::from_key_material(&kat_material()).unwrap() + } + + /// A MAC key of `len` bytes 0xA5.., tagged with `strength`. + fn key_with(len: usize, strength: SecurityStrength) -> KeyMaterial<256> { + let mut key = + KeyMaterial::<256>::from_bytes_as_type(&[0xA5u8; 256][..len], KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut key, |k| k.set_security_strength(strength)).unwrap(); + key + } + + fn kat(len: usize) -> (Vec, Vec) { + let (_, unkeyed, keyed) = KAT.iter().find(|(n, _, _)| *n == len).unwrap(); + (h(unkeyed), h(keyed)) + } + + #[test] + fn rfc7693_appendix_a() { + assert_eq!(H::hash(b"abc"), h(RFC_ABC)); + } + + /// Every embedded KAT entry, one-shot and streamed byte-at-a-time, hash and MAC. + #[test] + fn official_kat() { + for (len, unkeyed, keyed) in KAT { + let (unkeyed, keyed) = (h(unkeyed), h(keyed)); + let m = msg(len); + + assert_eq!(H::hash(m), unkeyed, "hash, len {len}"); + let mut hash = H::new(); + for byte in m { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), unkeyed, "byte-at-a-time hash, len {len}"); + + assert_eq!(kat_key().create_generator().mac(m), keyed, "mac, len {len}"); + let mut mac = kat_key().create_generator(); + for byte in m { + mac.do_update(core::slice::from_ref(byte)); + } + assert_eq!(mac.do_final(), keyed, "byte-at-a-time mac, len {len}"); + } + } + + /// Splitting the input at every point, and feeding it in every chunk size up to two blocks, + /// gives the one-shot answer. This walks every branch of the buffering in do_update. + #[test] + fn streaming_splits() { + let (unkeyed, keyed) = kat(255); + let m = msg(255); + for split in 0..=m.len() { + let mut hash = H::new(); + hash.do_update(&m[..split]); + hash.do_update(&[]); + hash.do_update(&m[split..]); + assert_eq!(hash.do_final(), unkeyed, "split at {split}"); + + let mut mac = kat_key().create_generator(); + mac.do_update(&m[..split]); + mac.do_update(&m[split..]); + assert_eq!(mac.do_final(), keyed, "keyed split at {split}"); + } + for chunk in 1..=(2 * BB + 1) { + let mut hash = H::new(); + let mut mac = kat_key().create_generator(); + for part in m.chunks(chunk) { + hash.do_update(part); + mac.do_update(part); + } + assert_eq!(hash.do_final(), unkeyed, "chunk size {chunk}"); + assert_eq!(mac.do_final(), keyed, "keyed chunk size {chunk}"); + } + } + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + tf.test_hash::(b"abc", &h(RFC_ABC)); + for len in [0, 1, 64, BB - 1, BB, BB + 1, 255] { + tf.test_hash::(msg(len), &kat(len).0); + tf.test_hash_output_buffers(H::new, msg(len)); + } + } + + #[test] + fn core_test_framework_mac() { + let tf = TestFrameworkMAC::new(); + for len in [0, 3, BB, BB + 1, 255] { + tf.test_mac_key(&kat_key(), msg(len), &kat(len).1); + } + TestFrameworkFromAnyKeyMaterial::new().test::(&kat_material()); + } + + #[test] + fn constants() { + assert_eq!(BLAKE2B_512_NAME, "BLAKE2b-512"); + assert_eq!(BLAKE2B_512_MAC_NAME, "BLAKE2b-512-MAC"); + assert_eq!(H::ALG_NAME, BLAKE2B_512_NAME); + assert_eq!(M::ALG_NAME, BLAKE2B_512_MAC_NAME); + assert_eq!(H::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(M::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(H::new().max_security_strength(), SecurityStrength::_256bit); + assert_eq!(kat_key().create_generator().max_security_strength(), SecurityStrength::_256bit); + assert_eq!(H::OUTPUT_LEN, NN); + assert_eq!(H::BLOCK_LEN, BB); + assert_eq!(H::new().output_len(), NN); + assert_eq!(H::default().block_bitlen(), BB * 8); + assert_eq!(kat_key().create_generator().output_len(), NN); + // RFC 7693 s. 4: id-blake2b512 = 1.3.6.1.4.1.1722.12.2.1.16. DER checked with + // `openssl asn1parse -genstr OID:1.3.6.1.4.1.1722.12.2.1.16`. + assert_eq!(H::OID, &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 1, 16]); + assert_eq!(H::OID_DER, h("060b2b060104018d3a0c020110").as_slice()); + assert_eq!(STATE_LEN, 3 + 64 + 16 + BB + 2); + assert_eq!(MAC_STATE_LEN, STATE_LEN); + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + // RFC 7693 s. 4: the same OID covers keyed hashing. + assert_eq!(M::OID, H::OID); + assert_eq!(M::OID_DER, H::OID_DER); + } + + /// BLAKE2 is byte-oriented: 0 partial bits is do_final, 1..=7 is InvalidInput, more is + /// InvalidLength. + #[test] + fn partial_bits() { + let (expected, _) = kat(3); + let started = || { + let mut hash = H::new(); + hash.do_update(msg(3)); + hash + }; + + assert_eq!(started().do_final_partial_bits(0xFF, 0).unwrap(), expected); + let mut out = [0xAAu8; NN + 1]; + assert_eq!(started().do_final_partial_bits_out(0xFF, 0, &mut out).unwrap(), NN); + assert_eq!(&out[..NN], expected.as_slice()); + assert_eq!(out[NN], 0); + + for num_bits in 1..=7 { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + for num_bits in [8, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidLength(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + } + + /// The BLAKE2-specific construction rules; type and strength policy is the framework's. + #[test] + fn key_validation() { + // Empty key: kk = 0 is the unkeyed hash, not a MAC. + let empty = KeyMaterial::::new(); + assert!(matches!(K::from_key_material_allow_weak(&empty), Err(MACError::InvalidLength(_)))); + + // Over-long key. + let long = key_with(KK_MAX + 1, SecurityStrength::_256bit); + assert!(matches!(K::from_key_material(&long), Err(MACError::InvalidLength(_)))); + assert!(matches!(K::from_key_material_allow_weak(&long), Err(MACError::InvalidLength(_)))); + + // Boundary lengths are accepted. + assert!(K::from_key_material_allow_weak(&key_with(1, SecurityStrength::None)).is_ok()); + assert!(K::from_key_material(&key_with(KK_MAX, SecurityStrength::_256bit)).is_ok()); + + // Order: type before strength before length. + let mut wrong = key_with(KK_MAX + 1, SecurityStrength::None); + do_hazardous_operations(&mut wrong, |k| k.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); + assert!(matches!( + K::from_key_material(&wrong), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + let none64 = key_with(64, SecurityStrength::None); + assert!(matches!( + K::from_key_material(&none64), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let weak = key_with(KK_MAX, SecurityStrength::_192bit); + assert!(matches!( + K::from_key_material(&weak), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + + // An all-zero key is tagged Zeroized (no strength): allowed only as a weak key, and it is + // a real key, distinct from the unkeyed hash. + let zero = + KeyMaterial::::from_bytes_as_type(&[0u8; KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(matches!( + K::from_key_material(&zero), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let tag = K::from_key_material_allow_weak(&zero).unwrap().mac(b"abc"); + assert_ne!(tag, H::hash(b"abc")); + } + + /// generate draws KK_MAX bytes from the RNG and refuses an RNG below the MAC's strength. + #[test] + fn key_generate() { + let seed: [u8; 64] = core::array::from_fn(|i| DUMMY_SEED[300 + i]); + let generated = K::generate(&mut FixedSeedRNG::<64>::new(seed)).unwrap(); + let material = + KeyMaterial::::from_bytes_as_type(&seed[..KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(generated.mac(b"abc"), K::from_key_material(&material).unwrap().mac(b"abc")); + + let mut weak_rng = FixedSeedRNG::<64>::new(seed); + weak_rng.set_security_strength(SecurityStrength::_192bit); + assert!(matches!( + K::generate(&mut weak_rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + + /// The key length kk is bound into the parameter block: a key and the same key with a zero + /// byte appended give different tags, even though they pad to the same block d[0]. + #[test] + fn key_length_bound() { + let short = key_with(KK_MAX - 1, SecurityStrength::_256bit); + let mut padded_bytes = [0xA5u8; KK_MAX]; + padded_bytes[KK_MAX - 1] = 0; + let padded = + KeyMaterial::::from_bytes_as_type(&padded_bytes, KeyType::MACKey).unwrap(); + assert_ne!( + K::from_key_material_allow_weak(&short).unwrap().create_generator().mac(b"abc"), + K::from_key_material_allow_weak(&padded).unwrap().create_generator().mac(b"abc") + ); + } + + #[test] + fn mac_output_buffers() { + let (_, tag) = kat(65); + let started = || { + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + mac + }; + + // Below the floor: rejected, through every entry point, with the buffer zeroed first. + for len in 0..MIN_MAC_OUTPUT_LEN { + let mut out = vec![0xAAu8; len]; + assert!(matches!(started().do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().create_generator().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + } + // At the floor and above: truncated. + for len in MIN_MAC_OUTPUT_LEN..=NN { + let mut out = vec![0xAAu8; len]; + assert_eq!(started().do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + let mut out = vec![0xAAu8; len]; + assert_eq!(kat_key().create_generator().mac_out(msg(65), &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + // Oversized: the tag, then zeros. + let mut out = vec![0xAAu8; NN + 7]; + assert_eq!(started().do_final_out(&mut out).unwrap(), NN); + assert_eq!(&out[..NN], tag.as_slice()); + assert!(out[NN..].iter().all(|&b| b == 0)); + } + + #[test] + fn mac_verify() { + let (_, tag) = kat(65); + assert!(kat_key().verify(msg(65), &tag)); + assert!(kat_key().create_verifier().verify(msg(65), &tag)); + // Exact length only. + assert!(!kat_key().verify(msg(65), &tag[..NN - 1])); + let mut longer = tag.clone(); + longer.push(0); + assert!(!kat_key().verify(msg(65), &longer)); + // Any flipped bit fails. + for i in [0, NN / 2, NN - 1] { + let mut bad = tag.clone(); + bad[i] ^= 0x01; + assert!(!kat_key().verify(msg(65), &bad)); + assert!(!kat_key().create_verifier().verify(msg(65), &bad)); + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(!mac.do_verify_final(&bad)); + } + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(mac.do_verify_final(&tag)); + } + + #[test] + fn mac_clone() { + let (_, tag) = kat(255); + let mut mac = kat_key().create_generator(); + mac.do_update(&msg(255)[..100]); + let mut fork = mac.clone(); + mac.do_update(&msg(255)[100..]); + fork.do_update(&msg(255)[100..]); + assert_eq!(mac.do_final(), tag); + assert_eq!(fork.clone().do_final(), tag); + fork.do_update(b"x"); + assert_ne!(fork.do_final(), tag); + } + + /// Suspending at every interesting position and resuming finishes with the same digest. + #[test] + fn suspend_resume_hash() { + let (expected, _) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 2 * BB - 1, 255] { + let mut hash = H::new(); + hash.do_update(&msg(255)[..at]); + let state = hash.suspend(); + let mut resumed = H::from_suspended(state).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), expected, "suspended at {at}"); + } + + let mut hash = H::new(); + hash.do_update(&msg(255)[..BB + 3]); + TestFrameworkSuspendableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&H::new()); + } + + /// Pins the documented state layout. + #[test] + fn suspend_layout() { + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + // h[0] = IV[0] ^ 0x01010000 ^ (kk << 8) ^ nn, little-endian: RFC 7693 Appendix A shows + // v[0] = 6A09E667F2BDC948 at i = 0. + assert_eq!(state[3..11], 0x6A09E667F2BDC948u64.to_le_bytes()); + assert!(state[T_OFF..BUF_OFF].iter().all(|&b| b == 0), "t = 0"); + assert_eq!(&state[BUF_OFF..BUF_OFF + 3], b"abc"); + assert_eq!(state[BUF_LEN_OFF], 3); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8, "t = bb after one block"); + assert_eq!(state[BUF_OFF], BB as u8, "buf holds message byte bb"); + assert_eq!(state[BUF_LEN_OFF], 1); + assert_eq!(state[KK_OFF], 0, "kk = 0 for the unkeyed hash"); + + let mut mac = kat_key().create_generator(); + mac.do_update(b"x"); + assert_eq!(mac.suspend()[KK_OFF], KK_MAX as u8, "kk is the key length"); + } + + /// After a compression the hash and MAC states have the same shape and only kk tells them + /// apart: a MAC state must not finish as a keyless hash, a hash state must not verify as a + /// MAC, and a key of another length is refused. + #[test] + fn suspend_rejects_cross_type_after_compression() { + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 1)); + let mac_state = mac.suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let hash_state = hash.suspend(); + assert!(matches!(M::from_suspended(hash_state, &key), Err(SuspendableError::InvalidData))); + + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::None)).unwrap(); + assert!(matches!(M::from_suspended(mac_state, &short), Err(SuspendableError::InvalidData))); + + // kk above the RFC's maximum is never produced. + let mut st = hash_state; + st[KK_OFF] = KK_MAX as u8 + 1; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData))); + } + + /// `update` keeps at least one byte back after a compression, so an empty buffer with t > 0 + /// is a state no hasher produces. + #[test] + fn suspend_rejects_empty_buffer_after_compression() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let mut state = hash.suspend(); + assert_eq!(state[BUF_LEN_OFF], 1); + state[BUF_LEN_OFF] = 0; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = 0; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } + + #[test] + fn suspend_rejects_bad_buf_len() { + let mut hash = H::new(); + hash.do_update(msg(BB)); + let mut state = hash.suspend(); + // A full buffer is valid ... + assert_eq!(state[BUF_LEN_OFF], BB as u8); + let (expected, _) = kat(BB); + assert_eq!(H::from_suspended(state).unwrap().do_final(), expected); + // ... one more byte is not. + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = 0xFF; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + } + + /// Before any compression the hash's h must be the unkeyed initial state: a keyed state + /// (h carries kk in p[0]) or a tampered h is rejected. + #[test] + fn suspend_hash_rejects_foreign_h() { + // A fresh MAC state has the same shape as a hash state with a full buffer. + let mac_state: [u8; STATE_LEN] = kat_key().create_generator().suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + assert!(H::from_suspended(state).is_ok()); + for i in [3, 4, (3 + T_OFF) / 2, T_OFF - 1] { + let mut st = state; + st[i] ^= 0x01; + assert!( + matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), + "h byte {i}" + ); + } + + // After a compression h is no longer the initial state, and that is fine. + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + assert!(H::from_suspended(hash.suspend()).is_ok()); + } + + /// t must be a whole number of blocks, in both the hash and the MAC state. + #[test] + fn suspend_rejects_bad_t() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), "t {bad}"); + } + // A larger multiple of bb is fine. + let mut st = state; + st[T_OFF + 1] = 1; + assert!(H::from_suspended(st).is_ok()); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let state = mac.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(M::from_suspended(st, &key), Err(SuspendableError::InvalidData))); + } + } + + /// Before any input the serialized buffer is ignored and rebuilt from the supplied key. + #[test] + fn suspend_mac_ignores_serialized_key_block() { + let key = kat_key(); + let (_, tag) = kat(3); + let mut state = key.create_generator().suspend(); + state[BUF_OFF..BUF_OFF + BB].fill(0x5A); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + } + + /// The key never appears in a keyed state, and resuming with the key gives the KAT tag. + #[test] + fn suspend_resume_mac() { + let key = kat_key(); + let (_, tag) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 255] { + let mut mac = key.create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + if at == 0 { + // Before any input the buffer is the key block, which must be written as zeros. + assert!(state[BUF_OFF..BUF_OFF + BB].iter().all(|&b| b == 0)); + assert_eq!(state[BUF_LEN_OFF], BB as u8); + } + let mut resumed = M::from_suspended(state, &key).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), tag, "suspended at {at}"); + + // The KAT key 0, 1, 2, ... is also a run of the KAT message, so look for a key that + // cannot occur in the message: 0xA5 repeated. + let distinct = key_with(KK_MAX, SecurityStrength::_256bit); + let mut mac = K::from_key_material(&distinct).unwrap().create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + assert!( + !state.windows(8).any(|w| w == &distinct.ref_to_bytes()[..8]), + "key bytes found in the state suspended at {at}" + ); + } + + TestFrameworkSuspendableKeyedState::new().test(&key.create_generator(), &key); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 3)); + TestFrameworkSuspendableKeyedState::new().test(&mac, &key); + } + + /// Before any input, the resumed MAC depends on the supplied key; after it, the key has been + /// folded into h and the supplied key is validated but not used. + #[test] + fn suspend_mac_wrong_key() { + let key = kat_key(); + let other = K::from_key_material(&key_with(KK_MAX, SecurityStrength::_256bit)).unwrap(); + let (_, tag) = kat(3); + + let state = key.create_generator().suspend(); + assert_ne!(M::from_suspended(state, &other).unwrap().mac(msg(3)), tag); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + + let mut mac = key.create_generator(); + mac.do_update(msg(1)); + let state = mac.suspend(); + let mut resumed = M::from_suspended(state, &other).unwrap(); + resumed.do_update(&msg(3)[1..]); + assert_eq!(resumed.do_final(), tag); + } + + /// A wrong key type or length cannot reach resume (the key object enforces them), but a valid + /// key of a different length can: kk is bound into h, so a state taken before any input is + /// rejected. The strength is not re-checked, so a MAC from a weak key resumes. + #[test] + fn suspend_mac_key_length_and_strength() { + let state = kat_key().create_generator().suspend(); + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::_256bit)) + .unwrap(); + assert!(matches!(M::from_suspended(state, &short), Err(SuspendableError::InvalidData))); + + let weak = + K::from_key_material_allow_weak(&key_with(KK_MAX, SecurityStrength::None)).unwrap(); + let weak_state = weak.create_generator().suspend(); + assert_eq!(M::from_suspended(weak_state, &weak).unwrap().mac(b"abc"), weak.mac(b"abc")); + } + + #[test] + fn suspend_mac_rejects_bad_state() { + let key = kat_key(); + + // Before any input the buffer must be the full key block. + let mut state = key.create_generator().suspend(); + state[BUF_LEN_OFF] = BB as u8 - 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + + // An unkeyed hash state of the same shape (t == 0, full buffer) is not a keyed state. + let mut hash = H::new(); + hash.do_update(&[0u8; BB]); + let hash_state = hash.suspend(); + let as_mac: [u8; MAC_STATE_LEN] = hash_state; + assert!(matches!(M::from_suspended(as_mac, &key), Err(SuspendableError::InvalidData))); + + // After input, buf_len is still bounded. + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } +} diff --git a/crypto/blake2/tests/blake2s_tests.rs b/crypto/blake2/tests/blake2s_tests.rs new file mode 100644 index 000000000..f000aef01 --- /dev/null +++ b/crypto/blake2/tests/blake2s_tests.rs @@ -0,0 +1,763 @@ +//! Tests for BLAKE2s-256 and keyed BLAKE2s-256 (RFC 7693). +//! +//! Vector sources: RFC 7693 Appendix B; the official BLAKE2 KAT (blake2-kat.json, embedded below); +//! and the bc-test-data sweep in `bc_test_data.rs`. Wycheproof has no BLAKE2 vectors, so the +//! QUALITY_AND_STYLE.md "against wycheproof" requirement is satisfied vacuously. + +#[cfg(test)] +mod blake2s_tests { + use bouncycastle_blake2::*; + use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; + 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::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, AlgorithmOID, Hash, HashAlgParams, HashOneShot, MAC, + MIN_MAC_OUTPUT_LEN, Suspendable, SuspendableKeyed, + }; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::{ + TestFrameworkSuspendableKeyedState, TestFrameworkSuspendableState, + }; + use bouncycastle_hex as hex; + + type H = BLAKE2s_256; + type K = BLAKE2s_256_MACKey; + type M = BLAKE2s_256_MAC; + /// RFC 7693 s. 2.1 block bytes bb. + const BB: usize = 64; + /// Digest bytes nn. + const NN: usize = 32; + /// Largest key, kk. + const KK_MAX: usize = 32; + const STATE_LEN: usize = SUSPENDED_BLAKE2S_256_STATE_LEN; + const MAC_STATE_LEN: usize = SUSPENDED_BLAKE2S_256_MAC_STATE_LEN; + /// Offsets in the suspended state (after the 3-byte version header) of t, buf, buf_len and kk. + const T_OFF: usize = 3 + 32; + const BUF_OFF: usize = 3 + 40; + const BUF_LEN_OFF: usize = 3 + 104; + const KK_OFF: usize = 3 + 105; + + /// RFC 7693 Appendix B: BLAKE2s-256("abc"), copied by script from the RFC text. + const RFC_ABC: &str = "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982"; + + /// (input length, unkeyed digest, keyed digest) for blake2s: generated by script from the + /// official BLAKE2 KAT, github.com/BLAKE2/BLAKE2 testvectors/blake2-kat.json. The input is + /// the bytes 0, 1, 2, ..., len - 1 and the key is the 32 bytes 0, 1, ..., 31. + const KAT: [(usize, &str, &str); 19] = [ + ( + 0, + "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", + "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49", + ), + ( + 1, + "e34d74dbaf4ff4c6abd871cc220451d2ea2648846c7757fbaac82fe51ad64bea", + "40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1", + ), + ( + 2, + "ddad9ab15dac4549ba42f49d262496bef6c0bae1dd342a8808f8ea267c6e210c", + "6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803", + ), + ( + 3, + "e8f91c6ef232a041452ab0e149070cdd7dd1769e75b3a5921be37876c45c9900", + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + ), + ( + 7, + "598001fafbe8f94ec66dc827d012cfcbba2228569f448e89ea2208c8bf769293", + "e6c8125637438d0905b749f46560ac89fd471cf8692e28fab982f73f019b83a9", + ), + ( + 8, + "c7e887b546623635e93e0495598f1726821996c2377705b93a1f636f872bfa2d", + "19fc8ca6979d60e6edd3b4541e2f967ced740df6ec1eaebbfe813832e96b2974", + ), + ( + 55, + "f4495470f226c8c214be08fdfad4bc4a2a9dbea9136a210df0d4b64929e6fc14", + "f16012d93f28851a1eb989f5d0b43f3f39ca73c9a62d5181bff237536bd348c3", + ), + ( + 56, + "e290dd270b467f34ab1c002d340fa016257ff19e5833fdbbf2cb401c3b2817de", + "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806", + ), + ( + 57, + "9fc7b5ded3c15042b2a6582dc39be016d24a682d5e61ad1eff9c63309848f706", + "ddb8782485e900bc60bcf4c33a6fd585680cc683d516efa03eb9985fad8715fb", + ), + ( + 63, + "e57cb79487dd57902432b250733813bd96a84efce59f650fac26e6696aefafc3", + "c65382513f07460da39833cb666c5ed82e61b9e998f4b0c4287cee56c3cc9bcd", + ), + ( + 64, + "56f34e8b96557e90c1f24b52d0c89d51086acf1b00f634cf1dde9233b8eaaa3e", + "8975b0577fd35566d750b362b0897a26c399136df07bababbde6203ff2954ed4", + ), + ( + 65, + "1b53ee94aaf34e4b159d48de352c7f0661d0a40edff95a0b1639b4090e974472", + "21fe0ceb0052be7fb0f004187cacd7de67fa6eb0938d927677f2398c132317a8", + ), + ( + 127, + "f18417b39d617ab1c18fdf91ebd0fc6d5516bb34cf39364037bce81fa04cecb1", + "ddbfea75cc467882eb3483ce5e2e756a4f4701b76b445519e89f22d60fa86e06", + ), + ( + 128, + "1fa877de67259d19863a2a34bcc6962a2b25fcbf5cbecd7ede8f1fa36688a796", + "0c311f38c35a4fb90d651c289d486856cd1413df9b0677f53ece2cd9e477c60a", + ), + ( + 129, + "5bd169e67c82c2c2e98ef7008bdf261f2ddf30b1c00f9e7f275bb3e8a28dc9a2", + "46a73a8dd3e70f59d3942c01df599def783c9da82fd83222cd662b53dce7dbdf", + ), + ( + 191, + "d120b5ef6d57ebf06eaf96bc933c967b16cbe6e2bf00741c30aa1c54ba64801f", + "91a25ec0ec0d9a567f89c4bfe1a65a0e432d07064b4190e27dfb81901fd3139b", + ), + ( + 192, + "58d212ad6f58aef0f80116b441e57f6195bfef26b61463edec1183cdb04fe76d", + "5950d39a23e1545f301270aa1a12f2e6c453776e4d6355de425cc153f9818867", + ), + ( + 193, + "b8836f51d1e29bdfdba325565360268b8fad627473edecef7eaefee837c74003", + "d79f14720c610af179a3765d4b7c0968f977962dbf655b521272b6f1e194488e", + ), + ( + 255, + "f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d", + "3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd", + ), + ]; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /// The KAT message of length `len`: bytes 0, 1, ..., len - 1. + fn msg(len: usize) -> &'static [u8] { + &DUMMY_SEED[..len] + } + + /// The KAT key material, bytes 0..KK_MAX, as a MAC key. + fn kat_material() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KK_MAX], KeyType::MACKey).unwrap() + } + + /// The KAT key object. + fn kat_key() -> K { + K::from_key_material(&kat_material()).unwrap() + } + + /// A MAC key of `len` bytes 0xA5.., tagged with `strength`. + fn key_with(len: usize, strength: SecurityStrength) -> KeyMaterial<256> { + let mut key = + KeyMaterial::<256>::from_bytes_as_type(&[0xA5u8; 256][..len], KeyType::MACKey).unwrap(); + do_hazardous_operations(&mut key, |k| k.set_security_strength(strength)).unwrap(); + key + } + + fn kat(len: usize) -> (Vec, Vec) { + let (_, unkeyed, keyed) = KAT.iter().find(|(n, _, _)| *n == len).unwrap(); + (h(unkeyed), h(keyed)) + } + + #[test] + fn rfc7693_appendix_b() { + assert_eq!(H::hash(b"abc"), h(RFC_ABC)); + } + + /// Every embedded KAT entry, one-shot and streamed byte-at-a-time, hash and MAC. + #[test] + fn official_kat() { + for (len, unkeyed, keyed) in KAT { + let (unkeyed, keyed) = (h(unkeyed), h(keyed)); + let m = msg(len); + + assert_eq!(H::hash(m), unkeyed, "hash, len {len}"); + let mut hash = H::new(); + for byte in m { + hash.do_update(core::slice::from_ref(byte)); + } + assert_eq!(hash.do_final(), unkeyed, "byte-at-a-time hash, len {len}"); + + assert_eq!(kat_key().create_generator().mac(m), keyed, "mac, len {len}"); + let mut mac = kat_key().create_generator(); + for byte in m { + mac.do_update(core::slice::from_ref(byte)); + } + assert_eq!(mac.do_final(), keyed, "byte-at-a-time mac, len {len}"); + } + } + + /// Splitting the input at every point, and feeding it in every chunk size up to two blocks, + /// gives the one-shot answer. This walks every branch of the buffering in do_update. + #[test] + fn streaming_splits() { + let (unkeyed, keyed) = kat(255); + let m = msg(255); + for split in 0..=m.len() { + let mut hash = H::new(); + hash.do_update(&m[..split]); + hash.do_update(&[]); + hash.do_update(&m[split..]); + assert_eq!(hash.do_final(), unkeyed, "split at {split}"); + + let mut mac = kat_key().create_generator(); + mac.do_update(&m[..split]); + mac.do_update(&m[split..]); + assert_eq!(mac.do_final(), keyed, "keyed split at {split}"); + } + for chunk in 1..=(2 * BB + 1) { + let mut hash = H::new(); + let mut mac = kat_key().create_generator(); + for part in m.chunks(chunk) { + hash.do_update(part); + mac.do_update(part); + } + assert_eq!(hash.do_final(), unkeyed, "chunk size {chunk}"); + assert_eq!(mac.do_final(), keyed, "keyed chunk size {chunk}"); + } + } + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + tf.test_hash::(b"abc", &h(RFC_ABC)); + for len in [0, 1, 64, BB - 1, BB, BB + 1, 255] { + tf.test_hash::(msg(len), &kat(len).0); + tf.test_hash_output_buffers(H::new, msg(len)); + } + } + + #[test] + fn core_test_framework_mac() { + let tf = TestFrameworkMAC::new(); + for len in [0, 3, BB, BB + 1, 255] { + tf.test_mac_key(&kat_key(), msg(len), &kat(len).1); + } + TestFrameworkFromAnyKeyMaterial::new().test::(&kat_material()); + } + + #[test] + fn constants() { + assert_eq!(BLAKE2S_256_NAME, "BLAKE2s-256"); + assert_eq!(BLAKE2S_256_MAC_NAME, "BLAKE2s-256-MAC"); + assert_eq!(H::ALG_NAME, BLAKE2S_256_NAME); + assert_eq!(M::ALG_NAME, BLAKE2S_256_MAC_NAME); + assert_eq!(H::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(M::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(H::new().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(kat_key().create_generator().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(H::OUTPUT_LEN, NN); + assert_eq!(H::BLOCK_LEN, BB); + assert_eq!(H::new().output_len(), NN); + assert_eq!(H::default().block_bitlen(), BB * 8); + assert_eq!(kat_key().create_generator().output_len(), NN); + // RFC 7693 s. 4: id-blake2s256 = 1.3.6.1.4.1.1722.12.2.2.8. DER checked with + // `openssl asn1parse -genstr OID:1.3.6.1.4.1.1722.12.2.2.8`. + assert_eq!(H::OID, &[1, 3, 6, 1, 4, 1, 1722, 12, 2, 2, 8]); + assert_eq!(H::OID_DER, h("060b2b060104018d3a0c020208").as_slice()); + assert_eq!(STATE_LEN, 3 + 32 + 8 + BB + 2); + assert_eq!(MAC_STATE_LEN, STATE_LEN); + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + // RFC 7693 s. 4: the same OID covers keyed hashing. + assert_eq!(M::OID, H::OID); + assert_eq!(M::OID_DER, H::OID_DER); + } + + /// BLAKE2 is byte-oriented: 0 partial bits is do_final, 1..=7 is InvalidInput, more is + /// InvalidLength. + #[test] + fn partial_bits() { + let (expected, _) = kat(3); + let started = || { + let mut hash = H::new(); + hash.do_update(msg(3)); + hash + }; + + assert_eq!(started().do_final_partial_bits(0xFF, 0).unwrap(), expected); + let mut out = [0xAAu8; NN + 1]; + assert_eq!(started().do_final_partial_bits_out(0xFF, 0, &mut out).unwrap(), NN); + assert_eq!(&out[..NN], expected.as_slice()); + assert_eq!(out[NN], 0); + + for num_bits in 1..=7 { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + for num_bits in [8, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + started().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidLength(_)) + )); + out.fill(0xAA); + assert!(matches!( + started().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "the buffer is zeroed before the error"); + } + } + + /// The BLAKE2-specific construction rules; type and strength policy is the framework's. + #[test] + fn key_validation() { + // Empty key: kk = 0 is the unkeyed hash, not a MAC. + let empty = KeyMaterial::::new(); + assert!(matches!(K::from_key_material_allow_weak(&empty), Err(MACError::InvalidLength(_)))); + + // Over-long key. + let long = key_with(KK_MAX + 1, SecurityStrength::_128bit); + assert!(matches!(K::from_key_material(&long), Err(MACError::InvalidLength(_)))); + assert!(matches!(K::from_key_material_allow_weak(&long), Err(MACError::InvalidLength(_)))); + + // Boundary lengths are accepted. + assert!(K::from_key_material_allow_weak(&key_with(1, SecurityStrength::None)).is_ok()); + assert!(K::from_key_material(&key_with(KK_MAX, SecurityStrength::_128bit)).is_ok()); + + // Order: type before strength before length. + let mut wrong = key_with(KK_MAX + 1, SecurityStrength::None); + do_hazardous_operations(&mut wrong, |k| k.set_key_type(KeyType::SymmetricCipherKey)) + .unwrap(); + assert!(matches!( + K::from_key_material(&wrong), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + let none64 = key_with(64, SecurityStrength::None); + assert!(matches!( + K::from_key_material(&none64), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let weak = key_with(KK_MAX, SecurityStrength::_112bit); + assert!(matches!( + K::from_key_material(&weak), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + + // An all-zero key is tagged Zeroized (no strength): allowed only as a weak key, and it is + // a real key, distinct from the unkeyed hash. + let zero = + KeyMaterial::::from_bytes_as_type(&[0u8; KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(matches!( + K::from_key_material(&zero), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + let tag = K::from_key_material_allow_weak(&zero).unwrap().mac(b"abc"); + assert_ne!(tag, H::hash(b"abc")); + } + + /// generate draws KK_MAX bytes from the RNG and refuses an RNG below the MAC's strength. + #[test] + fn key_generate() { + let seed: [u8; 64] = core::array::from_fn(|i| DUMMY_SEED[300 + i]); + let generated = K::generate(&mut FixedSeedRNG::<64>::new(seed)).unwrap(); + let material = + KeyMaterial::::from_bytes_as_type(&seed[..KK_MAX], KeyType::MACKey).unwrap(); + assert_eq!(generated.mac(b"abc"), K::from_key_material(&material).unwrap().mac(b"abc")); + + let mut weak_rng = FixedSeedRNG::<64>::new(seed); + weak_rng.set_security_strength(SecurityStrength::_112bit); + assert!(matches!( + K::generate(&mut weak_rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + + /// The key length kk is bound into the parameter block: a key and the same key with a zero + /// byte appended give different tags, even though they pad to the same block d[0]. + #[test] + fn key_length_bound() { + let short = key_with(KK_MAX - 1, SecurityStrength::_128bit); + let mut padded_bytes = [0xA5u8; KK_MAX]; + padded_bytes[KK_MAX - 1] = 0; + let padded = + KeyMaterial::::from_bytes_as_type(&padded_bytes, KeyType::MACKey).unwrap(); + assert_ne!( + K::from_key_material_allow_weak(&short).unwrap().create_generator().mac(b"abc"), + K::from_key_material_allow_weak(&padded).unwrap().create_generator().mac(b"abc") + ); + } + + #[test] + fn mac_output_buffers() { + let (_, tag) = kat(65); + let started = || { + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + mac + }; + + // Below the floor: rejected, through every entry point, with the buffer zeroed first. + for len in 0..MIN_MAC_OUTPUT_LEN { + let mut out = vec![0xAAu8; len]; + assert!(matches!(started().do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().create_generator().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + out.fill(0xAA); + assert!(matches!( + kat_key().mac_out(msg(65), &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0)); + } + // At the floor and above: truncated. + for len in MIN_MAC_OUTPUT_LEN..=NN { + let mut out = vec![0xAAu8; len]; + assert_eq!(started().do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + let mut out = vec![0xAAu8; len]; + assert_eq!(kat_key().create_generator().mac_out(msg(65), &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + // Oversized: the tag, then zeros. + let mut out = vec![0xAAu8; NN + 7]; + assert_eq!(started().do_final_out(&mut out).unwrap(), NN); + assert_eq!(&out[..NN], tag.as_slice()); + assert!(out[NN..].iter().all(|&b| b == 0)); + } + + #[test] + fn mac_verify() { + let (_, tag) = kat(65); + assert!(kat_key().verify(msg(65), &tag)); + assert!(kat_key().create_verifier().verify(msg(65), &tag)); + // Exact length only. + assert!(!kat_key().verify(msg(65), &tag[..NN - 1])); + let mut longer = tag.clone(); + longer.push(0); + assert!(!kat_key().verify(msg(65), &longer)); + // Any flipped bit fails. + for i in [0, NN / 2, NN - 1] { + let mut bad = tag.clone(); + bad[i] ^= 0x01; + assert!(!kat_key().verify(msg(65), &bad)); + assert!(!kat_key().create_verifier().verify(msg(65), &bad)); + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(!mac.do_verify_final(&bad)); + } + let mut mac = kat_key().create_generator(); + mac.do_update(msg(65)); + assert!(mac.do_verify_final(&tag)); + } + + #[test] + fn mac_clone() { + let (_, tag) = kat(255); + let mut mac = kat_key().create_generator(); + mac.do_update(&msg(255)[..100]); + let mut fork = mac.clone(); + mac.do_update(&msg(255)[100..]); + fork.do_update(&msg(255)[100..]); + assert_eq!(mac.do_final(), tag); + assert_eq!(fork.clone().do_final(), tag); + fork.do_update(b"x"); + assert_ne!(fork.do_final(), tag); + } + + /// Suspending at every interesting position and resuming finishes with the same digest. + #[test] + fn suspend_resume_hash() { + let (expected, _) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 2 * BB - 1, 255] { + let mut hash = H::new(); + hash.do_update(&msg(255)[..at]); + let state = hash.suspend(); + let mut resumed = H::from_suspended(state).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), expected, "suspended at {at}"); + } + + let mut hash = H::new(); + hash.do_update(&msg(255)[..BB + 3]); + TestFrameworkSuspendableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&H::new()); + } + + /// Pins the documented state layout. + #[test] + fn suspend_layout() { + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + // h[0] = IV[0] ^ 0x01010000 ^ (kk << 8) ^ nn, little-endian: RFC 7693 Appendix B shows + // v[0] = 6B08E647 at i = 0. + assert_eq!(state[3..7], 0x6B08E647u32.to_le_bytes()); + assert!(state[T_OFF..BUF_OFF].iter().all(|&b| b == 0), "t = 0"); + assert_eq!(&state[BUF_OFF..BUF_OFF + 3], b"abc"); + assert_eq!(state[BUF_LEN_OFF], 3); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8, "t = bb after one block"); + assert_eq!(state[BUF_OFF], BB as u8, "buf holds message byte bb"); + assert_eq!(state[BUF_LEN_OFF], 1); + assert_eq!(state[KK_OFF], 0, "kk = 0 for the unkeyed hash"); + + let mut mac = kat_key().create_generator(); + mac.do_update(b"x"); + assert_eq!(mac.suspend()[KK_OFF], KK_MAX as u8, "kk is the key length"); + } + + /// After a compression the hash and MAC states have the same shape and only kk tells them + /// apart: a MAC state must not finish as a keyless hash, a hash state must not verify as a + /// MAC, and a key of another length is refused. + #[test] + fn suspend_rejects_cross_type_after_compression() { + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 1)); + let mac_state = mac.suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let hash_state = hash.suspend(); + assert!(matches!(M::from_suspended(hash_state, &key), Err(SuspendableError::InvalidData))); + + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::None)).unwrap(); + assert!(matches!(M::from_suspended(mac_state, &short), Err(SuspendableError::InvalidData))); + + // kk above the RFC's maximum is never produced. + let mut st = hash_state; + st[KK_OFF] = KK_MAX as u8 + 1; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData))); + } + + /// `update` keeps at least one byte back after a compression, so an empty buffer with t > 0 + /// is a state no hasher produces. + #[test] + fn suspend_rejects_empty_buffer_after_compression() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let mut state = hash.suspend(); + assert_eq!(state[BUF_LEN_OFF], 1); + state[BUF_LEN_OFF] = 0; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = 0; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } + + #[test] + fn suspend_rejects_bad_buf_len() { + let mut hash = H::new(); + hash.do_update(msg(BB)); + let mut state = hash.suspend(); + // A full buffer is valid ... + assert_eq!(state[BUF_LEN_OFF], BB as u8); + let (expected, _) = kat(BB); + assert_eq!(H::from_suspended(state).unwrap().do_final(), expected); + // ... one more byte is not. + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = 0xFF; + assert!(matches!(H::from_suspended(state), Err(SuspendableError::InvalidData))); + } + + /// Before any compression the hash's h must be the unkeyed initial state: a keyed state + /// (h carries kk in p[0]) or a tampered h is rejected. + #[test] + fn suspend_hash_rejects_foreign_h() { + // A fresh MAC state has the same shape as a hash state with a full buffer. + let mac_state: [u8; STATE_LEN] = kat_key().create_generator().suspend(); + assert!(matches!(H::from_suspended(mac_state), Err(SuspendableError::InvalidData))); + + let mut hash = H::new(); + hash.do_update(b"abc"); + let state = hash.suspend(); + assert!(H::from_suspended(state).is_ok()); + for i in [3, 4, (3 + T_OFF) / 2, T_OFF - 1] { + let mut st = state; + st[i] ^= 0x01; + assert!( + matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), + "h byte {i}" + ); + } + + // After a compression h is no longer the initial state, and that is fine. + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + assert!(H::from_suspended(hash.suspend()).is_ok()); + } + + /// t must be a whole number of blocks, in both the hash and the MAC state. + #[test] + fn suspend_rejects_bad_t() { + let mut hash = H::new(); + hash.do_update(msg(BB + 1)); + let state = hash.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(H::from_suspended(st), Err(SuspendableError::InvalidData)), "t {bad}"); + } + // A larger multiple of bb is fine. + let mut st = state; + st[T_OFF + 1] = 1; + assert!(H::from_suspended(st).is_ok()); + + let key = kat_key(); + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let state = mac.suspend(); + assert_eq!(state[T_OFF], BB as u8); + for bad in [1u8, BB as u8 - 1, BB as u8 + 1] { + let mut st = state; + st[T_OFF] = bad; + assert!(matches!(M::from_suspended(st, &key), Err(SuspendableError::InvalidData))); + } + } + + /// Before any input the serialized buffer is ignored and rebuilt from the supplied key. + #[test] + fn suspend_mac_ignores_serialized_key_block() { + let key = kat_key(); + let (_, tag) = kat(3); + let mut state = key.create_generator().suspend(); + state[BUF_OFF..BUF_OFF + BB].fill(0x5A); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + } + + /// The key never appears in a keyed state, and resuming with the key gives the KAT tag. + #[test] + fn suspend_resume_mac() { + let key = kat_key(); + let (_, tag) = kat(255); + for at in [0, 1, BB - 1, BB, BB + 1, 255] { + let mut mac = key.create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + if at == 0 { + // Before any input the buffer is the key block, which must be written as zeros. + assert!(state[BUF_OFF..BUF_OFF + BB].iter().all(|&b| b == 0)); + assert_eq!(state[BUF_LEN_OFF], BB as u8); + } + let mut resumed = M::from_suspended(state, &key).unwrap(); + resumed.do_update(&msg(255)[at..]); + assert_eq!(resumed.do_final(), tag, "suspended at {at}"); + + // The KAT key 0, 1, 2, ... is also a run of the KAT message, so look for a key that + // cannot occur in the message: 0xA5 repeated. + let distinct = key_with(KK_MAX, SecurityStrength::_128bit); + let mut mac = K::from_key_material(&distinct).unwrap().create_generator(); + mac.do_update(&msg(255)[..at]); + let state = mac.suspend(); + assert!( + !state.windows(8).any(|w| w == &distinct.ref_to_bytes()[..8]), + "key bytes found in the state suspended at {at}" + ); + } + + TestFrameworkSuspendableKeyedState::new().test(&key.create_generator(), &key); + let mut mac = key.create_generator(); + mac.do_update(msg(BB + 3)); + TestFrameworkSuspendableKeyedState::new().test(&mac, &key); + } + + /// Before any input, the resumed MAC depends on the supplied key; after it, the key has been + /// folded into h and the supplied key is validated but not used. + #[test] + fn suspend_mac_wrong_key() { + let key = kat_key(); + let other = K::from_key_material(&key_with(KK_MAX, SecurityStrength::_128bit)).unwrap(); + let (_, tag) = kat(3); + + let state = key.create_generator().suspend(); + assert_ne!(M::from_suspended(state, &other).unwrap().mac(msg(3)), tag); + assert_eq!(M::from_suspended(state, &key).unwrap().mac(msg(3)), tag); + + let mut mac = key.create_generator(); + mac.do_update(msg(1)); + let state = mac.suspend(); + let mut resumed = M::from_suspended(state, &other).unwrap(); + resumed.do_update(&msg(3)[1..]); + assert_eq!(resumed.do_final(), tag); + } + + /// A wrong key type or length cannot reach resume (the key object enforces them), but a valid + /// key of a different length can: kk is bound into h, so a state taken before any input is + /// rejected. The strength is not re-checked, so a MAC from a weak key resumes. + #[test] + fn suspend_mac_key_length_and_strength() { + let state = kat_key().create_generator().suspend(); + let short = + K::from_key_material_allow_weak(&key_with(KK_MAX - 1, SecurityStrength::_128bit)) + .unwrap(); + assert!(matches!(M::from_suspended(state, &short), Err(SuspendableError::InvalidData))); + + let weak = + K::from_key_material_allow_weak(&key_with(KK_MAX, SecurityStrength::None)).unwrap(); + let weak_state = weak.create_generator().suspend(); + assert_eq!(M::from_suspended(weak_state, &weak).unwrap().mac(b"abc"), weak.mac(b"abc")); + } + + #[test] + fn suspend_mac_rejects_bad_state() { + let key = kat_key(); + + // Before any input the buffer must be the full key block. + let mut state = key.create_generator().suspend(); + state[BUF_LEN_OFF] = BB as u8 - 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + + // An unkeyed hash state of the same shape (t == 0, full buffer) is not a keyed state. + let mut hash = H::new(); + hash.do_update(&[0u8; BB]); + let hash_state = hash.suspend(); + let as_mac: [u8; MAC_STATE_LEN] = hash_state; + assert!(matches!(M::from_suspended(as_mac, &key), Err(SuspendableError::InvalidData))); + + // After input, buf_len is still bounded. + let mut mac = key.create_generator(); + mac.do_update(b"x"); + let mut state = mac.suspend(); + state[BUF_LEN_OFF] = BB as u8 + 1; + assert!(matches!(M::from_suspended(state, &key), Err(SuspendableError::InvalidData))); + } +} diff --git a/crypto/blake3/Cargo.toml b/crypto/blake3/Cargo.toml new file mode 100644 index 000000000..406fab1a4 --- /dev/null +++ b/crypto/blake3/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bouncycastle-blake3" +version.workspace = true +edition.workspace = true + +[dependencies] +bouncycastle-core.workspace = true +bouncycastle-utils.workspace = true + +[dev-dependencies] +criterion.workspace = true +bouncycastle-core-test-framework.workspace = true +bouncycastle-hex.workspace = true +bouncycastle-rng.workspace = true + +[[bench]] +name = "blake3_benches" +harness = false diff --git a/crypto/blake3/benches/blake3_benches.rs b/crypto/blake3/benches/blake3_benches.rs new file mode 100644 index 000000000..c40321e7d --- /dev/null +++ b/crypto/blake3/benches/blake3_benches.rs @@ -0,0 +1,56 @@ +//! Criterion benches for BLAKE3. +//! +//! Two cost profiles are measured: absorbing input (the chunk/tree hash) and squeezing extended +//! output (repeated root compressions, s. 2.6). The keyed_hash and derive_key modes are not benched +//! separately: they run the same compression function over the same tree, differing only in the +//! key words and a flag (s. 2.3). derive_key adds one hash of the context string at construction. + +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +use bouncycastle_blake3::BLAKE3; +use bouncycastle_core::traits::{Hash, RNG, XOF, XOFSqueezer}; +use bouncycastle_rng as rng; + +fn bench_blake3(c: &mut Criterion) { + let mut data = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data).unwrap(); + + let mut digest = vec![0; BLAKE3::new().output_len()]; + + let mut group = c.benchmark_group("blake3"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE3::new(); + for _ in 0..16 { + md.do_update(black_box(&data)); + } + _ = md.do_final_out(&mut digest); + black_box(&digest); + }) + }); + group.finish(); +} + +fn bench_blake3_squeeze(c: &mut Criterion) { + let mut output = vec![0_u8; 1024]; + + let mut group = c.benchmark_group("blake3-squeeze"); + group.throughput(Throughput::Bytes(16 * 1024)); + group.bench_function("16KiB", |b| { + b.iter(|| { + let mut md = BLAKE3::new(); + md.do_update(black_box(b"abc")); + let mut squeezer = md.into_squeezer(); + for _ in 0..16 { + squeezer.do_output_out(&mut output); + black_box(&output); + } + }) + }); + group.finish(); +} + +criterion_group!(benches, bench_blake3, bench_blake3_squeeze); +criterion_main!(benches); diff --git a/crypto/blake3/src/blake3.rs b/crypto/blake3/src/blake3.rs new file mode 100644 index 000000000..0a215fee3 --- /dev/null +++ b/crypto/blake3/src/blake3.rs @@ -0,0 +1,1246 @@ +//! The BLAKE3 tree hash (spec s. 2.1, 2.3-2.6) as an incremental hasher (spec s. 5.1). +//! +//! The structure -- `Output`, `ChunkState`, the CV stack, `parent_output` -- follows the reference +//! implementation that BLAKE3 spec s. 5.1 describes "as an aid to implementers", adapted to this +//! library's types: chaining values, buffered input and key words live in [`Secret`]s, and the +//! public types implement the `core` traits rather than the reference's `Hasher` API. + +use crate::compress::{ + BLOCK_LEN, CHUNK_END, CHUNK_LEN, CHUNK_START, DERIVE_KEY_CONTEXT, DERIVE_KEY_MATERIAL, IV, + KEYED_HASH, PARENT, ROOT, compress, words_from_le_bytes, +}; +use bouncycastle_core::errors::{HashError, KeyMaterialError, MACError, SuspendableError}; +use bouncycastle_core::hazmat::do_hazardous_operations; +use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +use bouncycastle_core::traits::{ + Hash, HashOneShot, MAC, MIN_MAC_OUTPUT_LEN, RNG, Suspendable, SuspendableKeyed, XOF, + XOFSqueezer, +}; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_utils::{ct, min, secret::Secret}; + +/// BLAKE3 spec s. 2.1: "The chaining value of the root node, encoded as 32 bytes in little-endian +/// order, is the default-length BLAKE3 hash of the input." +const OUT_LEN: usize = 32; + +/// BLAKE3 spec s. 2.3: "The keyed_hash mode takes an input of any length together with a 256-bit +/// key." +const KEY_LEN: usize = 32; + +/// BLAKE3 spec s. 5.4: "The maximum input size is 2^64 - 1 bytes, and the chunk size is 2^10 +/// bytes, giving a maximum tree depth of 64 - 10 = 54. The CV stack thus requires 54 * 32 = 1728 +/// bytes." +const MAX_DEPTH: usize = 54; + +/// The first 8 words of a compression output: "The output of the compression function is most +/// often truncated to produce 256-bit chaining values" (s. 2.2). +#[inline] +fn first_8_words(words: &[u32; 16]) -> [u32; 8] { + let mut cv = [0u32; 8]; + cv.copy_from_slice(&words[..8]); + cv +} + +/// Reads a little-endian u32 from exactly 4 bytes (serialized-state decoding). +#[inline] +fn u32_from_le(bytes: &[u8]) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(bytes); + u32::from_le_bytes(b) +} + +/// Reads a little-endian u64 from exactly 8 bytes (serialized-state decoding). +#[inline] +fn u64_from_le(bytes: &[u8]) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(bytes); + u64::from_le_bytes(b) +} + +/* --------------------------------------------------------------------------------------------- + * Output: one compression held back until it is known whether it is the root (reference impl). + * ------------------------------------------------------------------------------------------- */ + +/// The inputs of a single compression, captured "just prior to choosing between" producing an +/// 8-word chaining value and, with the ROOT flag, any number of output bytes (reference impl). +/// +/// The incremental hasher cannot know whether a chunk or parent node is the root until the input +/// ends (s. 5.1.1, 5.1.2), so the last compression of each candidate is kept in this form. +#[derive(Clone)] +struct Output { + /// h0 ... h7 for this compression. + input_chaining_value: Secret<[u32; 8]>, + /// m0 ... m15 for this compression. + block_words: Secret<[u32; 16]>, + /// t: the chunk index for a chunk's last block, 0 for a parent (s. 2.4, 2.5). + counter: u64, + /// b: the number of input bytes in the block. + block_len: u32, + /// d: every flag except ROOT, which is added only when the root is finalized. + flags: u32, +} + +impl Output { + /// This node's chaining value: it is not the root, so ROOT is not set. + fn chaining_value(&self) -> [u32; 8] { + first_8_words(&compress( + &self.input_chaining_value, + &self.block_words, + self.counter, + self.block_len, + self.flags, + )) + } + + /// One 64-byte block of root output, as 16 words. + /// + /// BLAKE3 spec s. 2.6: "This is done by repeating the root compression -- that is, the very + /// last call to the compression function, which sets the ROOT flag -- with incrementing + /// values of the counter t." and "the first root compression always uses the counter value + /// t = 0 ... After the first root compression, as long as more output bytes are needed, t is + /// incremented by 1, and the root compression is repeated on otherwise the same inputs." + /// + /// Mutants note: `self.flags` never contains ROOT (it is added only here), so `| ROOT` and + /// `^ ROOT` are equivalent; a surviving `|`/`^` swap is an equivalent mutant. + /// + /// So `self.counter` is not used here: it is always 0 for a root (the root is either the only + /// chunk, index 0, or a parent node, counter 0), and the output block counter replaces it. + fn root_output_block(&self, output_block_counter: u64) -> [u32; 16] { + compress( + &self.input_chaining_value, + &self.block_words, + output_block_counter, + self.block_len, + self.flags | ROOT, + ) + } +} + +/// BLAKE3 spec s. 2.5: a parent node's single compression. +fn parent_output( + left_child_cv: &[u32; 8], + right_child_cv: &[u32; 8], + key_words: &[u32; 8], + flags: u32, +) -> Output { + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + // "The input chaining value h0 ... h7 is the key words k0 ... k7." + input_chaining_value.copy_from_slice(key_words); + + let mut block_words = Secret::<[u32; 16]>::new(); + // "The message words m0 ... m7 are the chaining value of the left child, and the message + // words m8 ... m15 are the chaining value of the right child." + block_words[..8].copy_from_slice(left_child_cv); + block_words[8..].copy_from_slice(right_child_cv); + + Output { + input_chaining_value, + block_words, + // "The counter t for parent nodes is always 0." + counter: 0, + // "The number of bytes b for parent nodes is always 64." + block_len: BLOCK_LEN as u32, + // "Parent nodes set the PARENT flag." (ROOT is added by root_output_block if this + // parent turns out to be the root.) `flags` carries the mode flag of s. 2.3. + // Mutants note: `flags` is only ever a mode flag, never PARENT, so `|` and `^` are + // equivalent here; a surviving swap is an equivalent mutant. + flags: PARENT | flags, + } +} + +/* --------------------------------------------------------------------------------------------- + * Chunk state (s. 2.4, 5.1.1) + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 spec s. 5.1.1: "The chunk state contains the 32-byte CV of the previous block and a +/// 64-byte input buffer for the next block, and typically also the compression function +/// parameters t and d." +#[derive(Clone)] +struct ChunkState { + /// The CV of the previous block; the key words before the first block is compressed. + chaining_value: Secret<[u32; 8]>, + /// t: "the chunk index, i.e., 0 for all blocks in the first chunk, 1 for all blocks in the + /// second chunk, and so on" (s. 2.4). + chunk_counter: u64, + /// The block not yet compressed. Only the first `block_len` bytes are meaningful. + block: Secret<[u8; BLOCK_LEN]>, + /// Number of meaningful bytes in `block`, 0..=64. + block_len: u8, + /// Number of blocks of this chunk already compressed, 0..=15 (the 16th block of a full chunk + /// is never compressed early, see `update`). + blocks_compressed: u8, + /// The mode flag of s. 2.3 (0, KEYED_HASH or a DERIVE_KEY_* flag). + flags: u32, +} + +impl ChunkState { + fn new(key_words: &[u32; 8], chunk_counter: u64, flags: u32) -> Self { + let mut chaining_value = Secret::<[u32; 8]>::new(); + // s. 2.4: "The input chaining value h0 ... h7 for the first block of each chunk is + // composed of the key words k0 ... k7." + chaining_value.copy_from_slice(key_words); + Self { + chaining_value, + chunk_counter, + block: Secret::new(), + block_len: 0, + blocks_compressed: 0, + flags, + } + } + + /// Number of input bytes absorbed into this chunk so far. + fn len(&self) -> usize { + BLOCK_LEN * self.blocks_compressed as usize + self.block_len as usize + } + + /// s. 2.4: "The first block of each chunk sets the CHUNK_START flag". + fn start_flag(&self) -> u32 { + if self.blocks_compressed == 0 { CHUNK_START } else { 0 } + } + + /// Absorbs `input`, which the caller guarantees fits in this chunk (`len() + input.len() <= + /// CHUNK_LEN`). + /// + /// s. 5.1.1: "Input bytes from the caller are copied into the buffer until it is full. Then the + /// buffer is compressed together with the previous CV, using the truncated compression + /// function. The output CV overwrites the previous CV, and the buffer is cleared. ... the + /// chunk state waits to compress the next block until both the buffer is full and the caller + /// has supplied more input." + fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // The buffer is full and more input is here, so this block is not the chunk's last: + // no CHUNK_END, no ROOT. + if self.block_len as usize == BLOCK_LEN { + let mut block_words = Secret::<[u32; 16]>::new(); + // s. 2.4: "Each block is parsed in little-endian order into message words m0 ... + // m15 and compressed." + words_from_le_bytes(&*self.block, &mut block_words); + let cv = first_8_words(&compress( + &self.chaining_value, + &block_words, + // s. 2.4: "The counter t for each block is the chunk index" + self.chunk_counter, + // s. 2.4: "The block length b is the number of input bytes in each block, + // i.e., 64 for all blocks except the last block of the last chunk" + BLOCK_LEN as u32, + // Mutants note: the mode flag and CHUNK_START are distinct bits, so `|` and + // `^` are equivalent here; a surviving swap is an equivalent mutant. + self.flags | self.start_flag(), + )); + // s. 2.4: "The input chaining value for subsequent blocks in each chunk is the + // output of the truncated compression function for the previous block." + self.chaining_value.copy_from_slice(&cv); + self.blocks_compressed += 1; + // "the buffer is cleared": only the length is reset. The stale bytes are never + // read, because `output` zero-pads everything past `block_len` itself. + self.block_len = 0; + } + + // Copy input bytes into the block buffer. + let block_len = self.block_len as usize; + let take = *min(&(BLOCK_LEN - block_len), &input.len()); + self.block[block_len..block_len + take].copy_from_slice(&input[..take]); + self.block_len += take as u8; + input = &input[take..]; + } + } + + /// The chunk's last compression, held back as an [`Output`]. + fn output(&self) -> Output { + // s. 2.4: "The last block of the last chunk may be shorter, but not empty, unless the + // entire input is empty. If necessary, the last block is padded with zeros to be 64 + // bytes." + let block_len = self.block_len as usize; + let mut padded = Secret::<[u8; BLOCK_LEN]>::new(); + padded[..block_len].copy_from_slice(&self.block[..block_len]); + let mut block_words = Secret::<[u32; 16]>::new(); + words_from_le_bytes(&*padded, &mut block_words); + + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + input_chaining_value.copy_from_slice(&*self.chaining_value); + + Output { + input_chaining_value, + block_words, + counter: self.chunk_counter, + block_len: self.block_len as u32, + // s. 2.4: "the last block of each chunk sets the CHUNK_END flag. If a chunk contains + // only one block, that block sets both CHUNK_START and CHUNK_END." ROOT is added by + // root_output_block if this chunk turns out to be the root. + // Mutants note: the mode flag, CHUNK_START and CHUNK_END are distinct bits, so `|` and + // `^` are equivalent here; surviving swaps are equivalent mutants. + flags: self.flags | self.start_flag() | CHUNK_END, + } + } +} + +/* --------------------------------------------------------------------------------------------- + * The incremental hasher: chunk state + CV stack (s. 5.1) + * ------------------------------------------------------------------------------------------- */ + +/// Length of the chunk-state-and-CV-stack part of a serialized state; see +/// [`Blake3Core::write_state`]. +const CORE_STATE_LEN: usize = 32 + 8 + BLOCK_LEN + 1 + 1 + 1 + MAX_DEPTH * 32; + +/// BLAKE3 spec s. 5.1: "An incremental implementation of BLAKE3 has two major components: the +/// state of the current chunk and a stack of subtree chaining values (the "CV stack")." +/// +/// Shared by all three modes, which differ only in `key_words` and `flags` (s. 2.3). +#[derive(Clone)] +struct Blake3Core { + chunk_state: ChunkState, + /// k0 ... k7 (s. 2.3). + key_words: Secret<[u32; 8]>, + /// "Space for 54 subtree chaining values: 2^54 * CHUNK_LEN = 2^64" (reference impl). + cv_stack: Secret<[[u32; 8]; MAX_DEPTH]>, + cv_stack_len: u8, + /// The mode flag of s. 2.3, set "for every compression". + flags: u32, +} + +impl Blake3Core { + fn new(key_words: &[u32; 8], flags: u32) -> Self { + let mut kw = Secret::<[u32; 8]>::new(); + kw.copy_from_slice(key_words); + Self { + chunk_state: ChunkState::new(key_words, 0, flags), + key_words: kw, + cv_stack: Secret::new(), + cv_stack_len: 0, + flags, + } + } + + fn push_stack(&mut self, cv: &[u32; 8]) { + // The stack cannot overflow for any input BLAKE3 accepts: s. 2.1 caps the input at + // ell < 2^64 bytes, i.e. fewer than 2^54 chunks, and s. 5.4 derives the depth bound of 54 + // from exactly that. Reaching this would need 2^64 bytes of input, so a debug assertion + // is enough; in release the index below would panic rather than corrupt memory. + debug_assert!((self.cv_stack_len as usize) < MAX_DEPTH, "BLAKE3 input exceeds 2^64 bytes"); + self.cv_stack[self.cv_stack_len as usize] = *cv; + self.cv_stack_len += 1; + } + + fn pop_stack(&mut self) -> [u32; 8] { + self.cv_stack_len -= 1; + self.cv_stack[self.cv_stack_len as usize] + } + + /// BLAKE3 spec s. 5.1.2, Listing 1: "For each trailing 0-bit in the new total number of chunks, + /// pop a CV off the stack, and merge it with the new CV we were about to add. Finally, push the + /// fully merged result onto the stack." + fn add_chunk_chaining_value(&mut self, mut new_cv: [u32; 8], mut total_chunks: u64) { + // "The entries in the CV stack behave like the 1-bits in the binary representation of the + // total number of chunks so far." Each trailing 0-bit of the new total is a completed + // subtree whose left child is on top of the stack and whose right child is `new_cv`. + while total_chunks & 1 == 0 { + let left_child_cv = self.pop_stack(); + new_cv = parent_output(&left_child_cv, &new_cv, &self.key_words, self.flags) + .chaining_value(); + total_chunks >>= 1; + } + self.push_stack(&new_cv); + } + + fn update(&mut self, mut input: &[u8]) { + while !input.is_empty() { + // s. 5.1.1: "chunk CVs are added only after the caller supplies at least 1 byte for the + // following chunk" -- so a full chunk is finalized here, where more input is known to + // follow, and it is therefore not the root (s. 5.1.2: "we know that none of these + // chunks or parent nodes is the root of the tree"). + if self.chunk_state.len() == CHUNK_LEN { + let chunk_cv = self.chunk_state.output().chaining_value(); + let total_chunks = self.chunk_state.chunk_counter + 1; + self.add_chunk_chaining_value(chunk_cv, total_chunks); + self.chunk_state = ChunkState::new(&self.key_words, total_chunks, self.flags); + } + + // Absorb as much as fits in the current chunk. + let take = *min(&(CHUNK_LEN - self.chunk_state.len()), &input.len()); + self.chunk_state.update(&input[..take]); + input = &input[take..]; + } + } + + /// The root node's held-back compression. + /// + /// BLAKE3 spec s. 5.1.2: "Once we reach the end of the input, we can use a simpler algorithm + /// to compute the root hash. Finalize the CV of the current chunk, which may be incomplete or + /// empty. Then, merge that CV with every CV currently in the stack, regardless of the number + /// of chunks so far. Set the ROOT flag for the last parent node to be merged. Or if there are + /// no CVs in the stack, and thus no parents to merge, set the ROOT flag for chunk + /// finalization instead." (ROOT itself is applied by [`Output::root_output_block`].) + fn root_output(&self) -> Output { + let mut output = self.chunk_state.output(); + let mut parent_nodes_remaining = self.cv_stack_len as usize; + while parent_nodes_remaining > 0 { + parent_nodes_remaining -= 1; + output = parent_output( + &self.cv_stack[parent_nodes_remaining], + &output.chaining_value(), + &self.key_words, + self.flags, + ); + } + output + } + + /// Serializes the chunk state and CV stack (not `key_words` or `flags`, which each public type + /// handles itself) into `CORE_STATE_LEN` bytes. All integers little-endian. + /// + /// ```text + /// [0 .. 32) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 + /// [32 .. 40) chunk_counter u64 + /// [40 .. 104) block [u8; 64], zero past block_len + /// [104 .. 105) block_len u8 (0..=64) + /// [105 .. 106) blocks_compressed u8 (0..=15) + /// [106 .. 107) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) + /// [107 .. 1835) cv_stack 54 x [u32; 8], zero past cv_stack_len + /// ``` + /// + /// The chunk chaining value is omitted while no block of the chunk has been compressed, + /// because it then *is* the key words (s. 2.4: "The input chaining value h0 ... h7 for the + /// first block of each chunk is composed of the key words k0 ... k7"), and in keyed_hash mode + /// those are the key. `read_state` restores it from the key words. Stale bytes past + /// `block_len` and stale CVs past `cv_stack_len` are not written either. + fn write_state(&self, out: &mut [u8]) { + debug_assert_eq!(out.len(), CORE_STATE_LEN); + let cs = &self.chunk_state; + if cs.blocks_compressed != 0 { + for (i, word) in cs.chaining_value.iter().enumerate() { + out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + } + out[32..40].copy_from_slice(&cs.chunk_counter.to_le_bytes()); + let block_len = cs.block_len as usize; + out[40..40 + block_len].copy_from_slice(&cs.block[..block_len]); + out[104] = cs.block_len; + out[105] = cs.blocks_compressed; + out[106] = self.cv_stack_len; + for (j, cv) in self.cv_stack[..self.cv_stack_len as usize].iter().enumerate() { + for (i, word) in cv.iter().enumerate() { + let at = 107 + j * 32 + i * 4; + out[at..at + 4].copy_from_slice(&word.to_le_bytes()); + } + } + } + + /// The inverse of [`Self::write_state`], given the key words and mode flag. + /// + /// Rejects with [`SuspendableError::InvalidData`] any state the hasher could not have reached, + /// where resuming it would index out of bounds or underflow the stack: + /// * `block_len > 64`: the buffer is one block; + /// * `blocks_compressed > 15`: a chunk is at most 16 blocks and its last block is never + /// compressed before the chunk is finalized (s. 5.1.1); + /// * `cv_stack_len > 54`: s. 5.4's maximum tree depth; + /// * `chunk_counter >= 2^54`: s. 2.1 caps the input below 2^64 bytes, i.e. 2^54 chunks. Without + /// this, a counter with 54 one-bits whose next chunk adds a 55th would overflow the stack; + /// * `cv_stack_len != chunk_counter.count_ones()`: s. 5.1.2, "The entries in the CV stack + /// behave like the 1-bits in the binary representation of the total number of chunks so + /// far". This is what guarantees `add_chunk_chaining_value` never pops an empty stack; + /// * `blocks_compressed > 0 && block_len == 0`: s. 5.1.1, a block is compressed only once "the + /// caller has supplied more input", so a compression is always followed by at least one + /// buffered byte. (Resuming such a state would finish the chunk on an empty block without + /// CHUNK_START, which no input produces.) + /// + /// While `blocks_compressed == 0` the chunk chaining value is taken from `key_words` + /// unconditionally; the serialized bytes for it are never read. + fn read_state( + input: &[u8], + key_words: &[u32; 8], + flags: u32, + ) -> Result { + debug_assert_eq!(input.len(), CORE_STATE_LEN); + let chunk_counter = u64_from_le(&input[32..40]); + let block_len = input[104]; + let blocks_compressed = input[105]; + let cv_stack_len = input[106]; + + if block_len as usize > BLOCK_LEN + || blocks_compressed > 15 + || cv_stack_len as usize > MAX_DEPTH + || chunk_counter >= (1u64 << MAX_DEPTH) + || chunk_counter.count_ones() != cv_stack_len as u32 + || (blocks_compressed > 0 && block_len == 0) + { + return Err(SuspendableError::InvalidData); + } + + let mut core = Self::new(key_words, flags); + let cs = &mut core.chunk_state; + if blocks_compressed != 0 { + words_from_le_bytes(&input[0..32], &mut cs.chaining_value); + } + // else: ChunkState::new already set the chaining value to the key words. + cs.chunk_counter = chunk_counter; + cs.block[..block_len as usize].copy_from_slice(&input[40..40 + block_len as usize]); + cs.block_len = block_len; + cs.blocks_compressed = blocks_compressed; + + for j in 0..cv_stack_len as usize { + words_from_le_bytes(&input[107 + j * 32..107 + j * 32 + 32], &mut core.cv_stack[j]); + } + core.cv_stack_len = cv_stack_len; + Ok(core) + } +} + +/* --------------------------------------------------------------------------------------------- + * Partial-bit policy + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 is byte-oriented: s. 2.1 "BLAKE3 supports input of any byte length 0 <= ell < 2^64", and +/// the block length parameter b of s. 2.2 counts bytes. There is no defined encoding of a partial +/// final byte, so the partial-bit entry points accept only `num_bits == 0`. +/// +/// This is the convention of this library's other byte-oriented XOFs, Ascon-XOF128/CXOF128: +/// `InvalidLength` for `num_bits > 7` (the trait's rule), `InvalidInput` for `1..=7`, and 0 is the +/// whole-byte call. KMACXOF differs: it returns `InvalidLength` for every non-zero `num_bits`. +fn check_partial_bits(num_bits: usize) -> Result<(), HashError> { + if num_bits > 7 { + return Err(HashError::InvalidLength("num_bits must be in the range [0,7]")); + } + if num_bits != 0 { + return Err(HashError::InvalidInput( + "BLAKE3 is byte-oriented: a partial final byte (num_bits in 1..=7) cannot be hashed", + )); + } + Ok(()) +} + +/* --------------------------------------------------------------------------------------------- + * BLAKE3: hash mode + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 in its `hash` mode (spec s. 2.3: "k0 ... k7 are the constants IV0 ... IV7, and no +/// additional flags are set"), usable as a [`Hash`] and as a [`XOF`]. +/// +/// As a [`Hash`] it produces the default 32-byte output; as a [`XOF`] any length, and "Shorter +/// outputs are prefixes of longer ones" (s. 2.6). For the keyed mode see [`BLAKE3KeyedHash`]; for +/// key derivation, [`BLAKE3DeriveKey`]. +/// +/// See the [crate-level documentation](crate) for usage. +#[derive(Clone)] +pub struct BLAKE3 { + core: Blake3Core, +} + +impl BLAKE3 { + /// A new BLAKE3 in `hash` mode, ready for use. + pub fn new() -> Self { + // s. 2.3: "In the hash mode, k0 ... k7 are the constants IV0 ... IV7, and no additional + // flags are set." + Self { core: Blake3Core::new(&IV, 0) } + } + + /// The engine in `derive_key` mode, for [`BLAKE3DeriveKey`] only: a `BLAKE3` value is + /// [`Suspendable`], and a derive_key state would hold the key material. + fn derive_key_mode(context: &[u8]) -> Self { + // s. 2.3: "First the context string is hashed, with k0 ... k7 set to the constants + // IV0 ... IV7, and the DERIVE_KEY_CONTEXT flag set for every compression." + let mut context_hasher = Blake3Core::new(&IV, DERIVE_KEY_CONTEXT); + context_hasher.update(context); + + // "Then the key material is hashed, with k0 ... k7 set to the first 8 output words of the + // first stage, and the DERIVE_KEY_MATERIAL flag set for every compression." The first + // 8 output words are the first 8 words of root output block 0 (s. 2.6). + let first_stage_output = context_hasher.root_output().root_output_block(0); + Self { core: Blake3Core::new(&first_8_words(&first_stage_output), DERIVE_KEY_MATERIAL) } + } +} + +impl Default for BLAKE3 { + fn default() -> Self { + Self::new() + } +} + +impl HashOneShot for BLAKE3 {} + +impl Hash for BLAKE3 { + /// BLAKE3 spec s. 2.2: the message block is 512 bits. + fn block_bitlen(&self) -> usize { + BLOCK_LEN * 8 + } + + /// The default output length, 32 bytes (s. 2.1). This is nominal: BLAKE3 does not bind the + /// output length into the computation (s. 2.6), so use the [`XOF`] methods for other lengths. + fn output_len(&self) -> usize { + OUT_LEN + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut output = vec![0u8; OUT_LEN]; + self.do_final_out(&mut output); + output + } + + /// Writes `min(output.len(), 32)` bytes of the default-length hash and zeroizes the rest of + /// `output`. To fill a longer buffer with extended output, use [`XOF::xof_out`] or + /// [`XOFSqueezer::do_output_out`]. + fn do_final_out(self, output: &mut [u8]) -> usize { + output.fill(0); + let n = *min(&output.len(), &OUT_LEN); + self.into_squeezer().do_output_out(&mut output[..n]) + } + + /// Only `num_bits == 0` is accepted (BLAKE3 is byte-oriented; s. 2.1): it is identical to + /// [`Hash::do_final`] and `partial_byte` is ignored. `num_bits` in `1..=7` returns + /// [`HashError::InvalidInput`], and larger values [`HashError::InvalidLength`]. + fn do_final_partial_bits( + self, + _partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + check_partial_bits(num_bits)?; + Ok(self.do_final()) + } + + /// As [`Hash::do_final_partial_bits`]; `output` is zeroized first, so a rejected call leaves + /// it all zeros. + fn do_final_partial_bits_out( + self, + _partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + output.fill(0); + check_partial_bits(num_bits)?; + Ok(self.do_final_out(output)) + } +} + +/// The absorb-then-squeeze rule is a compile error: +/// +/// ```compile_fail +/// use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +/// use bouncycastle_blake3::BLAKE3; +/// +/// let mut blake3 = BLAKE3::new(); +/// blake3.do_update(b"abc"); +/// let mut out = blake3.into_squeezer(); +/// let _ = out.do_output(32); +/// blake3.do_update(b"more"); // `blake3` was moved by into_squeezer() +/// ``` +impl XOF for BLAKE3 { + type Squeezer = BLAKE3Squeezer; + + fn into_squeezer(self) -> Self::Squeezer { + BLAKE3Squeezer::new(self.core.root_output()) + } + + /// Only `num_bits == 0` is accepted; see [`Hash::do_final_partial_bits`] on [`BLAKE3`]. + fn into_squeezer_partial_bits( + self, + _partial_byte: u8, + num_bits: usize, + ) -> Result { + check_partial_bits(num_bits)?; + Ok(self.into_squeezer()) + } +} + +/// Length in bytes of the serialized state of [`BLAKE3`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// ```text +/// [0 .. 4) flags u32: always 0 (hash mode) +/// [4 .. 36) key_words [u32; 8]: always IV0 ... IV7 +/// [36 .. 1871) the chunk state and CV stack: +/// [36 .. 68) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 +/// [68 .. 76) chunk_counter u64 (< 2^54) +/// [76 .. 140) block [u8; 64], zero past block_len +/// [140 .. 141) block_len u8 (0..=64) +/// [141 .. 142) blocks_compressed u8 (0..=15) +/// [142 .. 143) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) +/// [143 .. 1871) cv_stack 54 x [u32; 8], zero past cv_stack_len +/// ``` +pub const SUSPENDED_BLAKE3_STATE_LEN: usize = 3 + 4 + 32 + CORE_STATE_LEN; + +impl Suspendable for BLAKE3 { + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly SUSPENDED_BLAKE3_STATE_LEN - 3 bytes. + let out: &mut [u8; SUSPENDED_BLAKE3_STATE_LEN - 3] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + out[0..4].copy_from_slice(&self.core.flags.to_le_bytes()); + for (i, word) in self.core.key_words.iter().enumerate() { + out[4 + i * 4..4 + i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + self.core.write_state(&mut out[36..]); + + out_to_return + } + + /// Rejects with [`SuspendableError::InvalidData`]: a flags word other than 0 or key words + /// other than IV0 ... IV7 (only the `hash` mode is suspendable, s. 2.3); and the chunk-state + /// and CV-stack inconsistencies listed in the layout above. + fn from_suspended( + serialized_state: [u8; SUSPENDED_BLAKE3_STATE_LEN], + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly SUSPENDED_BLAKE3_STATE_LEN - 3 + // bytes. + let input: &[u8; SUSPENDED_BLAKE3_STATE_LEN - 3] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + let flags = u32_from_le(&input[0..4]); + // s. 2.3: hash mode has "no additional flags" and k = IV. A keyed_hash or derive_key + // state is never in this format (those modes are BLAKE3KeyedHash and BLAKE3DeriveKey). + let mut iv_bytes = [0u8; 32]; + for (bytes, word) in iv_bytes.as_chunks_mut::<4>().0.iter_mut().zip(IV.iter()) { + bytes.copy_from_slice(&word.to_le_bytes()); + } + if flags != 0 || !ct::ct_eq_bytes(&input[4..36], &iv_bytes) { + return Err(SuspendableError::InvalidData); + } + + let core = Blake3Core::read_state(&input[36..], &IV, 0)?; + Ok(Self { core }) + } +} + +/* --------------------------------------------------------------------------------------------- + * The output phase + * ------------------------------------------------------------------------------------------- */ + +/// The squeezing half of [`BLAKE3`]: what [`XOF::into_squeezer`] hands back. +/// +/// It holds the root node's compression inputs and produces "outputs of any byte length +/// 0 <= ell < 2^64 ... by repeating the root compression ... with incrementing values of the +/// counter t" (s. 2.6). Successive reads continue one stream. +#[derive(Clone)] +pub struct BLAKE3Squeezer { + /// The root compression (without ROOT, which `root_output_block` adds). + root: Output, + /// t for the next root compression. + output_block_counter: u64, + /// The current 64-byte output block. Derive_key output is a key, hence the `Secret`. + buf: Secret<[u8; BLOCK_LEN]>, + /// Bytes of `buf` already handed out, 0..=64; 64 means a new block is needed. + buf_pos: u8, +} + +impl BLAKE3Squeezer { + fn new(root: Output) -> Self { + Self { root, output_block_counter: 0, buf: Secret::new(), buf_pos: BLOCK_LEN as u8 } + } +} + +impl XOFSqueezer for BLAKE3Squeezer { + fn do_output(&mut self, num_bytes: usize) -> Vec { + let mut out = vec![0u8; num_bytes]; + self.do_output_out(&mut out); + out + } + + fn do_output_out(&mut self, output: &mut [u8]) -> usize { + // Zeroized first per the house rule, although every byte is then overwritten. + output.fill(0); + let mut written = 0; + while written < output.len() { + if self.buf_pos as usize == BLOCK_LEN { + // s. 2.6: "When building the output, BLAKE3 uses the full output of the + // compression function (cf. s. 2.2). Each 16-word output is encoded as 64 bytes in + // little-endian order." + let words = self.root.root_output_block(self.output_block_counter); + for (i, word) in words.iter().enumerate() { + self.buf[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + // "as long as more output bytes are needed, t is incremented by 1". This cannot + // overflow: the counter starts at most at MAX_OUTPUT_BLOCKS (from_suspended + // enforces it), and 2^64 - 2^58 further blocks would be far past ell < 2^64. + self.output_block_counter += 1; + self.buf_pos = 0; + } + // "If the target output length is not a multiple of 64, the final compression output + // is truncated" -- the rest of the block stays buffered for the next read. + let pos = self.buf_pos as usize; + let take = *min(&(BLOCK_LEN - pos), &(output.len() - written)); + output[written..written + take].copy_from_slice(&self.buf[pos..pos + take]); + self.buf_pos += take as u8; + written += take; + } + written + } +} + +/// Length in bytes of the serialized state of [`BLAKE3Squeezer`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian): +/// ```text +/// [0 .. 32) root input_chaining_value [u32; 8] +/// [32 .. 96) root block_words [u32; 16] +/// [96 .. 100) root block_len u32 +/// [100 .. 104) root flags u32 (without ROOT; never KEYED_HASH) +/// [104 .. 112) output_block_counter u64 +/// [112 .. 176) output buffer [u8; 64] +/// [176 .. 177) buffer position u8 (0..=64) +/// ``` +/// The root's own counter is not stored: s. 2.6, "the first root compression always uses the +/// counter value t = 0", and the output block counter takes its place. The stream position is +/// `64 * (output_block_counter - 1) + buffer position` once a block has been produced. +pub const SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN: usize = 3 + 177; + +/// The most output blocks a squeezer can have produced: s. 2.6 bounds the output at +/// "0 <= ell < 2^64" bytes, which is 2^58 blocks of 64 bytes. +const MAX_OUTPUT_BLOCKS: u64 = 1 << 58; + +/// A long output stream can be paused and resumed. Only the squeezer of [`BLAKE3`] implements +/// this; the keyed squeezer does not, see [`BLAKE3KeyedSqueezer`]. +impl Suspendable for BLAKE3Squeezer { + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly 177 bytes. + let out: &mut [u8; 177] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + for (i, word) in self.root.input_chaining_value.iter().enumerate() { + out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + for (i, word) in self.root.block_words.iter().enumerate() { + out[32 + i * 4..32 + i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + out[96..100].copy_from_slice(&self.root.block_len.to_le_bytes()); + out[100..104].copy_from_slice(&self.root.flags.to_le_bytes()); + out[104..112].copy_from_slice(&self.output_block_counter.to_le_bytes()); + out[112..176].copy_from_slice(&*self.buf); + out[176] = self.buf_pos; + + out_to_return + } + + /// Rejects with [`SuspendableError::InvalidData`] a buffer position past 64, and root flags + /// that no [`BLAKE3`] root can have: the stored flags are a node-type part (CHUNK_START, + /// CHUNK_END, PARENT) and no mode flag. In particular KEYED_HASH and DERIVE_KEY_MATERIAL are + /// rejected (those streams are never suspendable, see [`BLAKE3KeyedSqueezer`] and + /// [`BLAKE3DeriveKeySqueezer`]), as is ROOT (it is added per output block, never stored). + /// Also rejected, as states no squeezer reaches: + /// * a root `block_len` above 64: a root is a chunk's last block (b <= 64, s. 2.4) or a + /// parent node (b = 64, s. 2.5); + /// * `output_block_counter == 0` with a position below 64: a fresh squeezer starts at position + /// 64 (no block computed), and computing a block makes the counter at least 1, so such a + /// state would hand out buffer bytes that were never computed; + /// * `output_block_counter` above 2^58: s. 2.6 allows "outputs of any byte + /// length 0 <= ell < 2^64", at most 2^58 blocks, and a counter near `u64::MAX` would wrap + /// and repeat output already handed out. + fn from_suspended( + serialized_state: [u8; SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN], + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly 177 bytes. + let input: &[u8; 177] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + let buf_pos = input[176]; + let flags = u32_from_le(&input[100..104]); + // Mutants note: the three node-type flags are distinct bits, so `|` and `^` build the same + // mask; surviving swaps here are equivalent mutants. + let mode_flags = flags & !(CHUNK_START | CHUNK_END | PARENT); + let block_len = u32_from_le(&input[96..100]); + let output_block_counter = u64_from_le(&input[104..112]); + if buf_pos as usize > BLOCK_LEN + || mode_flags != 0 + || block_len as usize > BLOCK_LEN + || (output_block_counter == 0 && (buf_pos as usize) < BLOCK_LEN) + || output_block_counter > MAX_OUTPUT_BLOCKS + { + return Err(SuspendableError::InvalidData); + } + + let mut input_chaining_value = Secret::<[u32; 8]>::new(); + words_from_le_bytes(&input[0..32], &mut input_chaining_value); + let mut block_words = Secret::<[u32; 16]>::new(); + words_from_le_bytes(&input[32..96], &mut block_words); + let root = Output { input_chaining_value, block_words, counter: 0, block_len, flags }; + let mut buf = Secret::<[u8; BLOCK_LEN]>::new(); + buf.copy_from_slice(&input[112..176]); + + Ok(Self { root, output_block_counter, buf, buf_pos }) + } +} + +/* --------------------------------------------------------------------------------------------- + * BLAKE3DeriveKey: derive_key mode + * ------------------------------------------------------------------------------------------- */ + +/// BLAKE3 in its `derive_key` mode (spec s. 2.3), usable as a [`Hash`] and as a [`XOF`]: the +/// context string is hashed into a context key, the key material is then fed through +/// [`Hash::do_update`], and the output is the derived key. +/// +/// The context string "should be hardcoded, globally unique, and application-specific" and "should +/// not contain variable data, like salts, IDs, or the current time" (s. 6.2). +/// +/// It deliberately does **not** implement [`Suspendable`]: its state holds up to 64 bytes of the +/// key material verbatim, and values derived from all of it, while [`Suspendable`] forbids a +/// serialized private key. Its squeezer, [`BLAKE3DeriveKeySqueezer`], is not suspendable either. +/// +/// ```compile_fail +/// use bouncycastle_blake3::BLAKE3DeriveKey; +/// use bouncycastle_core::traits::{Hash, Suspendable}; +/// +/// let mut kdf = BLAKE3DeriveKey::new(b"example.com 2019-12-25 16:18:03 session tokens v1"); +/// kdf.do_update(&[0x0bu8; 32]); +/// let _ = kdf.suspend(); // not Suspendable +/// ``` +#[derive(Clone)] +pub struct BLAKE3DeriveKey(BLAKE3); + +impl BLAKE3DeriveKey { + /// A new `derive_key` instance for the given context string. + pub fn new(context: &[u8]) -> Self { + Self(BLAKE3::derive_key_mode(context)) + } + + /// One-shot: the 32-byte derived key for the key material `data`. [`HashOneShot`] is for + /// hashes that take no parameters; this one takes its context string at construction. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} + +impl Hash for BLAKE3DeriveKey { + fn block_bitlen(&self) -> usize { + self.0.block_bitlen() + } + + /// The default output length, 32 bytes; use the [`XOF`] methods for other lengths. + fn output_len(&self) -> usize { + self.0.output_len() + } + + fn do_update(&mut self, data: &[u8]) { + self.0.do_update(data) + } + + fn do_final(self) -> Vec { + self.0.do_final() + } + + fn do_final_out(self, output: &mut [u8]) -> usize { + self.0.do_final_out(output) + } + + /// As on [`BLAKE3`]: only `num_bits == 0` is accepted. + fn do_final_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result, HashError> { + self.0.do_final_partial_bits(partial_byte, num_bits) + } + + fn do_final_partial_bits_out( + self, + partial_byte: u8, + num_bits: usize, + output: &mut [u8], + ) -> Result { + self.0.do_final_partial_bits_out(partial_byte, num_bits, output) + } +} + +impl XOF for BLAKE3DeriveKey { + type Squeezer = BLAKE3DeriveKeySqueezer; + + fn into_squeezer(self) -> Self::Squeezer { + BLAKE3DeriveKeySqueezer(self.0.into_squeezer()) + } + + fn into_squeezer_partial_bits( + self, + partial_byte: u8, + num_bits: usize, + ) -> Result { + Ok(BLAKE3DeriveKeySqueezer(self.0.into_squeezer_partial_bits(partial_byte, num_bits)?)) + } +} + +/// The squeezing half of [`BLAKE3DeriveKey`]. Like [`BLAKE3KeyedSqueezer`] it is deliberately not +/// [`Suspendable`]: the root compression it holds is the last block of key material, or a parent +/// node over chaining values derived from it. +#[derive(Clone)] +pub struct BLAKE3DeriveKeySqueezer(BLAKE3Squeezer); + +impl XOFSqueezer for BLAKE3DeriveKeySqueezer { + fn do_output(&mut self, num_bytes: usize) -> Vec { + self.0.do_output(num_bytes) + } + + fn do_output_out(&mut self, output: &mut [u8]) -> usize { + self.0.do_output_out(output) + } +} + +/* --------------------------------------------------------------------------------------------- + * BLAKE3KeyedHash: keyed_hash mode + * ------------------------------------------------------------------------------------------- */ + +/// A BLAKE3 `keyed_hash` key (spec s. 2.3): the key words k0 ... k7, "parsed in little-endian +/// order from the 256-bit key given by the caller". +/// +/// Construction is [`FromAnyKeyMaterial`]: the material must be exactly 32 bytes, tagged +/// [`KeyType::MACKey`] (or [`KeyType::Zeroized`]). An empty key is refused like any other length; +/// unkeyed BLAKE3 is [`BLAKE3`]. The workers come from [`MacGenerationKey`] and +/// [`MacVerificationKey`], each holding its own copy of the key words. +#[derive(Clone)] +pub struct BLAKE3KeyedHashKey { + key_words: Secret<[u32; 8]>, +} + +impl BLAKE3KeyedHashKey { + fn from_key_material_checked( + key: &dyn KeyMaterialTrait, + allow_weak_key: bool, + ) -> Result { + // Same stance as HMAC and KMAC: an all-zero key is `Zeroized` rather than `MACKey`, and is + // let through so callers are not forced to re-tag it. + if !(key.key_type() == KeyType::MACKey || key.key_type() == KeyType::Zeroized) { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "Key type must be a MAC key.", + ))); + } + // Strength before length, so that an under-strength key is reported as such whatever its + // length (the shared key-construction suite relies on this). + if !allow_weak_key && key.security_strength() < SecurityStrength::_128bit { + Err(KeyMaterialError::SecurityStrength( + "BLAKE3KeyedHashKey: the key's security strength is below 128 bits", + ))? + } + // s. 2.3: "a 256-bit key". BLAKE3 defines no way to use a key of any other length. + if key.key_len() != KEY_LEN { + return Err(MACError::InvalidLength("BLAKE3 keyed_hash requires a 32-byte key")); + } + let mut key_words = Secret::<[u32; 8]>::new(); + words_from_le_bytes(key.ref_to_bytes(), &mut key_words); + Ok(Self { key_words }) + } +} + +impl FromAnyKeyMaterial for BLAKE3KeyedHashKey { + /// # Errors + /// [`MACError::KeyMaterialError`] if the material is not tagged [`KeyType::MACKey`] or + /// [`KeyType::Zeroized`], or is tagged below 128-bit security; [`MACError::InvalidLength`] if + /// it is not exactly 32 bytes. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, false) + } + + /// As [`from_key_material`](Self::from_key_material) without the strength check. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, true) + } + + /// Draws a 32-byte key from `rng`, tagged [`KeyType::MACKey`] at the RNG's strength. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below 128 bits. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial256::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 KMACKey::generate does. + // Mutants note: `<` and `<=` pick the same value when the two are equal, so a + // surviving `<=` is an equivalent mutant. + 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 BLAKE3KeyedHashKey { + type Generator<'k> + = BLAKE3KeyedHash + where + Self: 'k; + + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + // s. 2.3: "In the keyed_hash mode, k0 ... k7 are parsed ... from the 256-bit key ..., and + // the KEYED_HASH flag is set for every compression." + BLAKE3KeyedHash { core: Blake3Core::new(&self.key_words, KEYED_HASH) } + } +} + +impl MacVerificationKey for BLAKE3KeyedHashKey { + type Verifier<'k> + = BLAKE3KeyedHash + where + Self: 'k; + + fn create_verifier<'k>(&'k self) -> Self::Verifier<'k> { + self.create_generator() + } +} + +/// BLAKE3 in its `keyed_hash` mode (spec s. 2.3), as a [`MAC`]: the worker a +/// [`BLAKE3KeyedHashKey`] hands out. +/// +/// The [`MAC`] methods produce the default 32-byte tag; [`BLAKE3KeyedHash::into_squeezer`] gives +/// keyed output of any length (BLAKE3 as a PRF or XOF). +#[derive(Clone)] +pub struct BLAKE3KeyedHash { + core: Blake3Core, +} + +impl BLAKE3KeyedHash { + /// Ends the input and returns a squeezer for keyed output of any length (s. 2.6). Its first + /// 32 bytes are the [`MAC`] tag. No minimum length applies: choosing a safe output length is + /// the caller's responsibility. + pub fn into_squeezer(self) -> BLAKE3KeyedSqueezer { + BLAKE3KeyedSqueezer(BLAKE3Squeezer::new(self.core.root_output())) + } +} + +impl MAC for BLAKE3KeyedHash { + fn output_len(&self) -> usize { + OUT_LEN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + fn do_update(&mut self, data: &[u8]) { + self.core.update(data); + } + + fn do_final(self) -> Vec { + let mut out = vec![0u8; OUT_LEN]; + self.into_squeezer().do_output_out(&mut out); + out + } + + /// Zeroizes `out` (also on error), then writes `min(out.len(), 32)` bytes of the tag (a + /// shorter buffer truncates it) and returns that count. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`MIN_MAC_OUTPUT_LEN`]. + fn do_final_out(self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < MIN_MAC_OUTPUT_LEN { + return Err(MACError::InvalidLength( + "BLAKE3KeyedHash: output buffer is shorter than MIN_MAC_OUTPUT_LEN", + )); + } + let n = *min(&out.len(), &OUT_LEN); + Ok(self.into_squeezer().do_output_out(&mut out[..n])) + } + + /// Compares in constant time against the full 32-byte tag; a truncated or extended `mac` does + /// not verify. + fn do_verify_final(self, mac: &[u8]) -> bool { + if mac.len() != OUT_LEN { + return false; + } + let computed = self.do_final(); + ct::ct_eq_bytes(&computed, mac) + } +} + +/// Length in bytes of the serialized state of [`BLAKE3KeyedHash`]. +/// +/// Layout (after the 3-byte library version header; all integers little-endian). The key words and +/// the KEYED_HASH flag are not stored: the key is re-supplied to +/// [`SuspendableKeyed::from_suspended`] and the flag is implied by the type. +/// ```text +/// [0 .. 32) chunk chaining_value [u32; 8], all zero when blocks_compressed == 0 +/// (it is then the key itself) +/// [32 .. 40) chunk_counter u64 (< 2^54) +/// [40 .. 104) block [u8; 64], zero past block_len +/// [104 .. 105) block_len u8 (0..=64) +/// [105 .. 106) blocks_compressed u8 (0..=15) +/// [106 .. 107) cv_stack_len u8 (0..=54, == chunk_counter.count_ones()) +/// [107 .. 1835) cv_stack 54 x [u32; 8], zero past cv_stack_len +/// ``` +pub const SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN: usize = 3 + CORE_STATE_LEN; + +/// The key is never written into the serialized state; resume takes the key object. A wrong key +/// is not detected; it gives a different tag, except in the case below. +/// +/// # 🚨 Security Considerations 🚨 +/// While the absorbed prefix P is 65..=1024 bytes (one block of the first chunk compressed, CV +/// stack empty), the state finishes the tag of P || X without the key for every X that keeps the +/// total at or under 1024 bytes. Protect a suspended keyed state like the key. +// The key words are stored nowhere: the chunk chaining value is zeroed while it is still the key +// (s. 2.4) and rebuilt on resume. The worker owns its key words, so `'k` is unused. +impl<'k> SuspendableKeyed<'k, SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN> for BLAKE3KeyedHash { + type Key = BLAKE3KeyedHashKey; + + fn suspend(self) -> [u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN]; + // infallible: add_lib_ver returns a slice of exactly CORE_STATE_LEN bytes. + let out: &mut [u8; CORE_STATE_LEN] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + self.core.write_state(out); + out_to_return + } + + fn from_suspended( + state: [u8; SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN], + key: &'k Self::Key, + ) -> Result { + // infallible: check_lib_ver returns a slice of exactly CORE_STATE_LEN bytes. + let input: &[u8; CORE_STATE_LEN] = check_lib_ver(&state, None)?.try_into().unwrap(); + let core = Blake3Core::read_state(input, &key.key_words, KEYED_HASH)?; + Ok(Self { core }) + } +} + +/// The squeezing half of [`BLAKE3KeyedHash`]: what [`BLAKE3KeyedHash::into_squeezer`] hands back. +/// +/// It deliberately does **not** implement [`Suspendable`]: for every multi-chunk message (s. 2.5) +/// and every message of at most 64 bytes (s. 2.4) the root's input chaining value is the key words +/// themselves, so its state would contain the key. +/// +/// There is no route to suspending it: +/// +/// ```compile_fail +/// use bouncycastle_blake3::BLAKE3KeyedHashKey; +/// use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +/// use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +/// use bouncycastle_core::traits::Suspendable; +/// +/// let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +/// let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +/// let _ = key.create_generator().into_squeezer().suspend(); // not Suspendable +/// ``` +#[derive(Clone)] +pub struct BLAKE3KeyedSqueezer(BLAKE3Squeezer); + +impl XOFSqueezer for BLAKE3KeyedSqueezer { + fn do_output(&mut self, num_bytes: usize) -> Vec { + self.0.do_output(num_bytes) + } + + fn do_output_out(&mut self, output: &mut [u8]) -> usize { + self.0.do_output_out(output) + } +} diff --git a/crypto/blake3/src/compress.rs b/crypto/blake3/src/compress.rs new file mode 100644 index 000000000..81d264b8b --- /dev/null +++ b/crypto/blake3/src/compress.rs @@ -0,0 +1,159 @@ +//! The BLAKE3 compression function and its constants (BLAKE3 spec s. 2.2). +//! +//! Everything in this module is a direct transcription of s. 2.2 and Tables 1-3. The structure +//! (`g`, `round`, `permute`, `compress`) follows the reference implementation that the spec's +//! s. 5.1 describes "as an aid to implementers". +//! +//! There are no data-dependent branches or table lookups here: the only indexing is by the +//! fixed `MSG_PERMUTATION` schedule and fixed state positions. + +/// BLAKE3 spec s. 2.4: "Each block is parsed in little-endian order into message words m0 ... m15", +/// so a block is 16 x 4 = 64 bytes. +pub(crate) const BLOCK_LEN: usize = 64; + +/// BLAKE3 spec s. 2.1: "The input of BLAKE3 is split into contiguous chunks of 1024 bytes". +pub(crate) const CHUNK_LEN: usize = 1024; + +/// BLAKE3 spec s. 2.2, Table 1: "Constants IV0 ... IV7 used in the initialization of BLAKE3." +/// ("The IV0 ... IV7 constants are the same as in BLAKE2s".) +pub(crate) const IV: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; + +/// BLAKE3 spec s. 2.2, Table 2: "Permutational key schedule for BLAKE3's keyed permutation." +/// Entry `i` names the message word that moves to position `i`. +const MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]; + +// BLAKE3 spec s. 2.2, Table 3: "Admissible values for input d in the BLAKE3 compression function." +// "The value of d is the sum of all the flags defined for a given compression." + +/// Table 3: CHUNK_START = 2^0. +/// Mutants note: `1 << 0` and `1 >> 0` are both 1, so a surviving `<<`/`>>` swap here is an +/// equivalent mutant. +pub(crate) const CHUNK_START: u32 = 1 << 0; +/// Table 3: CHUNK_END = 2^1. +pub(crate) const CHUNK_END: u32 = 1 << 1; +/// Table 3: PARENT = 2^2. +pub(crate) const PARENT: u32 = 1 << 2; +/// Table 3: ROOT = 2^3. +pub(crate) const ROOT: u32 = 1 << 3; +/// Table 3: KEYED_HASH = 2^4. +pub(crate) const KEYED_HASH: u32 = 1 << 4; +/// Table 3: DERIVE_KEY_CONTEXT = 2^5. +pub(crate) const DERIVE_KEY_CONTEXT: u32 = 1 << 5; +/// Table 3: DERIVE_KEY_MATERIAL = 2^6. +pub(crate) const DERIVE_KEY_MATERIAL: u32 = 1 << 6; + +/// BLAKE3 spec s. 2.2: the "quarter-round" G_i(a, b, c, d), where `mx` = m_{2i+0} and +/// `my` = m_{2i+1}. "+ denote addition modulo 2^32, >>> denote bitwise right rotation". +#[inline] +fn g(v: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) { + // a <- a + b + m_{2i+0} + v[a] = v[a].wrapping_add(v[b]).wrapping_add(mx); + // d <- (d ^ a) >>> 16 + v[d] = (v[d] ^ v[a]).rotate_right(16); + // c <- c + d + v[c] = v[c].wrapping_add(v[d]); + // b <- (b ^ c) >>> 12 + v[b] = (v[b] ^ v[c]).rotate_right(12); + // a <- a + b + m_{2i+1} + v[a] = v[a].wrapping_add(v[b]).wrapping_add(my); + // d <- (d ^ a) >>> 8 + v[d] = (v[d] ^ v[a]).rotate_right(8); + // c <- c + d + v[c] = v[c].wrapping_add(v[d]); + // b <- (b ^ c) >>> 7 + v[b] = (v[b] ^ v[c]).rotate_right(7); +} + +/// BLAKE3 spec s. 2.2: "A round consists of the following 8 operations ... That is, a round +/// applies a G function to each column of the 4 x 4 state in parallel, and then to each of the +/// diagonals in parallel." G_i takes message words m_{2i} and m_{2i+1}. +#[inline] +fn round(v: &mut [u32; 16], m: &[u32; 16]) { + // G0(v0, v4, v8, v12) G1(v1, v5, v9, v13) G2(v2, v6, v10, v14) G3(v3, v7, v11, v15) + g(v, 0, 4, 8, 12, m[0], m[1]); + g(v, 1, 5, 9, 13, m[2], m[3]); + g(v, 2, 6, 10, 14, m[4], m[5]); + g(v, 3, 7, 11, 15, m[6], m[7]); + // G4(v0, v5, v10, v15) G5(v1, v6, v11, v12) G6(v2, v7, v8, v13) G7(v3, v4, v9, v14) + g(v, 0, 5, 10, 15, m[8], m[9]); + g(v, 1, 6, 11, 12, m[10], m[11]); + g(v, 2, 7, 8, 13, m[12], m[13]); + g(v, 3, 4, 9, 14, m[14], m[15]); +} + +/// BLAKE3 spec s. 2.2: "After each round (except the last one where it would be useless), the +/// message words are permuted according to Table 2." +#[inline] +fn permute(m: &mut [u32; 16]) { + let mut permuted = [0u32; 16]; + for i in 0..16 { + permuted[i] = m[MSG_PERMUTATION[i]]; + } + *m = permuted; +} + +/// The BLAKE3 compression function of spec s. 2.2. +/// +/// Inputs, in the spec's words: "The input chaining value, h0 ... h7 (256 bits)", "The message +/// block, m0 ... m15 (512 bits)", "A 64-bit counter, t = t0, t1, with t0 the lower order word and +/// t1 the higher order word", "The number of input bytes in the block, b (32 bits)", and "A set +/// of domain separation bit flags, d (32 bits)". +/// +/// Returns the full 16-word output h'0 ... h'15. "The output of the compression function is most +/// often truncated to produce 256-bit chaining values" -- callers that want a chaining value take +/// the first 8 words; the extendable output of s. 2.6 uses all 16. +/// +/// The working state and the permuted message copy are ordinary stack locals and are not +/// zeroized; the persistent chaining values and buffers live in `Secret`s in the callers. +pub(crate) fn compress(h: &[u32; 8], m: &[u32; 16], t: u64, b: u32, d: u32) -> [u32; 16] { + // t = t0, t1: "t0 the lower order word and t1 the higher order word". + let t0 = t as u32; + let t1 = (t >> 32) as u32; + + // "The compression function initializes its 16-word internal state v0 ... v15 as follows:" + // v0 v1 v2 v3 h0 h1 h2 h3 + // v4 v5 v6 v7 <- h4 h5 h6 h7 + // v8 v9 v10 v11 IV0 IV1 IV2 IV3 + // v12 v13 v14 v15 t0 t1 b d + #[rustfmt::skip] + let mut v: [u32; 16] = [ + h[0], h[1], h[2], h[3], + h[4], h[5], h[6], h[7], + IV[0], IV[1], IV[2], IV[3], + t0, t1, b, d, + ]; + + // "The compression function applies a 7-round keyed permutation v' = E(m, v) to the state + // v0 ... v15, keyed by the message m0 ... m15." The message is permuted between rounds but + // not after the seventh ("except the last one where it would be useless"). + let mut m = *m; + for _ in 0..6 { + round(&mut v, &m); + permute(&mut m); + } + round(&mut v, &m); + + // "The output of the compression function h'0 ... h'15 is defined as:" + // h'i <- v'i ^ v'(i+8) for i = 0..7 + // h'(i+8) <- v'(i+8) ^ hi for i = 0..7 + // The left column must read v'(i+8) before the right column overwrites it. + for i in 0..8 { + v[i] ^= v[i + 8]; + v[i + 8] ^= h[i]; + } + v +} + +/// BLAKE3 spec s. 2.4: "Each block is parsed in little-endian order into message words +/// m0 ... m15". Also used for the 32-byte key of s. 2.3 ("k0 ... k7 are parsed in little-endian +/// order from the 256-bit key"). +#[inline] +pub(crate) fn words_from_le_bytes(bytes: &[u8], words: &mut [u32; N]) { + debug_assert_eq!(bytes.len(), 4 * N); + let (chunks, _remainder) = bytes.as_chunks::<4>(); + for (word, four_bytes) in words.iter_mut().zip(chunks) { + *word = u32::from_le_bytes(*four_bytes); + } +} diff --git a/crypto/blake3/src/lib.rs b/crypto/blake3/src/lib.rs new file mode 100644 index 000000000..ff6577cf5 --- /dev/null +++ b/crypto/blake3/src/lib.rs @@ -0,0 +1,175 @@ +//! Implements the BLAKE3 cryptographic hash function -- the `hash`, `keyed_hash` and `derive_key` +//! modes, with extendable output -- as per the BLAKE3 specification (J. O'Connor, J.-P. Aumasson, +//! S. Neves, Z. Wilcox-O'Hearn, "BLAKE3: one function, fast everywhere", version 20211102173700). +//! +//! * [`BLAKE3`]: the `hash` mode, as a [`Hash`] and a [`XOF`]. +//! * [`BLAKE3DeriveKey`]: the `derive_key` mode, as a [`Hash`] and a [`XOF`]. +//! * [`BLAKE3KeyedHashKey`]: the `keyed_hash` key, which hands out [`BLAKE3KeyedHash`] workers, a +//! [`MAC`] with extended keyed output via [`BLAKE3KeyedHash::into_squeezer`]. +//! +//! # Usage Examples +//! ``` +//! use bouncycastle_core::traits::{Hash, HashOneShot, XOF, XOFSqueezer}; +//! use bouncycastle_blake3::{BLAKE3, BLAKE3DeriveKey}; +//! +//! // One-shot: the official test vector for the empty input begins af1349b9. +//! assert_eq!(BLAKE3::hash(b"")[..4], [0xaf, 0x13, 0x49, 0xb9]); +//! +//! // Streaming, then reading any amount of output; the first 32 bytes are the hash. +//! let mut blake3 = BLAKE3::new(); +//! blake3.do_update(b"ab"); +//! blake3.do_update(b"c"); +//! let mut squeezer = blake3.into_squeezer(); +//! let long = [squeezer.do_output(40), squeezer.do_output(60)].concat(); +//! assert_eq!(long, BLAKE3::new().xof(b"abc", 100)); +//! assert_eq!(long[..32], BLAKE3::hash(b"abc")); +//! +//! // derive_key: a hardcoded context string, then the key material as input. +//! const CONTEXT: &[u8] = b"example.com 2019-12-25 16:18:03 session tokens v1"; +//! let derived: Vec = BLAKE3DeriveKey::new(CONTEXT).xof(&[0x0bu8; 32], 64); +//! ``` +//! +//! The `keyed_hash` mode takes a 32-byte key tagged [`KeyType::MACKey`]: +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{ +//! FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, +//! }; +//! use bouncycastle_core::traits::{MAC, XOFSqueezer}; +//! use bouncycastle_blake3::BLAKE3KeyedHashKey; +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +//! +//! let tag: Vec = key.mac(b"message"); +//! assert!(key.verify(b"message", &tag)); // constant-time comparison +//! +//! // Streaming, then extended keyed output: its first 32 bytes are the tag. +//! let mut mac = key.create_generator(); +//! mac.do_update(b"mess"); +//! mac.do_update(b"age"); +//! assert_eq!(mac.into_squeezer().do_output(64)[..32], tag); +//! ``` +//! +//! # Memory Usage +//! +//! No heap memory is used except by the `Vec`-returning convenience methods. +//! +//! | Object | Size (bytes) | +//! |-------------------------------------------|--------------| +//! | [`BLAKE3`], [`BLAKE3DeriveKey`] | 1880 | +//! | [`BLAKE3KeyedHashKey`] | 32 | +//! | [`BLAKE3KeyedHash`] | 1880 | +//! | [`BLAKE3Squeezer`] | 192 | +//! | [`BLAKE3KeyedSqueezer`], [`BLAKE3DeriveKeySqueezer`] | 192 | +//! | [`SUSPENDED_BLAKE3_STATE_LEN`] | 1874 | +//! | [`SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN`] | 1838 | +//! | [`SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN`] | 180 | +//! +//! 1728 bytes of a hasher are the chaining-value stack, sized for the full 2^64-byte input range +//! (spec s. 5.4). +//! +//! # 🚨 Security Considerations 🚨 +//! +//! * Every type here reports [`SecurityStrength::_128bit`] (see the [`Algorithm`] impls); the +//! 256-bit key and longer outputs do not raise it. +//! * Shorter outputs are prefixes of longer ones (s. 2.6): the output length is not bound into the +//! result. +//! * `keyed_hash` is a MAC and PRF by design (s. 6.1); its key must be 32 uniformly random bytes. +//! The [`MAC`] trait will not truncate a tag below [`MIN_MAC_OUTPUT_LEN`] bytes; +//! [`BLAKE3KeyedHash::into_squeezer`] has no floor. +//! * The `derive_key` context string must be hardcoded, globally unique and application-specific, +//! never variable data (s. 6.2); see [`BLAKE3DeriveKey`]. +//! * BLAKE3 is byte-oriented: a partial final byte is refused. +//! * State and buffers are zeroized on drop; there are no data-dependent branches or table +//! lookups. +//! * A suspended `keyed_hash` state can finish some messages without the key (see +//! [`BLAKE3KeyedHash`]'s [`SuspendableKeyed`] impl); protect it like the key. `derive_key` is not +//! suspendable at all, since its state holds key material. +//! +//! # Suspending and resuming execution +//! +//! [`BLAKE3`] and [`BLAKE3Squeezer`] implement [`Suspendable`]; [`BLAKE3KeyedHash`] implements +//! [`SuspendableKeyed`], which never writes the key into the state and takes the key object on +//! resume. [`BLAKE3DeriveKey`], [`BLAKE3DeriveKeySqueezer`] and [`BLAKE3KeyedSqueezer`] cannot be +//! suspended, because their states can contain key material. +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +//! use bouncycastle_core::traits::{Hash, HashOneShot, MAC, Suspendable, SuspendableKeyed}; +//! use bouncycastle_blake3::{BLAKE3, BLAKE3KeyedHash, BLAKE3KeyedHashKey}; +//! +//! let mut blake3 = BLAKE3::new(); +//! blake3.do_update(b"part one, "); +//! let mut resumed = BLAKE3::from_suspended(blake3.suspend()).unwrap(); +//! resumed.do_update(b"part two"); +//! assert_eq!(resumed.do_final(), BLAKE3::hash(b"part one, part two")); +//! +//! let material = KeyMaterial256::from_bytes_as_type(&[0x42u8; 32], KeyType::MACKey).unwrap(); +//! let key = BLAKE3KeyedHashKey::from_key_material(&material).unwrap(); +//! let mut mac = key.create_generator(); +//! mac.do_update(b"part one, "); +//! let mut resumed = BLAKE3KeyedHash::from_suspended(mac.suspend(), &key).unwrap(); +//! resumed.do_update(b"part two"); +//! assert_eq!(resumed.do_final(), key.mac(b"part one, part two")); +//! ``` + +// todo #![no_std] +// waiting for the no_std refactor that removes the `-> Vec` from core::traits::Hash + +#![forbid(unsafe_code)] +#![forbid(missing_docs)] + +mod blake3; +mod compress; + +pub use self::blake3::{ + BLAKE3, BLAKE3DeriveKey, BLAKE3DeriveKeySqueezer, BLAKE3KeyedHash, BLAKE3KeyedHashKey, + BLAKE3KeyedSqueezer, BLAKE3Squeezer, SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, + SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, SUSPENDED_BLAKE3_STATE_LEN, +}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, HashAlgParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::KeyType; +#[allow(unused_imports)] +use bouncycastle_core::traits::{ + Hash, MAC, MIN_MAC_OUTPUT_LEN, Suspendable, SuspendableKeyed, XOF, +}; + +/// Algorithm name string for BLAKE3 (`hash` mode), as used by the factories and CLI. +pub const BLAKE3_NAME: &str = "BLAKE3"; + +/// Algorithm name string for BLAKE3 in `derive_key` mode. +pub const BLAKE3_DERIVE_KEY_NAME: &str = "BLAKE3-DeriveKey"; + +/// Algorithm name string for BLAKE3 in `keyed_hash` mode, as used by the factories and CLI. +pub const BLAKE3_KEYED_HASH_NAME: &str = "BLAKE3-KeyedHash"; + +/// BLAKE3 spec s. 4.1: "BLAKE3 targets 128-bit security for all of its security goals." +impl Algorithm for BLAKE3 { + const ALG_NAME: &'static str = BLAKE3_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// As for [`BLAKE3`]: the 256-bit key is "an extra defense layer" (s. 4.1), not a higher target. +impl Algorithm for BLAKE3KeyedHash { + const ALG_NAME: &'static str = BLAKE3_KEYED_HASH_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// As for [`BLAKE3`]. +impl Algorithm for BLAKE3DeriveKey { + const ALG_NAME: &'static str = BLAKE3_DERIVE_KEY_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// BLAKE3 spec s. 2.1: the default output is 32 bytes ("The chaining value of the root node, +/// encoded as 32 bytes ... is the default-length BLAKE3 hash"); s. 2.2: 64-byte message blocks. +/// BLAKE3 has no assigned OID. +impl HashAlgParams for BLAKE3 { + const OUTPUT_LEN: usize = 32; + const BLOCK_LEN: usize = 64; +} diff --git a/crypto/blake3/tests/blake3_tests.rs b/crypto/blake3/tests/blake3_tests.rs new file mode 100644 index 000000000..b46328f99 --- /dev/null +++ b/crypto/blake3/tests/blake3_tests.rs @@ -0,0 +1,570 @@ +//! Tests for [`BLAKE3`] (`hash` mode), [`BLAKE3DeriveKey`] (`derive_key` mode) and +//! [`BLAKE3Squeezer`]. +//! +//! Vector sources: the official BLAKE3 test vectors (see `vectors/mod.rs`). bc-test-data has no +//! BLAKE3 data, and Wycheproof has no BLAKE vectors (C2SP/wycheproof testvectors_v1 has no blake* +//! file), so the "against bc-test-data and wycheproof" requirement is met vacuously. + +mod vectors; + +#[cfg(test)] +mod blake3_tests { + use super::vectors::{CASES, CONTEXT, OUTPUT_LEN, input}; + use bouncycastle_blake3::*; + use bouncycastle_core::errors::{HashError, SuspendableError}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, Hash, HashAlgParams, HashOneShot, Suspendable, XOF, XOFSqueezer, + }; + use bouncycastle_core_test_framework::hash::TestFrameworkHash; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + use bouncycastle_core_test_framework::xof::TestFrameworkXOF; + use bouncycastle_hex as hex; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + /* ----------------------------------- official vectors ----------------------------------- */ + + /// Every `hash` case: the 32-byte default output, the full 131 bytes through every XOF entry + /// point, and split reads across the 64-byte output block boundary. + #[test] + fn official_vectors_hash() { + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.hash); + assert_eq!(expected.len(), OUTPUT_LEN); + let len = case.input_len; + + assert_eq!(BLAKE3::hash(&msg), expected[..32], "hash / len {len}"); + assert_eq!(BLAKE3::new().xof(&msg, OUTPUT_LEN), expected, "xof / len {len}"); + + let mut b = BLAKE3::new(); + b.do_update(&msg); + assert_eq!(b.into_squeezer().do_output(OUTPUT_LEN), expected, "squeeze / len {len}"); + + // Split reads: inside block 0, exactly at the block boundary, and across it. + for split in [1usize, 31, 63, 64, 65, 128, 130] { + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + let first = s.do_output(split); + let second = s.do_output(OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split} / len {len}"); + } + } + } + + /// Every `derive_key` case, via [`BLAKE3DeriveKey`], at 32 and 131 bytes. + #[test] + fn official_vectors_derive_key() { + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.derive_key); + let len = case.input_len; + + assert_eq!(BLAKE3DeriveKey::new(CONTEXT).hash(&msg), expected[..32], "len {len}"); + assert_eq!(BLAKE3DeriveKey::new(CONTEXT).xof(&msg, OUTPUT_LEN), expected, "len {len}"); + } + } + + /// The derive_key context goes through the full tree, so a context longer than one chunk must + /// differ from one that is merely a prefix of it, and the two modes must differ from `hash`. + #[test] + fn derive_key_context_is_bound() { + let long_context = input(2049); + let a = BLAKE3DeriveKey::new(&long_context).hash(b"km"); + let b = BLAKE3DeriveKey::new(&long_context[..2048]).hash(b"km"); + assert_ne!(a, b); + assert_ne!(BLAKE3DeriveKey::new(b"").hash(b"km"), BLAKE3::hash(b"km")); + } + + /* --------------------------------- core-test-framework ---------------------------------- */ + + #[test] + fn core_test_framework_hash() { + let tf = TestFrameworkHash { enable_partial_byte_tests: false }; + for case in CASES.iter().filter(|c| c.input_len <= 8193) { + let msg = input(case.input_len); + tf.test_hash::(&msg, &h(case.hash)[..32]); + } + for len in [0usize, 1, 64, 1024, 1025, 4097] { + tf.test_hash_output_buffers(BLAKE3::new, &input(len)); + tf.test_hash_output_buffers(|| BLAKE3DeriveKey::new(CONTEXT), &input(len)); + } + } + + #[test] + fn core_test_framework_xof() { + let tf = TestFrameworkXOF { + enable_partial_byte_tests: false, + do_final_binds_output_length: false, + }; + for case in CASES.iter().filter(|c| c.input_len <= 8193) { + let msg = input(case.input_len); + tf.test_xof(BLAKE3::new, &msg, &h(case.hash)); + tf.test_xof(|| BLAKE3DeriveKey::new(CONTEXT), &msg, &h(case.derive_key)); + } + } + + /* ------------------------------------- behaviour ---------------------------------------- */ + + /// Streaming in odd piece sizes equals the one-shot, at every vector length around the chunk + /// and tree boundaries. + #[test] + fn streaming_piece_sizes() { + for case in CASES.iter().filter(|c| c.input_len >= 1023 && c.input_len <= 8193) { + let msg = input(case.input_len); + let expected = h(case.hash); + for piece in [1usize, 63, 64, 65, 1023, 1024, 1025] { + let mut b = BLAKE3::new(); + for chunk in msg.chunks(piece) { + b.do_update(chunk); + } + assert_eq!( + b.into_squeezer().do_output(OUTPUT_LEN), + expected, + "len {} piece {piece}", + case.input_len + ); + } + } + } + + /// Empty updates are no-ops, wherever they fall. + #[test] + fn empty_updates() { + let msg = input(1025); + let mut b = BLAKE3::new(); + b.do_update(&[]); + b.do_update(&msg[..1024]); + b.do_update(&[]); + b.do_update(&msg[1024..]); + b.do_update(&[]); + assert_eq!(b.do_final(), BLAKE3::hash(&msg)); + } + + /// A clone continues independently, including from a state with a non-empty CV stack. + #[test] + fn clone_forks() { + let msg = input(3073); + let mut a = BLAKE3::new(); + a.do_update(&msg[..2500]); + let mut b = a.clone(); + a.do_update(&msg[2500..]); + b.do_update(&msg[2500..]); + let expected = BLAKE3::hash(&msg); + assert_eq!(a.clone().do_final(), expected); + assert_eq!(b.do_final(), expected); + a.do_update(b"x"); + assert_ne!(a.do_final(), expected); + + // A squeezer clone continues the same stream. + let mut s = BLAKE3::new().into_squeezer(); + let _ = s.do_output(70); + let mut t = s.clone(); + assert_eq!(s.do_output(100), t.do_output(100)); + } + + /// The partial-bit policy: 0 is the whole-byte call, 1..=7 is InvalidInput (byte-oriented), + /// and 8+ is InvalidLength. An `_out` call zeroizes the buffer first, even when it is + /// rejected. + #[test] + fn partial_bits_policy() { + check_partial_bits(BLAKE3::new); + check_partial_bits(|| BLAKE3DeriveKey::new(CONTEXT)); + } + + /// The partial-bit rules for one BLAKE3 mode, over "abc". + fn check_partial_bits(new: impl Fn() -> X) { + let expected = { + let mut x = new(); + x.do_update(b"abc"); + x.do_final() + }; + let mk = || { + let mut b = new(); + b.do_update(b"abc"); + b + }; + + for partial_byte in [0x00u8, 0xFF] { + assert_eq!(mk().do_final_partial_bits(partial_byte, 0).unwrap(), expected); + let mut out = [0xAAu8; 40]; + assert_eq!(mk().do_final_partial_bits_out(partial_byte, 0, &mut out).unwrap(), 32); + assert_eq!(out[..32], expected); + assert_eq!(out[32..], [0u8; 8]); + let mut s = mk().into_squeezer_partial_bits(partial_byte, 0).unwrap(); + assert_eq!(s.do_output(32), expected); + } + + for num_bits in 1..=7usize { + assert!(matches!( + mk().do_final_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + let mut out = [0xA5u8; 32]; + assert!(matches!( + mk().do_final_partial_bits_out(0x80, num_bits, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert_eq!(out, [0u8; 32], "a rejected call must leave the buffer zeroized"); + assert!(matches!( + mk().into_squeezer_partial_bits(0x80, num_bits), + Err(HashError::InvalidInput(_)) + )); + } + + // The frameworks' partial-bit tests are disabled for BLAKE3, so their > 7 values are pinned + // here for every entry point. + for num_bits in [8usize, 9, 15, 16, 64, usize::MAX] { + assert!(matches!( + mk().do_final_partial_bits(0, num_bits), + Err(HashError::InvalidLength(_)) + )); + let mut out = [0xA5u8; 32]; + assert!(matches!( + mk().do_final_partial_bits_out(0, num_bits, &mut out), + Err(HashError::InvalidLength(_)) + )); + assert_eq!(out, [0u8; 32], "a rejected call must leave the buffer zeroized"); + assert!(matches!( + mk().into_squeezer_partial_bits(0, num_bits), + Err(HashError::InvalidLength(_)) + )); + } + } + + /// `Hash::do_final_out` writes at most 32 bytes and zeroizes the rest; a zero-length buffer + /// gets nothing. + #[test] + fn hash_do_final_out_lengths() { + let expected = BLAKE3::hash(b"abc"); + let mut out = [0xAAu8; 100]; + assert_eq!(BLAKE3::hash_out(b"abc", &mut out), 32); + assert_eq!(out[..32], expected); + assert!(out[32..].iter().all(|&b| b == 0)); + assert_eq!(BLAKE3::hash_out(b"abc", &mut []), 0); + let mut out = [0u8; 5]; + assert_eq!(BLAKE3::hash_out(b"abc", &mut out), 5); + assert_eq!(out, expected[..5]); + } + + #[test] + fn constants_and_params() { + assert_eq!(BLAKE3_NAME, "BLAKE3"); + assert_eq!(BLAKE3::ALG_NAME, BLAKE3_NAME); + assert_eq!(BLAKE3::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(BLAKE3::new().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(BLAKE3::OUTPUT_LEN, 32); + assert_eq!(BLAKE3::BLOCK_LEN, 64); + assert_eq!(BLAKE3::new().output_len(), 32); + assert_eq!(BLAKE3::new().block_bitlen(), 512); + assert_eq!(BLAKE3::hash(b"abc"), BLAKE3::hash(b"abc")); + assert_eq!(SUSPENDED_BLAKE3_STATE_LEN, 1874); + assert_eq!(SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, 180); + } + + /// Prints, and pins, the `size_of` figures quoted in the crate docs' Memory Usage table. + #[test] + fn print_sizes() { + assert_eq!(size_of::(), 1880); + assert_eq!(size_of::(), 1880); + assert_eq!(size_of::(), 192); + assert_eq!(size_of::(), 192); + assert_eq!(size_of::(), 1880); + assert_eq!(size_of::(), 192); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + println!("size_of::() = {}", size_of::()); + } + + /* ------------------------------------ suspend/resume ------------------------------------ */ + + /// Suspends after `prefix` bytes of `msg`, resumes, absorbs the rest, and compares to the + /// vector. + fn round_trip(make: impl Fn() -> BLAKE3, msg: &[u8], prefix: usize, expected: &[u8]) { + let mut b = make(); + b.do_update(&msg[..prefix]); + let mut resumed = BLAKE3::from_suspended(b.suspend()).unwrap(); + resumed.do_update(&msg[prefix..]); + assert_eq!(resumed.into_squeezer().do_output(OUTPUT_LEN), expected, "prefix {prefix}"); + } + + #[test] + fn suspend_round_trip() { + // 8193 bytes: past several chunk and tree boundaries, so the CV stack is non-empty. + let case = CASES.iter().find(|c| c.input_len == 8193).unwrap(); + let msg = input(case.input_len); + for prefix in [0usize, 1, 63, 64, 65, 1023, 1024, 1025, 2048, 3000, 4096, 7000, 8193] { + round_trip(BLAKE3::new, &msg, prefix, &h(case.hash)); + } + } + + #[test] + fn core_test_framework_suspendable() { + let tf = TestFrameworkSuspendableState::new(); + let msg = input(5000); + for len in [0usize, 100, 1024, 1025, 5000] { + let mut b = BLAKE3::new(); + b.do_update(&msg[..len]); + tf.test(&b); + } + + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + tf.test(&s); + let _ = s.do_output(70); + tf.test(&s); + } + + /// A suspended squeezer resumes mid-stream, including exactly at and past a block boundary. + #[test] + fn squeezer_suspend_round_trip() { + for case in CASES.iter().filter(|c| [0usize, 64, 1025, 4097].contains(&c.input_len)) { + let msg = input(case.input_len); + let expected = h(case.hash); + for split in [0usize, 1, 63, 64, 65, 128, 131] { + let mut b = BLAKE3::new(); + b.do_update(&msg); + let mut s = b.into_squeezer(); + let first = s.do_output(split); + let mut resumed = BLAKE3Squeezer::from_suspended(s.suspend()).unwrap(); + let second = resumed.do_output(OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split}"); + } + } + } + + // Offsets into a BLAKE3 state, after the 3-byte version header (see + // SUSPENDED_BLAKE3_STATE_LEN). + const FLAGS: usize = 3; + const KEY_WORDS: usize = 3 + 4; + const CHUNK_CV: usize = 3 + 36; + const CHUNK_COUNTER: usize = 3 + 68; + const BLOCK: usize = 3 + 76; + const BLOCK_LEN_AT: usize = 3 + 140; + const BLOCKS_COMPRESSED: usize = 3 + 141; + const CV_STACK_LEN: usize = 3 + 142; + const CV_STACK: usize = 3 + 143; + + fn state_after(len: usize) -> [u8; SUSPENDED_BLAKE3_STATE_LEN] { + let mut b = BLAKE3::new(); + b.do_update(&input(len)); + b.suspend() + } + + fn rejected(state: [u8; SUSPENDED_BLAKE3_STATE_LEN]) -> bool { + matches!(BLAKE3::from_suspended(state), Err(SuspendableError::InvalidData)) + } + + /// Pins the serialized layout: the hash-mode key words are IV, the chunk CV is zero before the + /// first compression, stale bytes and stale stack entries are not written. + #[test] + fn suspend_layout() { + let iv0 = 0x6a09e667u32.to_le_bytes(); + let s = state_after(0); + assert_eq!(s[FLAGS..FLAGS + 4], [0u8; 4]); + assert_eq!(s[KEY_WORDS..KEY_WORDS + 4], iv0); + assert_eq!(s[CHUNK_CV..CHUNK_CV + 32], [0u8; 32], "chunk CV omitted while it is the key"); + assert!(s[CHUNK_COUNTER..].iter().all(|&b| b == 0)); + + // 1100 bytes: one chunk on the stack, 76 bytes into chunk 1: one block compressed. + let s = state_after(1100); + assert_eq!(s[CHUNK_COUNTER..CHUNK_COUNTER + 8], 1u64.to_le_bytes()); + assert_eq!(s[BLOCK_LEN_AT], 12); + assert_eq!(s[BLOCKS_COMPRESSED], 1); + assert_eq!(s[CV_STACK_LEN], 1); + assert_ne!(s[CHUNK_CV..CHUNK_CV + 32], [0u8; 32]); + assert_eq!(s[BLOCK..BLOCK + 12], input(1100)[1088..1100]); + assert!(s[BLOCK + 12..BLOCK + 64].iter().all(|&b| b == 0), "no stale block bytes"); + assert_ne!(s[CV_STACK..CV_STACK + 32], [0u8; 32]); + assert!(s[CV_STACK + 32..].iter().all(|&b| b == 0), "no stale stack entries"); + } + + /// Each InvalidData branch of `from_suspended`, with its boundary accepted. + #[test] + fn suspend_validation() { + let good = state_after(3000); // chunk_counter 2 (0b10), one CV on the stack + assert!(BLAKE3::from_suspended(good).is_ok()); + + // flags: only 0 (hash mode). A derive_key (64) or keyed_hash (16) state is refused, so + // neither can be resumed as a plain hash. + for flags in [1u32, 16, 32, 64, 64 | 1, 128] { + let mut s = good; + s[FLAGS..FLAGS + 4].copy_from_slice(&flags.to_le_bytes()); + assert!(rejected(s), "flags {flags}"); + } + // hash mode with key words other than IV. + let mut s = good; + s[KEY_WORDS] ^= 1; + assert!(rejected(s), "hash mode requires IV key words"); + + // block_len <= 64 + let mut s = good; + s[BLOCK_LEN_AT] = 64; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCK_LEN_AT] = 65; + assert!(rejected(s), "block_len 65"); + + // blocks_compressed <= 15 + let mut s = good; + s[BLOCKS_COMPRESSED] = 15; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCKS_COMPRESSED] = 16; + assert!(rejected(s), "blocks_compressed 16"); + + // cv_stack_len must equal chunk_counter.count_ones() + let mut s = good; + s[CV_STACK_LEN] = 2; + assert!(rejected(s), "stack longer than the counter's 1-bits"); + s[CV_STACK_LEN] = 0; + assert!(rejected(s), "stack shorter than the counter's 1-bits"); + + // cv_stack_len <= 54, and chunk_counter < 2^54. + let mut s = good; + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 54) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + assert!(BLAKE3::from_suspended(s).is_ok(), "the largest valid counter"); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 55) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 55; + assert!(rejected(s), "cv_stack_len 55"); + // 54 one-bits, but not the low 54: the next chunk would push a 55th CV. + s[CHUNK_COUNTER..CHUNK_COUNTER + 8] + .copy_from_slice(&(((1u64 << 54) - 1) << 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + assert!(rejected(s), "chunk_counter >= 2^54"); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&(1u64 << 54).to_le_bytes()); + s[CV_STACK_LEN] = 1; + assert!(rejected(s), "chunk_counter == 2^54"); + + // A compressed block is always followed by at least one buffered byte. + let mut s = good; + s[BLOCK_LEN_AT] = 1; + assert!(BLAKE3::from_suspended(s).is_ok()); + s[BLOCK_LEN_AT] = 0; + assert!(rejected(s), "blocks_compressed > 0 with an empty block"); + s[BLOCKS_COMPRESSED] = 0; + assert!(BLAKE3::from_suspended(s).is_ok(), "an empty chunk with nothing compressed"); + } + + /// While no block of the chunk is compressed, the chunk CV comes from the key words and the + /// serialized bytes are ignored. + #[test] + fn suspend_ignores_chunk_cv_before_first_compression() { + let msg = input(1030); + let mut b = BLAKE3::new(); + b.do_update(&msg[..1025]); // chunk 1 holds 1 byte, nothing compressed + let mut s = b.suspend(); + assert_eq!(s[BLOCKS_COMPRESSED], 0); + s[CHUNK_CV..CHUNK_CV + 32].fill(0x5A); + let mut resumed = BLAKE3::from_suspended(s).unwrap(); + resumed.do_update(&msg[1025..]); + assert_eq!(resumed.do_final(), BLAKE3::hash(&msg)); + } + + /// The largest valid stack resumes and keeps absorbing: completing the current chunk merges + /// all 54 entries without over- or underflowing. + #[test] + fn suspend_full_stack_resumes() { + let mut s = state_after(0); + s[CHUNK_COUNTER..CHUNK_COUNTER + 8].copy_from_slice(&((1u64 << 54) - 1).to_le_bytes()); + s[CV_STACK_LEN] = 54; + let mut b = BLAKE3::from_suspended(s).unwrap(); + b.do_update(&input(2048)); + assert_eq!(b.do_final().len(), 32); + } + + /// The 64-bit counter enters the compression as two words, t0 and t1 (spec s. 2.2). No real + /// input reaches 2^32 chunks or output blocks, so the high word is exercised by resuming a + /// squeezer at output block 2^32: with t1 dropped, that block would repeat block 0, and with + /// t0 dropped, block 2^32 + 1 would repeat block 1. + #[test] + fn squeezer_counter_high_word() { + let mut b = BLAKE3::new(); + b.do_update(b"abc"); + let s = b.into_squeezer(); + let blocks_0_1 = s.clone().do_output(128); + let mut state = s.suspend(); + // output_block_counter = 2^32, buffer position 64 (the next read computes a new block). + state[3 + 104..3 + 112].copy_from_slice(&(1u64 << 32).to_le_bytes()); + state[3 + 176] = 64; + let mut far = BLAKE3Squeezer::from_suspended(state).unwrap(); + let far_blocks = far.do_output(128); + assert_ne!(far_blocks[..64], blocks_0_1[..64], "block 2^32 must differ from block 0"); + assert_ne!(far_blocks[64..], blocks_0_1[64..], "block 2^32 + 1 must differ from block 1"); + assert_ne!(far_blocks[..64], far_blocks[64..]); + } + + #[test] + fn squeezer_suspend_validation() { + let mut b = BLAKE3::new(); + b.do_update(b"abc"); + let mut s = b.into_squeezer(); + let _ = s.do_output(10); + let state = s.suspend(); + assert_eq!(state[3 + 176], 10, "buffer position"); + + let mut bad = state; + bad[3 + 176] = 64; + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok()); + bad[3 + 176] = 65; + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + + // Root block_len <= 64 ("abc": 3). + assert_eq!(state[3 + 96..3 + 100], 3u32.to_le_bytes()); + let mut bad = state; + bad[3 + 96..3 + 100].copy_from_slice(&64u32.to_le_bytes()); + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok()); + bad[3 + 96..3 + 100].copy_from_slice(&65u32.to_le_bytes()); + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + + // output_block_counter == 0 is only valid at position 64 (nothing computed yet). + assert_eq!(state[3 + 104..3 + 112], 1u64.to_le_bytes()); + let mut bad = state; + bad[3 + 104..3 + 112].copy_from_slice(&0u64.to_le_bytes()); + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + bad[3 + 176] = 63; + assert!(matches!(BLAKE3Squeezer::from_suspended(bad), Err(SuspendableError::InvalidData))); + bad[3 + 176] = 64; + assert!(BLAKE3Squeezer::from_suspended(bad).is_ok(), "a fresh squeezer"); + let fresh = BLAKE3::new().into_squeezer().suspend(); + assert_eq!(fresh[3 + 104..3 + 112], 0u64.to_le_bytes()); + assert_eq!(fresh[3 + 176], 64); + assert!(BLAKE3Squeezer::from_suspended(fresh).is_ok()); + + // Root flags: a node-type part and no mode flag; never ROOT, KEYED_HASH or + // DERIVE_KEY_MATERIAL. + // "abc" is a one-block chunk root: CHUNK_START | CHUNK_END. + assert_eq!(state[3 + 100..3 + 104], 3u32.to_le_bytes()); + let with_flags = |flags: u32| { + let mut st = state; + st[3 + 100..3 + 104].copy_from_slice(&flags.to_le_bytes()); + BLAKE3Squeezer::from_suspended(st) + }; + for ok in [0u32, 1, 2, 3, 4] { + assert!(with_flags(ok).is_ok(), "flags {ok}"); + } + for bad in [8u32, 16, 16 | 3, 32, 64, 64 | 3, 64 | 4, 64 | 16, 64 | 8, 128, 1 << 31] { + assert!(matches!(with_flags(bad), Err(SuspendableError::InvalidData)), "flags {bad}"); + } + + // output_block_counter <= 2^58: s. 2.6 caps output below 2^64 bytes, and a counter near + // u64::MAX would wrap and repeat blocks already handed out. + let with_counter = |counter: u64| { + let mut st = state; + st[3 + 104..3 + 112].copy_from_slice(&counter.to_le_bytes()); + BLAKE3Squeezer::from_suspended(st) + }; + assert!(with_counter(1 << 58).is_ok()); + for bad in [(1u64 << 58) + 1, u64::MAX - 1, u64::MAX] { + assert!( + matches!(with_counter(bad), Err(SuspendableError::InvalidData)), + "counter {bad}" + ); + } + } +} diff --git a/crypto/blake3/tests/keyed_hash_tests.rs b/crypto/blake3/tests/keyed_hash_tests.rs new file mode 100644 index 000000000..c142023a5 --- /dev/null +++ b/crypto/blake3/tests/keyed_hash_tests.rs @@ -0,0 +1,392 @@ +//! Tests for [`BLAKE3KeyedHashKey`], the [`BLAKE3KeyedHash`] worker it hands out (`keyed_hash` +//! mode) and [`BLAKE3KeyedSqueezer`]. +//! +//! Vector sources: the official BLAKE3 test vectors (see `vectors/mod.rs`). bc-test-data has no +//! BLAKE3 data, and Wycheproof has no BLAKE vectors (C2SP/wycheproof testvectors_v1 has no blake* +//! file), so the "against bc-test-data and wycheproof" requirement is met vacuously. + +mod vectors; + +#[cfg(test)] +mod keyed_hash_tests { + use super::vectors::{CASES, KEY, OUTPUT_LEN, input}; + use bouncycastle_blake3::*; + use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; + use bouncycastle_core::key_material::{ + KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, + }; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{ + FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey, + }; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, HashOneShot, MAC, MIN_MAC_OUTPUT_LEN, SuspendableKeyed, + XOFSqueezer, + }; + use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_hex as hex; + + fn h(s: &str) -> Vec { + hex::decode(s).unwrap() + } + + fn material() -> KeyMaterial256 { + KeyMaterial256::from_bytes_as_type(KEY, KeyType::MACKey).unwrap() + } + + fn key() -> BLAKE3KeyedHashKey { + BLAKE3KeyedHashKey::from_key_material(&material()).unwrap() + } + + fn other_key() -> BLAKE3KeyedHashKey { + let m = KeyMaterial256::from_bytes_as_type(&[9u8; 32], KeyType::MACKey).unwrap(); + BLAKE3KeyedHashKey::from_key_material(&m).unwrap() + } + + /* ----------------------------------- official vectors ----------------------------------- */ + + /// Every `keyed_hash` case: the 32-byte tag through the MAC path, and the full 131 bytes (and + /// split reads) through `into_squeezer`. + #[test] + fn official_vectors_keyed_hash() { + let k = key(); + for case in CASES { + let msg = input(case.input_len); + let expected = h(case.keyed_hash); + let len = case.input_len; + + assert_eq!(k.mac(&msg), expected[..32], "len {len}"); + assert!(k.verify(&msg, &expected[..32])); + + let mut m = k.create_generator(); + m.do_update(&msg); + assert_eq!(m.into_squeezer().do_output(OUTPUT_LEN), expected, "len {len}"); + + for split in [1usize, 64, 65] { + let mut m = k.create_generator(); + m.do_update(&msg); + let mut s = m.into_squeezer(); + let first = s.do_output(split); + let mut second = vec![0xAAu8; OUTPUT_LEN - split]; + assert_eq!(s.do_output_out(&mut second), OUTPUT_LEN - split); + assert_eq!([first, second].concat(), expected, "split {split} / len {len}"); + } + } + } + + /// The default `do_output_final` of the keyed squeezer continues the stream. + #[test] + fn keyed_squeezer_do_output_final() { + let expected = h(CASES[3].keyed_hash); + let mut m = key().create_generator(); + m.do_update(&input(CASES[3].input_len)); + let mut s = m.into_squeezer(); + let first = s.do_output(10); + assert_eq!([first, s.clone().do_output_final(121)].concat(), expected); + let mut rest = vec![0u8; 121]; + assert_eq!(s.do_output_final_out(&mut rest), 121); + assert_eq!(rest, expected[10..]); + } + + /* --------------------------------- core-test-framework ---------------------------------- */ + + #[test] + fn core_test_framework_mac_key() { + let tf = TestFrameworkMAC::new(); + let k = key(); + for case in CASES.iter().filter(|c| c.input_len <= 4097) { + tf.test_mac_key(&k, &input(case.input_len), &h(case.keyed_hash)[..32]); + } + } + + /// Key type, the strength policy (including strength-before-length for a 64-byte key of no + /// strength) and `generate`. + #[test] + fn core_test_framework_from_any_key_material() { + TestFrameworkFromAnyKeyMaterial::new().test::(&material()); + } + + #[test] + fn core_test_framework_suspendable_keyed() { + let tf = TestFrameworkSuspendableKeyedState::new(); + let k = key(); + let msg = input(5000); + for len in [0usize, 1, 64, 65, 1024, 1025, 5000] { + let mut m = k.create_generator(); + m.do_update(&msg[..len]); + tf.test(&m, &k); + } + } + + /* ------------------------------------- MAC behaviour ------------------------------------ */ + + /// Output buffers: a short buffer truncates down to MIN_MAC_OUTPUT_LEN, below that is an + /// error, a long buffer gets 32 bytes and zeros. + #[test] + fn mac_output_buffers() { + assert_eq!(MIN_MAC_OUTPUT_LEN, 4); + let k = key(); + let tag = k.mac(b"abc"); + assert_eq!(k.create_generator().output_len(), 32); + + for len in [4usize, 5, 31, 32] { + let mut out = vec![0xAAu8; len]; + assert_eq!(k.mac_out(b"abc", &mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + + let mut m = k.create_generator(); + m.do_update(b"abc"); + let mut out = vec![0xAAu8; len]; + assert_eq!(m.do_final_out(&mut out).unwrap(), len); + assert_eq!(out, tag[..len]); + } + + // Below the floor: an error, and the buffer still comes back zeroized. + for len in [0usize, 1, 3] { + let mut out = vec![0xA5u8; len]; + assert!(matches!(k.mac_out(b"abc", &mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0), "key mac_out / len {len}"); + + let mut out = vec![0xA5u8; len]; + assert!(matches!( + k.create_generator().mac_out(b"abc", &mut out), + Err(MACError::InvalidLength(_)) + )); + assert!(out.iter().all(|&b| b == 0), "worker mac_out / len {len}"); + + let mut out = vec![0xA5u8; len]; + let mut m = k.create_generator(); + m.do_update(b"abc"); + assert!(matches!(m.do_final_out(&mut out), Err(MACError::InvalidLength(_)))); + assert!(out.iter().all(|&b| b == 0), "do_final_out / len {len}"); + } + + let mut out = [0xAAu8; 50]; + assert_eq!(k.create_generator().mac_out(b"abc", &mut out).unwrap(), 32); + assert_eq!(out[..32], tag); + assert_eq!(out[32..], [0u8; 18]); + } + + /// Verification needs the exact, full-length tag. + #[test] + fn verify_exact_match() { + let k = key(); + let tag = k.mac(b"abc"); + assert!(k.verify(b"abc", &tag)); + assert!(!k.verify(b"abd", &tag)); + assert!(!k.verify(b"abc", &tag[..31]), "truncated tag"); + assert!(!k.verify(b"abc", &[&tag[..], &[0u8]].concat()), "extended tag"); + let mut flipped = tag.clone(); + flipped[31] ^= 1; + assert!(!k.verify(b"abc", &flipped)); + + let mut m = k.create_verifier(); + m.do_update(b"abc"); + assert!(m.clone().do_verify_final(&tag)); + assert!(!m.do_verify_final(&tag[..16])); + + // The worker's own one-shot. + assert!(k.create_verifier().verify(b"abc", &tag)); + assert!(!k.create_verifier().verify(b"abd", &tag)); + assert!(!k.create_verifier().verify(b"abc", &tag[..31])); + } + + /// The keyed mode differs from the hash mode, and from other keys. + #[test] + fn keyed_differs() { + let a = key().mac(b"abc"); + assert_ne!(a, other_key().mac(b"abc")); + assert_ne!(a, BLAKE3::hash(b"abc")); + } + + /// The BLAKE3-specific construction rule the shared suite cannot know: exactly 32 bytes + /// (s. 2.3), whatever the strength. A Zeroized (all-zero) key is a MAC key, though a weak one. + #[test] + fn key_length_rules() { + for len in [0usize, 1, 16, 31, 33, 64] { + let mut k = KeyMaterial512::from_bytes_as_type(&[7u8; 64], KeyType::MACKey).unwrap(); + k.set_key_len(len).unwrap(); + if len > 0 { + k.set_key_type(KeyType::MACKey).unwrap(); + k.set_security_strength(SecurityStrength::_128bit).unwrap_or(()); + } + assert!( + matches!( + BLAKE3KeyedHashKey::from_key_material_allow_weak(&k), + Err(MACError::InvalidLength(_)) + ), + "key length {len}" + ); + } + + let zero = KeyMaterial256::from_bytes_as_type(&[0u8; 32], KeyType::Zeroized).unwrap(); + assert_eq!(zero.key_type(), KeyType::Zeroized); + assert!(BLAKE3KeyedHashKey::from_key_material_allow_weak(&zero).is_ok()); + + // 128 bits is enough; the strength tag does not change the computation. + let mut ok = material(); + ok.set_security_strength(SecurityStrength::_128bit).unwrap(); + let strong = BLAKE3KeyedHashKey::from_key_material(&ok).unwrap(); + let mut weak = material(); + weak.set_security_strength(SecurityStrength::_112bit).unwrap(); + let weak = BLAKE3KeyedHashKey::from_key_material_allow_weak(&weak).unwrap(); + assert_eq!(strong.mac(b"abc"), weak.mac(b"abc")); + assert_eq!(strong.mac(b"abc"), key().mac(b"abc")); + } + + /// `generate` draws 32 bytes from the RNG and tags them at the RNG's strength (capped at the + /// length's), so an RNG below 128 bits cannot produce a key. + #[test] + fn generate_strength_and_bytes() { + let seed: [u8; 64] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(3)); + let from_seed = KeyMaterial256::from_bytes_as_type(&seed[..32], KeyType::MACKey).unwrap(); + let expected = BLAKE3KeyedHashKey::from_key_material(&from_seed).unwrap().mac(b"abc"); + + for strength in [SecurityStrength::_128bit, SecurityStrength::_256bit] { + let mut rng = FixedSeedRNG::<64>::new(seed); + rng.set_security_strength(strength); + let key = BLAKE3KeyedHashKey::generate(&mut rng).unwrap(); + assert_eq!(key.mac(b"abc"), expected, "the first 32 RNG bytes are the key"); + } + + for strength in [SecurityStrength::None, SecurityStrength::_112bit] { + let mut rng = FixedSeedRNG::<64>::new(seed); + rng.set_security_strength(strength); + assert!(matches!( + BLAKE3KeyedHashKey::generate(&mut rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + } + } + + #[test] + fn constants_and_params() { + assert_eq!(BLAKE3_KEYED_HASH_NAME, "BLAKE3-KeyedHash"); + assert_eq!(BLAKE3KeyedHash::ALG_NAME, BLAKE3_KEYED_HASH_NAME); + assert_eq!(BLAKE3KeyedHash::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(key().create_generator().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, 1838); + assert_eq!(size_of::(), 32); + } + + /* ------------------------------------ suspend/resume ------------------------------------ */ + + /// No byte sequence of the key may appear in a keyed state: checked for a fresh MAC (whose + /// chunk chaining value *is* the key), mid-first-block, after exactly one compressed block, + /// and at chunk boundaries. + #[test] + fn keyed_state_excludes_key() { + let k = key(); + let msg = input(3000); + for len in [0usize, 1, 63, 64, 65, 129, 1024, 1025, 2048, 2049, 3000] { + let mut m = k.create_generator(); + m.do_update(&msg[..len]); + let state = m.suspend(); + for w in KEY.chunks(4) { + assert!( + !state.windows(4).any(|x| x == w), + "key bytes {w:?} found in state after {len} bytes" + ); + } + if len <= 64 { + assert_eq!(state[3..3 + 32], [0u8; 32], "chunk CV is zeroed / {len}"); + } + } + } + + /// Resume at many points, including before any input (the key-is-the-CV case) and after one + /// compressed block. + #[test] + fn keyed_suspend_round_trip() { + let case = CASES.iter().find(|c| c.input_len == 4097).unwrap(); + let msg = input(case.input_len); + let expected = h(case.keyed_hash); + let k = key(); + for prefix in [0usize, 1, 64, 65, 1024, 1025, 2048, 4096, 4097] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let mut resumed = BLAKE3KeyedHash::from_suspended(m.suspend(), &k).unwrap(); + resumed.do_update(&msg[prefix..]); + assert_eq!(resumed.into_squeezer().do_output(OUTPUT_LEN), expected, "prefix {prefix}"); + } + } + + /// A wrong key resumes but gives a different tag whenever the key is still needed (see + /// `mid_chunk_state_finishes_without_key` for when it is not). A key of the wrong type or + /// length cannot be supplied: resume takes a `BLAKE3KeyedHashKey`. + #[test] + fn keyed_resume_wrong_key() { + let k = key(); + let msg = input(3000); + // prefix 0: the chunk CV is the key. prefix 100: the rest of the message starts new chunks. + for prefix in [0usize, 100] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let state = m.suspend(); + let finish = |mut m: BLAKE3KeyedHash| { + m.do_update(&msg[prefix..]); + m.do_final() + }; + let right = finish(BLAKE3KeyedHash::from_suspended(state, &k).unwrap()); + let wrong = finish(BLAKE3KeyedHash::from_suspended(state, &other_key()).unwrap()); + assert_eq!(right, k.mac(&msg)); + assert_ne!(wrong, right, "prefix {prefix}"); + } + } + + /// Pins a property of BLAKE3 itself that the docs warn about: the key words enter only as the + /// first chaining value of each chunk and of each parent (spec s. 2.4, 2.5). For a prefix of + /// 65..=1024 bytes (first chunk, a block compressed, empty CV stack), a state resumed with the + /// WRONG key still produces the right tag for any extension that keeps the message at or under + /// 1024 bytes -- and does not for an extension that crosses the chunk boundary, nor for any + /// multi-chunk prefix. + #[test] + fn mid_chunk_state_finishes_without_key() { + let k = key(); + let other = other_key(); + let msg = input(3000); + // (prefix length, total length, does the wrong key reproduce the right tag?) + for (prefix, total, key_free) in [ + (65usize, 66usize, true), + (100, 1024, true), + (1024, 1024, true), + (64, 100, false), // nothing compressed yet: the chunk CV is the key + (100, 1025, false), // crosses into a second chunk + (1024, 1025, false), + (1100, 1200, false), // CV stack non-empty: the root is a parent node + (2100, 2200, false), + ] { + let mut m = k.create_generator(); + m.do_update(&msg[..prefix]); + let mut resumed = BLAKE3KeyedHash::from_suspended(m.suspend(), &other).unwrap(); + resumed.do_update(&msg[prefix..total]); + let right = k.mac(&msg[..total]); + assert_eq!(resumed.do_final() == right, key_free, "prefix {prefix}, total {total}"); + } + } + + /// The keyed state shares the chunk/stack validation with BLAKE3; spot-check that it is wired + /// in (the full branch-by-branch test is in blake3_tests.rs). + #[test] + fn keyed_suspend_validation() { + let k = key(); + let mut m = k.create_generator(); + m.do_update(&input(100)); + let state = m.suspend(); + let mut bad = state; + bad[3 + 104] = 65; // block_len + assert!(matches!( + BLAKE3KeyedHash::from_suspended(bad, &k), + Err(SuspendableError::InvalidData) + )); + let mut bad = state; + bad[3 + 106] = 1; // cv_stack_len vs chunk_counter 0 + assert!(matches!( + BLAKE3KeyedHash::from_suspended(bad, &k), + Err(SuspendableError::InvalidData) + )); + } +} diff --git a/crypto/blake3/tests/vectors/mod.rs b/crypto/blake3/tests/vectors/mod.rs new file mode 100644 index 000000000..b9519fb93 --- /dev/null +++ b/crypto/blake3/tests/vectors/mod.rs @@ -0,0 +1,245 @@ +//! The official BLAKE3 test vectors, generated by a script from +//! `test_vectors/test_vectors.json` of github.com/BLAKE3-team/BLAKE3 (never hand-edited). +//! +//! From the file's `_comment`: "The input in each case is filled with a repeating sequence of +//! 251 bytes: 0, 1, 2, ..., 249, 250, 0, 1, ..., and so on." Each output is 131 bytes of extended +//! output, and "implementations should also check that the first 32 bytes match their +//! default-length output." + +#![allow(dead_code)] + +/// The keyed_hash key (the JSON `key` field). +pub const KEY: &[u8; 32] = b"whats the Elvish word for friend"; + +/// The derive_key context string (the JSON `context_string` field). +pub const CONTEXT: &[u8] = b"BLAKE3 2019-12-27 16:29:52 test vectors context"; + +/// Length of every expected output in the file. +pub const OUTPUT_LEN: usize = 131; + +/// One case: input length and the hex of the hash, keyed_hash and derive_key outputs. +pub struct Case { + pub input_len: usize, + pub hash: &'static str, + pub keyed_hash: &'static str, + pub derive_key: &'static str, +} + +/// The input for a case: the repeating 0..=250 byte pattern. +pub fn input(len: usize) -> Vec { + (0..len).map(|i| (i % 251) as u8).collect() +} + +#[rustfmt::skip] +pub const CASES: &[Case] = &[ + Case { + input_len: 0, + hash: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d", + keyed_hash: "92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f", + derive_key: "2cc39783c223154fea8dfb7c1b1660f2ac2dcbd1c1de8277b0b0dd39b7e50d7d905630c8be290dfcf3e6842f13bddd573c098c3f17361f1f206b8cad9d088aa4a3f746752c6b0ce6a83b0da81d59649257cdf8eb3e9f7d4998e41021fac119deefb896224ac99f860011f73609e6e0e4540f93b273e56547dfd3aa1a035ba6689d89a0", + }, + Case { + input_len: 1, + hash: "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213c3a6cb8bf623e20cdb535f8d1a5ffb86342d9c0b64aca3bce1d31f60adfa137b358ad4d79f97b47c3d5e79f179df87a3b9776ef8325f8329886ba42f07fb138bb502f4081cbcec3195c5871e6c23e2cc97d3c69a613eba131e5f1351f3f1da786545e5", + keyed_hash: "6d7878dfff2f485635d39013278ae14f1454b8c0a3a2d34bc1ab38228a80c95b6568c0490609413006fbd428eb3fd14e7756d90f73a4725fad147f7bf70fd61c4e0cf7074885e92b0e3f125978b4154986d4fb202a3f331a3fb6cf349a3a70e49990f98fe4289761c8602c4e6ab1138d31d3b62218078b2f3ba9a88e1d08d0dd4cea11", + derive_key: "b3e2e340a117a499c6cf2398a19ee0d29cca2bb7404c73063382693bf66cb06c5827b91bf889b6b97c5477f535361caefca0b5d8c4746441c57617111933158950670f9aa8a05d791daae10ac683cbef8faf897c84e6114a59d2173c3f417023a35d6983f2c7dfa57e7fc559ad751dbfb9ffab39c2ef8c4aafebc9ae973a64f0c76551", + }, + Case { + input_len: 2, + hash: "7b7015bb92cf0b318037702a6cdd81dee41224f734684c2c122cd6359cb1ee63d8386b22e2ddc05836b7c1bb693d92af006deb5ffbc4c70fb44d0195d0c6f252faac61659ef86523aa16517f87cb5f1340e723756ab65efb2f91964e14391de2a432263a6faf1d146937b35a33621c12d00be8223a7f1919cec0acd12097ff3ab00ab1", + keyed_hash: "5392ddae0e0a69d5f40160462cbd9bd889375082ff224ac9c758802b7a6fd20a9ffbf7efd13e989a6c246f96d3a96b9d279f2c4e63fb0bdff633957acf50ee1a5f658be144bab0f6f16500dee4aa5967fc2c586d85a04caddec90fffb7633f46a60786024353b9e5cebe277fcd9514217fee2267dcda8f7b31697b7c54fab6a939bf8f", + derive_key: "1f166565a7df0098ee65922d7fea425fb18b9943f19d6161e2d17939356168e6daa59cae19892b2d54f6fc9f475d26031fd1c22ae0a3e8ef7bdb23f452a15e0027629d2e867b1bb1e6ab21c71297377750826c404dfccc2406bd57a83775f89e0b075e59a7732326715ef912078e213944f490ad68037557518b79c0086de6d6f6cdd2", + }, + Case { + input_len: 3, + hash: "e1be4d7a8ab5560aa4199eea339849ba8e293d55ca0a81006726d184519e647f5b49b82f805a538c68915c1ae8035c900fd1d4b13902920fd05e1450822f36de9454b7e9996de4900c8e723512883f93f4345f8a58bfe64ee38d3ad71ab027765d25cdd0e448328a8e7a683b9a6af8b0af94fa09010d9186890b096a08471e4230a134", + keyed_hash: "39e67b76b5a007d4921969779fe666da67b5213b096084ab674742f0d5ec62b9b9142d0fab08e1b161efdbb28d18afc64d8f72160c958e53a950cdecf91c1a1bbab1a9c0f01def762a77e2e8545d4dec241e98a89b6db2e9a5b070fc110caae2622690bd7b76c02ab60750a3ea75426a6bb8803c370ffe465f07fb57def95df772c39f", + derive_key: "440aba35cb006b61fc17c0529255de438efc06a8c9ebf3f2ddac3b5a86705797f27e2e914574f4d87ec04c379e12789eccbfbc15892626042707802dbe4e97c3ff59dca80c1e54246b6d055154f7348a39b7d098b2b4824ebe90e104e763b2a447512132cede16243484a55a4e40a85790038bb0dcf762e8c053cabae41bbe22a5bff7", + }, + Case { + input_len: 4, + hash: "f30f5ab28fe047904037f77b6da4fea1e27241c5d132638d8bedce9d40494f328f603ba4564453e06cdcee6cbe728a4519bbe6f0d41e8a14b5b225174a566dbfa61b56afb1e452dc08c804f8c3143c9e2cc4a31bb738bf8c1917b55830c6e65797211701dc0b98daa1faeaa6ee9e56ab606ce03a1a881e8f14e87a4acf4646272cfd12", + keyed_hash: "7671dde590c95d5ac9616651ff5aa0a27bee5913a348e053b8aa9108917fe070116c0acff3f0d1fa97ab38d813fd46506089118147d83393019b068a55d646251ecf81105f798d76a10ae413f3d925787d6216a7eb444e510fd56916f1d753a5544ecf0072134a146b2615b42f50c179f56b8fae0788008e3e27c67482349e249cb86a", + derive_key: "f46085c8190d69022369ce1a18880e9b369c135eb93f3c63550d3e7630e91060fbd7d8f4258bec9da4e05044f88b91944f7cab317a2f0c18279629a3867fad0662c9ad4d42c6f27e5b124da17c8c4f3a94a025ba5d1b623686c6099d202a7317a82e3d95dae46a87de0555d727a5df55de44dab799a20dffe239594d6e99ed17950910", + }, + Case { + input_len: 5, + hash: "b40b44dfd97e7a84a996a91af8b85188c66c126940ba7aad2e7ae6b385402aa2ebcfdac6c5d32c31209e1f81a454751280db64942ce395104e1e4eaca62607de1c2ca748251754ea5bbe8c20150e7f47efd57012c63b3c6a6632dc1c7cd15f3e1c999904037d60fac2eb9397f2adbe458d7f264e64f1e73aa927b30988e2aed2f03620", + keyed_hash: "73ac69eecf286894d8102018a6fc729f4b1f4247d3703f69bdc6a5fe3e0c84616ab199d1f2f3e53bffb17f0a2209fe8b4f7d4c7bae59c2bc7d01f1ff94c67588cc6b38fa6024886f2c078bfe09b5d9e6584cd6c521c3bb52f4de7687b37117a2dbbec0d59e92fa9a8cc3240d4432f91757aabcae03e87431dac003e7d73574bfdd8218", + derive_key: "1f24eda69dbcb752847ec3ebb5dd42836d86e58500c7c98d906ecd82ed9ae47f6f48a3f67e4e43329c9a89b1ca526b9b35cbf7d25c1e353baffb590fd79be58ddb6c711f1a6b60e98620b851c688670412fcb0435657ba6b638d21f0f2a04f2f6b0bd8834837b10e438d5f4c7c2c71299cf7586ea9144ed09253d51f8f54dd6bff719d", + }, + Case { + input_len: 6, + hash: "06c4e8ffb6872fad96f9aaca5eee1553eb62aed0ad7198cef42e87f6a616c844611a30c4e4f37fe2fe23c0883cde5cf7059d88b657c7ed2087e3d210925ede716435d6d5d82597a1e52b9553919e804f5656278bd739880692c94bff2824d8e0b48cac1d24682699e4883389dc4f2faa2eb3b4db6e39debd5061ff3609916f3e07529a", + keyed_hash: "82d3199d0013035682cc7f2a399d4c212544376a839aa863a0f4c91220ca7a6dc2ffb3aa05f2631f0fa9ac19b6e97eb7e6669e5ec254799350c8b8d189e8807800842a5383c4d907c932f34490aaf00064de8cdb157357bde37c1504d2960034930887603abc5ccb9f5247f79224baff6120a3c622a46d7b1bcaee02c5025460941256", + derive_key: "be96b30b37919fe4379dfbe752ae77b4f7e2ab92f7ff27435f76f2f065f6a5f435ae01a1d14bd5a6b3b69d8cbd35f0b01ef2173ff6f9b640ca0bd4748efa398bf9a9c0acd6a66d9332fdc9b47ffe28ba7ab6090c26747b85f4fab22f936b71eb3f64613d8bd9dfabe9bb68da19de78321b481e5297df9e40ec8a3d662f3e1479c65de0", + }, + Case { + input_len: 7, + hash: "3f8770f387faad08faa9d8414e9f449ac68e6ff0417f673f602a646a891419fe66036ef6e6d1a8f54baa9fed1fc11c77cfb9cff65bae915045027046ebe0c01bf5a941f3bb0f73791d3fc0b84370f9f30af0cd5b0fc334dd61f70feb60dad785f070fef1f343ed933b49a5ca0d16a503f599a365a4296739248b28d1a20b0e2cc8975c", + keyed_hash: "af0a7ec382aedc0cfd626e49e7628bc7a353a4cb108855541a5651bf64fbb28a7c5035ba0f48a9c73dabb2be0533d02e8fd5d0d5639a18b2803ba6bf527e1d145d5fd6406c437b79bcaad6c7bdf1cf4bd56a893c3eb9510335a7a798548c6753f74617bede88bef924ba4b334f8852476d90b26c5dc4c3668a2519266a562c6c8034a6", + derive_key: "dc3b6485f9d94935329442916b0d059685ba815a1fa2a14107217453a7fc9f0e66266db2ea7c96843f9d8208e600a73f7f45b2f55b9e6d6a7ccf05daae63a3fdd10b25ac0bd2e224ce8291f88c05976d575df998477db86fb2cfbbf91725d62cb57acfeb3c2d973b89b503c2b60dde85a7802b69dc1ac2007d5623cbea8cbfb6b181f5", + }, + Case { + input_len: 8, + hash: "2351207d04fc16ade43ccab08600939c7c1fa70a5c0aaca76063d04c3228eaeb725d6d46ceed8f785ab9f2f9b06acfe398c6699c6129da084cb531177445a682894f9685eaf836999221d17c9a64a3a057000524cd2823986db378b074290a1a9b93a22e135ed2c14c7e20c6d045cd00b903400374126676ea78874d79f2dd7883cf5c", + keyed_hash: "be2f5495c61cba1bb348a34948c004045e3bd4dae8f0fe82bf44d0da245a060048eb5e68ce6dea1eb0229e144f578b3aa7e9f4f85febd135df8525e6fe40c6f0340d13dd09b255ccd5112a94238f2be3c0b5b7ecde06580426a93e0708555a265305abf86d874e34b4995b788e37a823491f25127a502fe0704baa6bfdf04e76c13276", + derive_key: "2b166978cef14d9d438046c720519d8b1cad707e199746f1562d0c87fbd32940f0e2545a96693a66654225ebbaac76d093bfa9cd8f525a53acb92a861a98c42e7d1c4ae82e68ab691d510012edd2a728f98cd4794ef757e94d6546961b4f280a51aac339cc95b64a92b83cc3f26d8af8dfb4c091c240acdb4d47728d23e7148720ef04", + }, + Case { + input_len: 63, + hash: "e9bc37a594daad83be9470df7f7b3798297c3d834ce80ba85d6e207627b7db7b1197012b1e7d9af4d7cb7bdd1f3bb49a90a9b5dec3ea2bbc6eaebce77f4e470cbf4687093b5352f04e4a4570fba233164e6acc36900e35d185886a827f7ea9bdc1e5c3ce88b095a200e62c10c043b3e9bc6cb9b6ac4dfa51794b02ace9f98779040755", + keyed_hash: "bb1eb5d4afa793c1ebdd9fb08def6c36d10096986ae0cfe148cd101170ce37aea05a63d74a840aecd514f654f080e51ac50fd617d22610d91780fe6b07a26b0847abb38291058c97474ef6ddd190d30fc318185c09ca1589d2024f0a6f16d45f11678377483fa5c005b2a107cb9943e5da634e7046855eaa888663de55d6471371d55d", + derive_key: "b6451e30b953c206e34644c6803724e9d2725e0893039cfc49584f991f451af3b89e8ff572d3da4f4022199b9563b9d70ebb616efff0763e9abec71b550f1371e233319c4c4e74da936ba8e5bbb29a598e007a0bbfa929c99738ca2cc098d59134d11ff300c39f82e2fce9f7f0fa266459503f64ab9913befc65fddc474f6dc1c67669", + }, + Case { + input_len: 64, + hash: "4eed7141ea4a5cd4b788606bd23f46e212af9cacebacdc7d1f4c6dc7f2511b98fc9cc56cb831ffe33ea8e7e1d1df09b26efd2767670066aa82d023b1dfe8ab1b2b7fbb5b97592d46ffe3e05a6a9b592e2949c74160e4674301bc3f97e04903f8c6cf95b863174c33228924cdef7ae47559b10b294acd660666c4538833582b43f82d74", + keyed_hash: "ba8ced36f327700d213f120b1a207a3b8c04330528586f414d09f2f7d9ccb7e68244c26010afc3f762615bbac552a1ca909e67c83e2fd5478cf46b9e811efccc93f77a21b17a152ebaca1695733fdb086e23cd0eb48c41c034d52523fc21236e5d8c9255306e48d52ba40b4dac24256460d56573d1312319afcf3ed39d72d0bfc69acb", + derive_key: "a5c4a7053fa86b64746d4bb688d06ad1f02a18fce9afd3e818fefaa7126bf73e9b9493a9befebe0bf0c9509fb3105cfa0e262cde141aa8e3f2c2f77890bb64a4cca96922a21ead111f6338ad5244f2c15c44cb595443ac2ac294231e31be4a4307d0a91e874d36fc9852aeb1265c09b6e0cda7c37ef686fbbcab97e8ff66718be048bb", + }, + Case { + input_len: 65, + hash: "de1e5fa0be70df6d2be8fffd0e99ceaa8eb6e8c93a63f2d8d1c30ecb6b263dee0e16e0a4749d6811dd1d6d1265c29729b1b75a9ac346cf93f0e1d7296dfcfd4313b3a227faaaaf7757cc95b4e87a49be3b8a270a12020233509b1c3632b3485eef309d0abc4a4a696c9decc6e90454b53b000f456a3f10079072baaf7a981653221f2c", + keyed_hash: "c0a4edefa2d2accb9277c371ac12fcdbb52988a86edc54f0716e1591b4326e72d5e795f46a596b02d3d4bfb43abad1e5d19211152722ec1f20fef2cd413e3c22f2fc5da3d73041275be6ede3517b3b9f0fc67ade5956a672b8b75d96cb43294b9041497de92637ed3f2439225e683910cb3ae923374449ca788fb0f9bea92731bc26ad", + derive_key: "51fd05c3c1cfbc8ed67d139ad76f5cf8236cd2acd26627a30c104dfd9d3ff8a82b02e8bd36d8498a75ad8c8e9b15eb386970283d6dd42c8ae7911cc592887fdbe26a0a5f0bf821cd92986c60b2502c9be3f98a9c133a7e8045ea867e0828c7252e739321f7c2d65daee4468eb4429efae469a42763f1f94977435d10dccae3e3dce88d", + }, + Case { + input_len: 127, + hash: "d81293fda863f008c09e92fc382a81f5a0b4a1251cba1634016a0f86a6bd640de3137d477156d1fde56b0cf36f8ef18b44b2d79897bece12227539ac9ae0a5119da47644d934d26e74dc316145dcb8bb69ac3f2e05c242dd6ee06484fcb0e956dc44355b452c5e2bbb5e2b66e99f5dd443d0cbcaaafd4beebaed24ae2f8bb672bcef78", + keyed_hash: "c64200ae7dfaf35577ac5a9521c47863fb71514a3bcad18819218b818de85818ee7a317aaccc1458f78d6f65f3427ec97d9c0adb0d6dacd4471374b621b7b5f35cd54663c64dbe0b9e2d95632f84c611313ea5bd90b71ce97b3cf645776f3adc11e27d135cbadb9875c2bf8d3ae6b02f8a0206aba0c35bfe42574011931c9a255ce6dc", + derive_key: "c91c090ceee3a3ac81902da31838012625bbcd73fcb92e7d7e56f78deba4f0c3feeb3974306966ccb3e3c69c337ef8a45660ad02526306fd685c88542ad00f759af6dd1adc2e50c2b8aac9f0c5221ff481565cf6455b772515a69463223202e5c371743e35210bbbbabd89651684107fd9fe493c937be16e39cfa7084a36207c99bea3", + }, + Case { + input_len: 128, + hash: "f17e570564b26578c33bb7f44643f539624b05df1a76c81f30acd548c44b45efa69faba091427f9c5c4caa873aa07828651f19c55bad85c47d1368b11c6fd99e47ecba5820a0325984d74fe3e4058494ca12e3f1d3293d0010a9722f7dee64f71246f75e9361f44cc8e214a100650db1313ff76a9f93ec6e84edb7add1cb4a95019b0c", + keyed_hash: "b04fe15577457267ff3b6f3c947d93be581e7e3a4b018679125eaf86f6a628ecd86bbe0001f10bda47e6077b735016fca8119da11348d93ca302bbd125bde0db2b50edbe728a620bb9d3e6f706286aedea973425c0b9eedf8a38873544cf91badf49ad92a635a93f71ddfcee1eae536c25d1b270956be16588ef1cfef2f1d15f650bd5", + derive_key: "81720f34452f58a0120a58b6b4608384b5c51d11f39ce97161a0c0e442ca022550e7cd651e312f0b4c6afb3c348ae5dd17d2b29fab3b894d9a0034c7b04fd9190cbd90043ff65d1657bbc05bfdecf2897dd894c7a1b54656d59a50b51190a9da44db426266ad6ce7c173a8c0bbe091b75e734b4dadb59b2861cd2518b4e7591e4b83c9", + }, + Case { + input_len: 129, + hash: "683aaae9f3c5ba37eaaf072aed0f9e30bac0865137bae68b1fde4ca2aebdcb12f96ffa7b36dd78ba321be7e842d364a62a42e3746681c8bace18a4a8a79649285c7127bf8febf125be9de39586d251f0d41da20980b70d35e3dac0eee59e468a894fa7e6a07129aaad09855f6ad4801512a116ba2b7841e6cfc99ad77594a8f2d181a7", + keyed_hash: "d4a64dae6cdccbac1e5287f54f17c5f985105457c1a2ec1878ebd4b57e20d38f1c9db018541eec241b748f87725665b7b1ace3e0065b29c3bcb232c90e37897fa5aaee7e1e8a2ecfcd9b51463e42238cfdd7fee1aecb3267fa7f2128079176132a412cd8aaf0791276f6b98ff67359bd8652ef3a203976d5ff1cd41885573487bcd683", + derive_key: "938d2d4435be30eafdbb2b7031f7857c98b04881227391dc40db3c7b21f41fc18d72d0f9c1de5760e1941aebf3100b51d64644cb459eb5d20258e233892805eb98b07570ef2a1787cd48e117c8d6a63a68fd8fc8e59e79dbe63129e88352865721c8d5f0cf183f85e0609860472b0d6087cefdd186d984b21542c1c780684ed6832d8d", + }, + Case { + input_len: 1023, + hash: "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11a182d27a591b05592b15607500e1e8dd56bc6c7fc063715b7a1d737df5bad3339c56778957d870eb9717b57ea3d9fb68d1b55127bba6a906a4a24bbd5acb2d123a37b28f9e9a81bbaae360d58f85e5fc9d75f7c370a0cc09b6522d9c8d822f2f28f485", + keyed_hash: "c951ecdf03288d0fcc96ee3413563d8a6d3589547f2c2fb36d9786470f1b9d6e890316d2e6d8b8c25b0a5b2180f94fb1a158ef508c3cde45e2966bd796a696d3e13efd86259d756387d9becf5c8bf1ce2192b87025152907b6d8cc33d17826d8b7b9bc97e38c3c85108ef09f013e01c229c20a83d9e8efac5b37470da28575fd755a10", + derive_key: "74a16c1c3d44368a86e1ca6df64be6a2f64cce8f09220787450722d85725dea59c413264404661e9e4d955409dfe4ad3aa487871bcd454ed12abfe2c2b1eb7757588cf6cb18d2eccad49e018c0d0fec323bec82bf1644c6325717d13ea712e6840d3e6e730d35553f59eff5377a9c350bcc1556694b924b858f329c44ee64b884ef00d", + }, + Case { + input_len: 1024, + hash: "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af71cf8107265ecdaf8505b95d8fcec83a98a6a96ea5109d2c179c47a387ffbb404756f6eeae7883b446b70ebb144527c2075ab8ab204c0086bb22b7c93d465efc57f8d917f0b385c6df265e77003b85102967486ed57db5c5ca170ba441427ed9afa684e", + keyed_hash: "75c46f6f3d9eb4f55ecaaee480db732e6c2105546f1e675003687c31719c7ba4a78bc838c72852d4f49c864acb7adafe2478e824afe51c8919d06168414c265f298a8094b1ad813a9b8614acabac321f24ce61c5a5346eb519520d38ecc43e89b5000236df0597243e4d2493fd626730e2ba17ac4d8824d09d1a4a8f57b8227778e2de", + derive_key: "7356cd7720d5b66b6d0697eb3177d9f8d73a4a5c5e968896eb6a6896843027066c23b601d3ddfb391e90d5c8eccdef4ae2a264bce9e612ba15e2bc9d654af1481b2e75dbabe615974f1070bba84d56853265a34330b4766f8e75edd1f4a1650476c10802f22b64bd3919d246ba20a17558bc51c199efdec67e80a227251808d8ce5bad", + }, + Case { + input_len: 1025, + hash: "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a", + keyed_hash: "357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69362396b77fdc0d2634a552970843722066c3c15902ae5097e00ff53f1e116f1cd5352720113a837ab2452cafbde4d54085d9cf5d21ca613071551b25d52e69d6c81123872b6f19cd3bc1333edf0c52b94de23ba772cf82636cff4542540a7738d5b930", + derive_key: "effaa245f065fbf82ac186839a249707c3bddf6d3fdda22d1b95a3c970379bcb5d31013a167509e9066273ab6e2123bc835b408b067d88f96addb550d96b6852dad38e320b9d940f86db74d398c770f462118b35d2724efa13da97194491d96dd37c3c09cbef665953f2ee85ec83d88b88d11547a6f911c8217cca46defa2751e7f3ad", + }, + Case { + input_len: 2048, + hash: "e776b6028c7cd22a4d0ba182a8bf62205d2ef576467e838ed6f2529b85fba24a9a60bf80001410ec9eea6698cd537939fad4749edd484cb541aced55cd9bf54764d063f23f6f1e32e12958ba5cfeb1bf618ad094266d4fc3c968c2088f677454c288c67ba0dba337b9d91c7e1ba586dc9a5bc2d5e90c14f53a8863ac75655461cea8f9", + keyed_hash: "879cf1fa2ea0e79126cb1063617a05b6ad9d0b696d0d757cf053439f60a99dd10173b961cd574288194b23ece278c330fbb8585485e74967f31352a8183aa782b2b22f26cdcadb61eed1a5bc144b8198fbb0c13abbf8e3192c145d0a5c21633b0ef86054f42809df823389ee40811a5910dcbd1018af31c3b43aa55201ed4edaac74fe", + derive_key: "7b2945cb4fef70885cc5d78a87bf6f6207dd901ff239201351ffac04e1088a23e2c11a1ebffcea4d80447867b61badb1383d842d4e79645d48dd82ccba290769caa7af8eaa1bd78a2a5e6e94fbdab78d9c7b74e894879f6a515257ccf6f95056f4e25390f24f6b35ffbb74b766202569b1d797f2d4bd9d17524c720107f985f4ddc583", + }, + Case { + input_len: 2049, + hash: "5f4d72f40d7a5f82b15ca2b2e44b1de3c2ef86c426c95c1af0b687952256303096de31d71d74103403822a2e0bc1eb193e7aecc9643a76b7bbc0c9f9c52e8783aae98764ca468962b5c2ec92f0c74eb5448d519713e09413719431c802f948dd5d90425a4ecdadece9eb178d80f26efccae630734dff63340285adec2aed3b51073ad3", + keyed_hash: "9f29700902f7c86e514ddc4df1e3049f258b2472b6dd5267f61bf13983b78dd5f9a88abfefdfa1e00b418971f2b39c64ca621e8eb37fceac57fd0c8fc8e117d43b81447be22d5d8186f8f5919ba6bcc6846bd7d50726c06d245672c2ad4f61702c646499ee1173daa061ffe15bf45a631e2946d616a4c345822f1151284712f76b2b0e", + derive_key: "2ea477c5515cc3dd606512ee72bb3e0e758cfae7232826f35fb98ca1bcbdf27316d8e9e79081a80b046b60f6a263616f33ca464bd78d79fa18200d06c7fc9bffd808cc4755277a7d5e09da0f29ed150f6537ea9bed946227ff184cc66a72a5f8c1e4bd8b04e81cf40fe6dc4427ad5678311a61f4ffc39d195589bdbc670f63ae70f4b6", + }, + Case { + input_len: 3072, + hash: "b98cb0ff3623be03326b373de6b9095218513e64f1ee2edd2525c7ad1e5cffd29a3f6b0b978d6608335c09dc94ccf682f9951cdfc501bfe47b9c9189a6fc7b404d120258506341a6d802857322fbd20d3e5dae05b95c88793fa83db1cb08e7d8008d1599b6209d78336e24839724c191b2a52a80448306e0daa84a3fdb566661a37e11", + keyed_hash: "044a0e7b172a312dc02a4c9a818c036ffa2776368d7f528268d2e6b5df19177022f302d0529e4174cc507c463671217975e81dab02b8fdeb0d7ccc7568dd22574c783a76be215441b32e91b9a904be8ea81f7a0afd14bad8ee7c8efc305ace5d3dd61b996febe8da4f56ca0919359a7533216e2999fc87ff7d8f176fbecb3d6f34278b", + derive_key: "050df97f8c2ead654d9bb3ab8c9178edcd902a32f8495949feadcc1e0480c46b3604131bbd6e3ba573b6dd682fa0a63e5b165d39fc43a625d00207607a2bfeb65ff1d29292152e26b298868e3b87be95d6458f6f2ce6118437b632415abe6ad522874bcd79e4030a5e7bad2efa90a7a7c67e93f0a18fb28369d0a9329ab5c24134ccb0", + }, + Case { + input_len: 3073, + hash: "7124b49501012f81cc7f11ca069ec9226cecb8a2c850cfe644e327d22d3e1cd39a27ae3b79d68d89da9bf25bc27139ae65a324918a5f9b7828181e52cf373c84f35b639b7fccbb985b6f2fa56aea0c18f531203497b8bbd3a07ceb5926f1cab74d14bd66486d9a91eba99059a98bd1cd25876b2af5a76c3e9eed554ed72ea952b603bf", + keyed_hash: "68dede9bef00ba89e43f31a6825f4cf433389fedae75c04ee9f0cf16a427c95a96d6da3fe985054d3478865be9a092250839a697bbda74e279e8a9e69f0025e4cfddd6cfb434b1cd9543aaf97c635d1b451a4386041e4bb100f5e45407cbbc24fa53ea2de3536ccb329e4eb9466ec37093a42cf62b82903c696a93a50b702c80f3c3c5", + derive_key: "72613c9ec9ff7e40f8f5c173784c532ad852e827dba2bf85b2ab4b76f7079081576288e552647a9d86481c2cae75c2dd4e7c5195fb9ada1ef50e9c5098c249d743929191441301c69e1f48505a4305ec1778450ee48b8e69dc23a25960fe33070ea549119599760a8a2d28aeca06b8c5e9ba58bc19e11fe57b6ee98aa44b2a8e6b14a5", + }, + Case { + input_len: 4096, + hash: "015094013f57a5277b59d8475c0501042c0b642e531b0a1c8f58d2163229e9690289e9409ddb1b99768eafe1623da896faf7e1114bebeadc1be30829b6f8af707d85c298f4f0ff4d9438aef948335612ae921e76d411c3a9111df62d27eaf871959ae0062b5492a0feb98ef3ed4af277f5395172dbe5c311918ea0074ce0036454f620", + keyed_hash: "befc660aea2f1718884cd8deb9902811d332f4fc4a38cf7c7300d597a081bfc0bbb64a36edb564e01e4b4aaf3b060092a6b838bea44afebd2deb8298fa562b7b597c757b9df4c911c3ca462e2ac89e9a787357aaf74c3b56d5c07bc93ce899568a3eb17d9250c20f6c5f6c1e792ec9a2dcb715398d5a6ec6d5c54f586a00403a1af1de", + derive_key: "1e0d7f3db8c414c97c6307cbda6cd27ac3b030949da8e23be1a1a924ad2f25b9d78038f7b198596c6cc4a9ccf93223c08722d684f240ff6569075ed81591fd93f9fff1110b3a75bc67e426012e5588959cc5a4c192173a03c00731cf84544f65a2fb9378989f72e9694a6a394a8a30997c2e67f95a504e631cd2c5f55246024761b245", + }, + Case { + input_len: 4097, + hash: "9b4052b38f1c5fc8b1f9ff7ac7b27cd242487b3d890d15c96a1c25b8aa0fb99505f91b0b5600a11251652eacfa9497b31cd3c409ce2e45cfe6c0a016967316c426bd26f619eab5d70af9a418b845c608840390f361630bd497b1ab44019316357c61dbe091ce72fc16dc340ac3d6e009e050b3adac4b5b2c92e722cffdc46501531956", + keyed_hash: "00df940cd36bb9fa7cbbc3556744e0dbc8191401afe70520ba292ee3ca80abbc606db4976cfdd266ae0abf667d9481831ff12e0caa268e7d3e57260c0824115a54ce595ccc897786d9dcbf495599cfd90157186a46ec800a6763f1c59e36197e9939e900809f7077c102f888caaf864b253bc41eea812656d46742e4ea42769f89b83f", + derive_key: "aca51029626b55fda7117b42a7c211f8c6e9ba4fe5b7a8ca922f34299500ead8a897f66a400fed9198fd61dd2d58d382458e64e100128075fc54b860934e8de2e84170734b06e1d212a117100820dbc48292d148afa50567b8b84b1ec336ae10d40c8c975a624996e12de31abbe135d9d159375739c333798a80c64ae895e51e22f3ad", + }, + Case { + input_len: 5120, + hash: "9cadc15fed8b5d854562b26a9536d9707cadeda9b143978f319ab34230535833acc61c8fdc114a2010ce8038c853e121e1544985133fccdd0a2d507e8e615e611e9a0ba4f47915f49e53d721816a9198e8b30f12d20ec3689989175f1bf7a300eee0d9321fad8da232ece6efb8e9fd81b42ad161f6b9550a069e66b11b40487a5f5059", + keyed_hash: "2c493e48e9b9bf31e0553a22b23503c0a3388f035cece68eb438d22fa1943e209b4dc9209cd80ce7c1f7c9a744658e7e288465717ae6e56d5463d4f80cdb2ef56495f6a4f5487f69749af0c34c2cdfa857f3056bf8d807336a14d7b89bf62bef2fb54f9af6a546f818dc1e98b9e07f8a5834da50fa28fb5874af91bf06020d1bf0120e", + derive_key: "7a7acac8a02adcf3038d74cdd1d34527de8a0fcc0ee3399d1262397ce5817f6055d0cefd84d9d57fe792d65a278fd20384ac6c30fdb340092f1a74a92ace99c482b28f0fc0ef3b923e56ade20c6dba47e49227166251337d80a037e987ad3a7f728b5ab6dfafd6e2ab1bd583a95d9c895ba9c2422c24ea0f62961f0dca45cad47bfa0d", + }, + Case { + input_len: 5121, + hash: "628bd2cb2004694adaab7bbd778a25df25c47b9d4155a55f8fbd79f2fe154cff96adaab0613a6146cdaabe498c3a94e529d3fc1da2bd08edf54ed64d40dcd6777647eac51d8277d70219a9694334a68bc8f0f23e20b0ff70ada6f844542dfa32cd4204ca1846ef76d811cdb296f65e260227f477aa7aa008bac878f72257484f2b6c95", + keyed_hash: "6ccf1c34753e7a044db80798ecd0782a8f76f33563accaddbfbb2e0ea4b2d0240d07e63f13667a8d1490e5e04f13eb617aea16a8c8a5aaed1ef6fbde1b0515e3c81050b361af6ead126032998290b563e3caddeaebfab592e155f2e161fb7cba939092133f23f9e65245e58ec23457b78a2e8a125588aad6e07d7f11a85b88d375b72d", + derive_key: "b07f01e518e702f7ccb44a267e9e112d403a7b3f4883a47ffbed4b48339b3c341a0add0ac032ab5aaea1e4e5b004707ec5681ae0fcbe3796974c0b1cf31a194740c14519273eedaabec832e8a784b6e7cfc2c5952677e6c3f2c3914454082d7eb1ce1766ac7d75a4d3001fc89544dd46b5147382240d689bbbaefc359fb6ae30263165", + }, + Case { + input_len: 6144, + hash: "3e2e5b74e048f3add6d21faab3f83aa44d3b2278afb83b80b3c35164ebeca2054d742022da6fdda444ebc384b04a54c3ac5839b49da7d39f6d8a9db03deab32aade156c1c0311e9b3435cde0ddba0dce7b26a376cad121294b689193508dd63151603c6ddb866ad16c2ee41585d1633a2cea093bea714f4c5d6b903522045b20395c83", + keyed_hash: "3d6b6d21281d0ade5b2b016ae4034c5dec10ca7e475f90f76eac7138e9bc8f1dc35754060091dc5caf3efabe0603c60f45e415bb3407db67e6beb3d11cf8e4f7907561f05dace0c15807f4b5f389c841eb114d81a82c02a00b57206b1d11fa6e803486b048a5ce87105a686dee041207e095323dfe172df73deb8c9532066d88f9da7e", + derive_key: "2a95beae63ddce523762355cf4b9c1d8f131465780a391286a5d01abb5683a1597099e3c6488aab6c48f3c15dbe1942d21dbcdc12115d19a8b8465fb54e9053323a9178e4275647f1a9927f6439e52b7031a0b465c861a3fc531527f7758b2b888cf2f20582e9e2c593709c0a44f9c6e0f8b963994882ea4168827823eef1f64169fef", + }, + Case { + input_len: 6145, + hash: "f1323a8631446cc50536a9f705ee5cb619424d46887f3c376c695b70e0f0507f18a2cfdd73c6e39dd75ce7c1c6e3ef238fd54465f053b25d21044ccb2093beb015015532b108313b5829c3621ce324b8e14229091b7c93f32db2e4e63126a377d2a63a3597997d4f1cba59309cb4af240ba70cebff9a23d5e3ff0cdae2cfd54e070022", + keyed_hash: "9ac301e9e39e45e3250a7e3b3df701aa0fb6889fbd80eeecf28dbc6300fbc539f3c184ca2f59780e27a576c1d1fb9772e99fd17881d02ac7dfd39675aca918453283ed8c3169085ef4a466b91c1649cc341dfdee60e32231fc34c9c4e0b9a2ba87ca8f372589c744c15fd6f985eec15e98136f25beeb4b13c4e43dc84abcc79cd4646c", + derive_key: "379bcc61d0051dd489f686c13de00d5b14c505245103dc040d9e4dd1facab8e5114493d029bdbd295aaa744a59e31f35c7f52dba9c3642f773dd0b4262a9980a2aef811697e1305d37ba9d8b6d850ef07fe41108993180cf779aeece363704c76483458603bbeeb693cffbbe5588d1f3535dcad888893e53d977424bb707201569a8d2", + }, + Case { + input_len: 7168, + hash: "61da957ec2499a95d6b8023e2b0e604ec7f6b50e80a9678b89d2628e99ada77a5707c321c83361793b9af62a40f43b523df1c8633cecb4cd14d00bdc79c78fca5165b863893f6d38b02ff7236c5a9a8ad2dba87d24c547cab046c29fc5bc1ed142e1de4763613bb162a5a538e6ef05ed05199d751f9eb58d332791b8d73fb74e4fce95", + keyed_hash: "b42835e40e9d4a7f42ad8cc04f85a963a76e18198377ed84adddeaecacc6f3fca2f01d5277d69bb681c70fa8d36094f73ec06e452c80d2ff2257ed82e7ba348400989a65ee8daa7094ae0933e3d2210ac6395c4af24f91c2b590ef87d7788d7066ea3eaebca4c08a4f14b9a27644f99084c3543711b64a070b94f2c9d1d8a90d035d52", + derive_key: "11c37a112765370c94a51415d0d651190c288566e295d505defdad895dae223730d5a5175a38841693020669c7638f40b9bc1f9f39cf98bda7a5b54ae24218a800a2116b34665aa95d846d97ea988bfcb53dd9c055d588fa21ba78996776ea6c40bc428b53c62b5f3ccf200f647a5aae8067f0ea1976391fcc72af1945100e2a6dcb88", + }, + Case { + input_len: 7169, + hash: "a003fc7a51754a9b3c7fae0367ab3d782dccf28855a03d435f8cfe74605e781798a8b20534be1ca9eb2ae2df3fae2ea60e48c6fb0b850b1385b5de0fe460dbe9d9f9b0d8db4435da75c601156df9d047f4ede008732eb17adc05d96180f8a73548522840779e6062d643b79478a6e8dbce68927f36ebf676ffa7d72d5f68f050b119c8", + keyed_hash: "ed9b1a922c046fdb3d423ae34e143b05ca1bf28b710432857bf738bcedbfa5113c9e28d72fcbfc020814ce3f5d4fc867f01c8f5b6caf305b3ea8a8ba2da3ab69fabcb438f19ff11f5378ad4484d75c478de425fb8e6ee809b54eec9bdb184315dc856617c09f5340451bf42fd3270a7b0b6566169f242e533777604c118a6358250f54", + derive_key: "554b0a5efea9ef183f2f9b931b7497995d9eb26f5c5c6dad2b97d62fc5ac31d99b20652c016d88ba2a611bbd761668d5eda3e568e940faae24b0d9991c3bd25a65f770b89fdcadabcb3d1a9c1cb63e69721cacf1ae69fefdcef1e3ef41bc5312ccc17222199e47a26552c6adc460cf47a72319cb5039369d0060eaea59d6c65130f1dd", + }, + Case { + input_len: 8192, + hash: "aae792484c8efe4f19e2ca7d371d8c467ffb10748d8a5a1ae579948f718a2a635fe51a27db045a567c1ad51be5aa34c01c6651c4d9b5b5ac5d0fd58cf18dd61a47778566b797a8c67df7b1d60b97b19288d2d877bb2df417ace009dcb0241ca1257d62712b6a4043b4ff33f690d849da91ea3bf711ed583cb7b7a7da2839ba71309bbf", + keyed_hash: "dc9637c8845a770b4cbf76b8daec0eebf7dc2eac11498517f08d44c8fc00d58a4834464159dcbc12a0ba0c6d6eb41bac0ed6585cabfe0aca36a375e6c5480c22afdc40785c170f5a6b8a1107dbee282318d00d915ac9ed1143ad40765ec120042ee121cd2baa36250c618adaf9e27260fda2f94dea8fb6f08c04f8f10c78292aa46102", + derive_key: "ad01d7ae4ad059b0d33baa3c01319dcf8088094d0359e5fd45d6aeaa8b2d0c3d4c9e58958553513b67f84f8eac653aeeb02ae1d5672dcecf91cd9985a0e67f4501910ecba25555395427ccc7241d70dc21c190e2aadee875e5aae6bf1912837e53411dabf7a56cbf8e4fb780432b0d7fe6cec45024a0788cf5874616407757e9e6bef7", + }, + Case { + input_len: 8193, + hash: "bab6c09cb8ce8cf459261398d2e7aef35700bf488116ceb94a36d0f5f1b7bc3bb2282aa69be089359ea1154b9a9286c4a56af4de975a9aa4a5c497654914d279bea60bb6d2cf7225a2fa0ff5ef56bbe4b149f3ed15860f78b4e2ad04e158e375c1e0c0b551cd7dfc82f1b155c11b6b3ed51ec9edb30d133653bb5709d1dbd55f4e1ff6", + keyed_hash: "954a2a75420c8d6547e3ba5b98d963e6fa6491addc8c023189cc519821b4a1f5f03228648fd983aef045c2fa8290934b0866b615f585149587dda2299039965328835a2b18f1d63b7e300fc76ff260b571839fe44876a4eae66cbac8c67694411ed7e09df51068a22c6e67d6d3dd2cca8ff12e3275384006c80f4db68023f24eebba57", + derive_key: "af1e0346e389b17c23200270a64aa4e1ead98c61695d917de7d5b00491c9b0f12f20a01d6d622edf3de026a4db4e4526225debb93c1237934d71c7340bb5916158cbdafe9ac3225476b6ab57a12357db3abbad7a26c6e66290e44034fb08a20a8d0ec264f309994d2810c49cfba6989d7abb095897459f5425adb48aba07c5fb3c83c0", + }, + Case { + input_len: 16384, + hash: "f875d6646de28985646f34ee13be9a576fd515f76b5b0a26bb324735041ddde49d764c270176e53e97bdffa58d549073f2c660be0e81293767ed4e4929f9ad34bbb39a529334c57c4a381ffd2a6d4bfdbf1482651b172aa883cc13408fa67758a3e47503f93f87720a3177325f7823251b85275f64636a8f1d599c2e49722f42e93893", + keyed_hash: "9e9fc4eb7cf081ea7c47d1807790ed211bfec56aa25bb7037784c13c4b707b0df9e601b101e4cf63a404dfe50f2e1865bb12edc8fca166579ce0c70dba5a5c0fc960ad6f3772183416a00bd29d4c6e651ea7620bb100c9449858bf14e1ddc9ecd35725581ca5b9160de04060045993d972571c3e8f71e9d0496bfa744656861b169d65", + derive_key: "160e18b5878cd0df1c3af85eb25a0db5344d43a6fbd7a8ef4ed98d0714c3f7e160dc0b1f09caa35f2f417b9ef309dfe5ebd67f4c9507995a531374d099cf8ae317542e885ec6f589378864d3ea98716b3bbb65ef4ab5e0ab5bb298a501f19a41ec19af84a5e6b428ecd813b1a47ed91c9657c3fba11c406bc316768b58f6802c9e9b57", + }, + Case { + input_len: 31744, + hash: "62b6960e1a44bcc1eb1a611a8d6235b6b4b78f32e7abc4fb4c6cdcce94895c47860cc51f2b0c28a7b77304bd55fe73af663c02d3f52ea053ba43431ca5bab7bfea2f5e9d7121770d88f70ae9649ea713087d1914f7f312147e247f87eb2d4ffef0ac978bf7b6579d57d533355aa20b8b77b13fd09748728a5cc327a8ec470f4013226f", + keyed_hash: "efa53b389ab67c593dba624d898d0f7353ab99e4ac9d42302ee64cbf9939a4193a7258db2d9cd32a7a3ecfce46144114b15c2fcb68a618a976bd74515d47be08b628be420b5e830fade7c080e351a076fbc38641ad80c736c8a18fe3c66ce12f95c61c2462a9770d60d0f77115bbcd3782b593016a4e728d4c06cee4505cb0c08a42ec", + derive_key: "39772aef80e0ebe60596361e45b061e8f417429d529171b6764468c22928e28e9759adeb797a3fbf771b1bcea30150a020e317982bf0d6e7d14dd9f064bc11025c25f31e81bd78a921db0174f03dd481d30e93fd8e90f8b2fee209f849f2d2a52f31719a490fb0ba7aea1e09814ee912eba111a9fde9d5c274185f7bae8ba85d300a2b", + }, + Case { + input_len: 102400, + hash: "bc3e3d41a1146b069abffad3c0d44860cf664390afce4d9661f7902e7943e085e01c59dab908c04c3342b816941a26d69c2605ebee5ec5291cc55e15b76146e6745f0601156c3596cb75065a9c57f35585a52e1ac70f69131c23d611ce11ee4ab1ec2c009012d236648e77be9295dd0426f29b764d65de58eb7d01dd42248204f45f8e", + keyed_hash: "1c35d1a5811083fd7119f5d5d1ba027b4d01c0c6c49fb6ff2cf75393ea5db4a7f9dbdd3e1d81dcbca3ba241bb18760f207710b751846faaeb9dff8262710999a59b2aa1aca298a032d94eacfadf1aa192418eb54808db23b56e34213266aa08499a16b354f018fc4967d05f8b9d2ad87a7278337be9693fc638a3bfdbe314574ee6fc4", + derive_key: "4652cff7a3f385a6103b5c260fc1593e13c778dbe608efb092fe7ee69df6e9c6d83a3e041bc3a48df2879f4a0a3ed40e7c961c73eff740f3117a0504c2dff4786d44fb17f1549eb0ba585e40ec29bf7732f0b7e286ff8acddc4cb1e23b87ff5d824a986458dcc6a04ac83969b80637562953df51ed1a7e90a7926924d2763778be8560", + }, +]; diff --git a/crypto/cipher/Cargo.toml b/crypto/cipher/Cargo.toml index f27e1e0cd..5e6261e50 100644 --- a/crypto/cipher/Cargo.toml +++ b/crypto/cipher/Cargo.toml @@ -46,6 +46,10 @@ path = "tests/modes/cfb8_tests.rs" name = "cfb_tests" path = "tests/modes/cfb_tests.rs" +[[test]] +name = "cmac_tests" +path = "tests/modes/cmac_tests.rs" + [[test]] name = "ctr_tests" path = "tests/modes/ctr_tests.rs" @@ -58,6 +62,10 @@ path = "tests/modes/ecb_tests.rs" name = "gcm_tests" path = "tests/modes/gcm_tests.rs" +[[test]] +name = "gmac_tests" +path = "tests/modes/gmac_tests.rs" + [[test]] name = "symmetric_cipher_api_tests" path = "tests/modes/symmetric_cipher_api_tests.rs" diff --git a/crypto/cipher/src/lib.rs b/crypto/cipher/src/lib.rs index 92b46f49a..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(); //! ``` @@ -54,6 +60,8 @@ #![forbid(missing_docs)] #![no_std] +extern crate alloc; + // `tests/common/toy_block_cipher.rs` names `bouncycastle_cipher::modes::ModeNames`, and is also // `include!`d by unit tests here, where that path would otherwise not resolve. #[cfg(test)] @@ -91,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>, /// >; /// ``` /// @@ -111,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 6aaada22e..2ad71174c 100644 --- a/crypto/cipher/src/modes/ccm.rs +++ b/crypto/cipher/src/modes/ccm.rs @@ -11,50 +11,51 @@ //! permutation that implements nothing but `encrypt_block` works here. //! //! # Usage Examples -//! The nonce is supplied rather than generated, and there is an extra input (the AAD, authenticated but -//! not encrypted) and an extra output (the tag). //! -//! Decryption either returns the plaintext or fails with [`SymmetricCipherError::AEADTagCheckFailed`] -//! -- it never returns plausible-looking rubbish the way the unauthenticated modes do when the -//! ciphertext has been altered. +//! 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_cipher::modes::CcmPacket; -//! use bouncycastle_cipher::{Decrypting, Encrypting}; +//! use bouncycastle_cipher::modes::CCM; //! -//! type ToyCcm = CcmPacket; +//! // 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"); -//! -//! // Supplied, not generated -//! // It is the caller's responsibility that it never repeat under this 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 spec's own layout (SP 800-38C Sec 6.1 step 8): `ciphertext || tag`. -//! let mut ct_and_tag = vec![0u8; message.len() + 16]; -//! ToyCcm::::encrypt_out(&key, &nonce, header, message, &mut ct_and_tag).expect("encryption"); -//! -//! let mut recovered_plaintext = vec![0u8; message.len()]; -//! let n = ToyCcm::::decrypt_out(&key, &nonce, header, &ct_and_tag, &mut recovered_plaintext).expect("decryption"); -//! assert_eq!(&recovered_plaintext[..n], 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); //! -//! // If we tamper with any byte of the ciphertext, then this fails with a SymmetricCipherError::AEADTagCheckFailed -//! let mut tampered = ct_and_tag.clone(); +//! // Tampering with the ciphertext, or changing the AAD, fails the tag check. +//! let mut tampered = ciphertext.clone(); //! tampered[0] ^= 1; -//! assert_eq!(ToyCcm::::decrypt_out(&key, &nonce, header, &tampered, &mut recovered_plaintext).unwrap_err(), -//! SymmetricCipherError::AEADTagCheckFailed); -//! -//! // Same if we provide the correct ciphertext and tag, but change the authenticated data -//! assert_eq!(ToyCcm::::decrypt_out(&key, &nonce, b"other header", &ct_and_tag, &mut recovered_plaintext).unwrap_err(), -//! SymmetricCipherError::AEADTagCheckFailed); +//! assert_eq!(Mode12::decrypt_detached_in_place(&state, &nonce, header, &mut tampered, &tag).unwrap_err(), +//! SymmetricCipherError::AEADTagCheckFailed); +//! let mut other = ciphertext.clone(); +//! assert_eq!(Mode12::decrypt_detached_in_place(&state, &nonce, b"other header", &mut other, &tag).unwrap_err(), +//! SymmetricCipherError::AEADTagCheckFailed); //! ``` //! //! # CCM is not a stream cipher @@ -70,18 +71,16 @@ //! > storage before CCM is applied; CCM is not designed to support partial processing or stream //! > processing. //! -//! This is different from the `do_encrypt_inplace()` mode, often referred to as a "streaming mode" where -//! the content is processed in batches; so long as the total expected length is known up-front. -//! The AAD can be processed in batches the same way, if its length is declared up-front too; see -//! [`CcmPacket::new_with_lengths`]. +//! The streaming methods still take the payload in pieces, and the AAD too, within the frame the +//! type declares. //! //! # Suspending and resuming execution //! -//! [`CcmPacket`] 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 CBC-MAC -//! chaining value and the AAD and payload still owed; the permutation is rebuilt from the key. The -//! array length is `CcmPacket::SUSPENDED_STATE_LEN`; see [the crate -//! docs](crate#suspending-and-resuming-execution) for an example. +//! [`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 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 🚨 //! @@ -109,14 +108,16 @@ use crate::modes::ctr::apply_counter_blocks; use crate::modes::iv::random_iv; use crate::stream::StreamCipher; use bouncycastle_core::errors::{SuspendableError, SymmetricCipherError}; -use bouncycastle_core::hazmat::{ElectronicCodeBook, KeyStream}; -use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::hazmat::{ + AeadDeclaredDecryptMode, AeadDeclaredEncryptMode, AeadDecryptMode, AeadEncryptMode, + BlockPermutation, ElectronicCodeBook, KeyStream, 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::{ @@ -129,64 +130,28 @@ 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. The -/// trait-implementing pair, [`CcmEncryptor`] / [`CcmDecryptor`], is built on this type. -/// -/// `Dir` is [`Encrypting`] or [`Decrypting`], `CcmPacket` has Sec 6.1's methods and -/// nothing else, and `CcmPacket` has Sec 6.2's. -/// -/// Asking an encryptor to verify a tag does not compile -- `do_decrypt_final` exists only on -/// `CcmPacket`: -/// -/// A nonce length A.1 does not permit does not compile: -/// -/// ```compile_fail -/// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_cipher::modes::CcmPacket; -/// use bouncycastle_cipher::Encrypting; -/// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .unwrap(); -/// // n = 6 is not in {7, ..., 13}: it would make q = 9, which A.1 does not allow. -/// let _ = CcmPacket::::new(&key, &[0u8; 6], &[], 0); -/// ``` -/// -/// Nor does an odd tag length: -/// -/// ```compile_fail -/// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } -/// # use toy::ToyBlockCipher; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_cipher::modes::CcmPacket; -/// use bouncycastle_cipher::Encrypting; +/// AAD and payload lengths are declared per message, and the nonce is the caller's. Crate-private: +/// 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. /// -/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) -/// .unwrap(); -/// // t = 15 is not in {4, 6, 8, 10, 12, 14, 16}. -/// let _ = CcmPacket::::new(&key, &[0u8; 12], &[], 0); -/// ``` +/// `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 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)`. @@ -210,41 +175,28 @@ pub 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; - - /// The `N` of this type's [`SuspendableKeyed`] impl: the version header, the CTR state, - /// the CBC-MAC chaining block and three counts as `u64`s. See [`bouncycastle_utils::suspendable_state`]. - pub const SUSPENDED_STATE_LEN: usize = - LIB_VERSION_LEN + ::STATE_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". /// /// `q = 8` would make `2^8q` exactly `2^64`, which does not fit a `u64`; there the bound is - /// `p <= 2^64 - 1`, i.e. `u64::MAX`, which is no bound at all on a `usize` length. Public so a - /// caller choosing a `DATA_LEN` for [`CcmEncryptor`] / [`CcmDecryptor`], or reporting the - /// limit in an error message, has the real number instead of re-deriving it. - pub const MAX_PAYLOAD_LEN: u64 = + /// `p <= 2^64 - 1`, i.e. `u64::MAX`, which is no bound at all on a `usize` length. Callers + /// outside the crate have it as [`CCM::MAX_PAYLOAD_LEN`]. + pub(crate) const MAX_PAYLOAD_LEN: u64 = if Self::Q_LEN >= 8 { u64::MAX } else { (1u64 << (8 * Self::Q_LEN)) - 1 }; /// The compile-time shape check, from Appendix A.1 and Sec 5.1; run from the constructor. @@ -275,98 +227,39 @@ 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. For an AAD that is not all in hand at once, see - /// [`Self::new_with_lengths`], which declares its length instead and takes it in pieces. - /// - /// * `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. + /// 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::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 fn new( - key: &KeyMaterial, + /// [`SymmetricCipherError::GenericError`] if `payload_len` exceeds A.1's `2^8q - 1`. + pub(crate) fn from_perm( + perm: &'k P, 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`], but with the AAD's length declared rather than the AAD itself, so that the - /// AAD can then be supplied in pieces through [`Self::do_update_aad`]. - /// - /// Both lengths are needed up front, and only the lengths. `B0` carries the payload length - /// (A.2.1), and A.2.2 formats the AAD as "the encoding of a [...] concatenated with the - /// associated data A", so the CBC-MAC cannot absorb the first AAD byte until it has absorbed - /// `B0` and the encoding of `a`. The AAD bytes themselves then go through the CBC-MAC as they - /// arrive, in any chunking. - /// - /// The flow is: this constructor, exactly `aad_len` bytes of AAD through - /// [`Self::do_update_aad`], then exactly `payload_len` bytes of payload, then the final. The - /// AAD must be complete before any payload: A.2.3 puts the payload blocks after the AAD blocks. - /// - /// ``` - /// # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } - /// # use toy::ToyBlockCipher; - /// use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; - /// use bouncycastle_cipher::modes::CcmPacket; - /// use bouncycastle_cipher::Encrypting; - /// - /// type ToyCcm = CcmPacket; - /// - /// let key = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) - /// .expect("a 16-byte symmetric cipher key"); - /// let nonce = [0x01u8; 12]; - /// let header: [&[u8]; 2] = [b"version: 1; ", b"route: a->b"]; - /// let mut message = *b"attack at dawn"; - /// - /// let aad_len = header.iter().map(|part| part.len()).sum(); - /// let mut ccm = - /// ToyCcm::::new_with_lengths(&key, &nonce, aad_len, message.len()).unwrap(); - /// for part in header { - /// ccm.do_update_aad(part).unwrap(); - /// } - /// ccm.do_encrypt_inplace(&mut message).unwrap(); - /// let tag = ccm.do_encrypt_final().unwrap(); - /// - /// // The same as supplying the AAD whole. - /// let mut whole = *b"attack at dawn"; - /// let mut ccm = ToyCcm::::new(&key, &nonce, b"version: 1; route: a->b", 14).unwrap(); - /// ccm.do_encrypt_inplace(&mut whole).unwrap(); - /// assert_eq!((message, tag), (whole, ccm.do_encrypt_final().unwrap())); - /// ``` + /// Draws a nonce for an encryption, under the floor every drawing form has: the + /// random-collision bound is only useful from 96 bits up, so a `NONCE_LEN` below 12 is a + /// compile error at the call that would draw, and nowhere else. /// /// # Errors - /// As [`Self::new`]. - pub fn new_with_lengths( - key: &KeyMaterial, - nonce: &[u8; NONCE_LEN], - aad_len: usize, - payload_len: usize, - ) -> Result { - let perm = P::new(key)?; - Self::from_perm_with_lengths(perm, nonce, aad_len, payload_len) + /// [`SymmetricCipherError::RNGError`] if the draw fails. + pub(crate) fn random_nonce(rng: &mut dyn RNG) -> Result<[u8; NONCE_LEN], SymmetricCipherError> { + const { + assert!( + NONCE_LEN >= 12, + "CCM: a generated nonce needs NONCE_LEN >= 12; supply the nonce for a shorter one" + ); + } + random_iv::(rng) } - /// Supplies the next piece of the AAD declared to [`Self::new_with_lengths`]. A sequence of + /// Supplies the next piece of the AAD whose length was declared at construction. A sequence of /// calls is equivalent to one call over the concatenation. An empty `aad` is a no-op. /// /// When the last declared byte arrives, the AAD is zero-padded to a block boundary (A.2.2's @@ -375,9 +268,9 @@ 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 fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { + pub(crate) fn do_update_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { if aad.len() > self.aad_owed { return Err(SymmetricCipherError::StateError( "CCM was given more AAD than the declared AAD length, which the AAD length \ @@ -388,8 +281,8 @@ where Ok(()) } - /// [`Self::do_update_aad`] without its check: `aad` must be at most the AAD still owed, which - /// the caller has established. An empty `aad` is a no-op. + /// Absorbs `aad`, which must be at most the AAD still owed, which the caller has + /// established. An empty `aad` is a no-op. fn absorb_aad(&mut self, aad: &[u8]) { if aad.is_empty() { return; @@ -403,11 +296,11 @@ where } } - /// As [`Self::new_with_lengths`], from a key schedule that has already been expanded: formats - /// and absorbs `B0` and, if there is any AAD, the encoding of its length, leaving the AAD - /// itself to [`Self::do_update_aad`]. + /// From a key schedule that has already been expanded, with the AAD's length declared rather + /// 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, @@ -426,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 { + /// `Self::MAX_PAYLOAD_LEN`; the adapters assert theirs at compile time. + 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)), @@ -458,7 +351,7 @@ where // A.2.2: if `a > 0`, "the encoding of a is concatenated with the associated data A, // followed by the minimum number of '0' bits, possibly none, such that the resulting string // can be partitioned into 16-octet blocks". The encoding goes in now; `A` and the pad - // follow through `do_update_aad`. If `a = 0` there are no AAD blocks at all, so nothing is + // follow through `absorb_aad`. If `a = 0` there are no AAD blocks at all, so nothing is // absorbed and nothing is padded, and `B0` has already ended on a block boundary. if aad_len > 0 { let (encoded, encoded_len) = Self::encode_aad_len(aad_len as u64); @@ -539,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 } @@ -582,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 \ @@ -597,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(()) } @@ -614,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 @@ -631,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. /// @@ -643,9 +544,12 @@ where /// /// # Errors /// [`SymmetricCipherError::StateError`] if `data` would take the total past the declared - /// payload length, or if it is non-empty while AAD declared to [`Self::new_with_lengths`] is - /// still outstanding. Nothing is consumed in either case. - pub fn do_encrypt_inplace(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { + /// payload length, or if it is non-empty while declared AAD is still outstanding. Nothing is + /// consumed in either case. + pub(crate) fn do_encrypt_inplace( + &mut self, + data: &mut [u8], + ) -> Result<(), SymmetricCipherError> { self.take_owed(data.len())?; self.mac_absorb(data); self.ctr.do_encrypt_inplace(data)?; @@ -656,10 +560,9 @@ 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 - /// verifier could reproduce -- or less AAD than declared to [`Self::new_with_lengths`], for the - /// same reason. - pub fn do_encrypt_final(self) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + /// 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 { return Err(SymmetricCipherError::StateError( "CCM was given less AAD than the declared AAD length", @@ -672,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 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 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. /// @@ -748,9 +595,12 @@ where /// /// # Errors /// [`SymmetricCipherError::StateError`] if `data` would take the total past the declared - /// payload length, or if it is non-empty while AAD declared to [`Self::new_with_lengths`] is - /// still outstanding. Nothing is consumed in either case. - pub fn do_decrypt_inplace(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { + /// payload length, or if it is non-empty while declared AAD is still outstanding. Nothing is + /// consumed in either case. + pub(crate) fn do_decrypt_inplace( + &mut self, + data: &mut [u8], + ) -> Result<(), SymmetricCipherError> { self.take_owed(data.len())?; self.ctr.do_decrypt_inplace(data)?; self.mac_absorb(data); @@ -767,8 +617,8 @@ 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 to [`Self::new_with_lengths`]. - pub fn do_decrypt_final(self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError> { + /// 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( "CCM was given less AAD than the declared AAD length", @@ -785,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 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 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; @@ -873,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`. @@ -890,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`. @@ -905,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 @@ -918,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]); @@ -929,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) { @@ -949,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 } @@ -981,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, @@ -989,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. /// @@ -1000,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], @@ -1025,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)" ); } @@ -1059,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, @@ -1100,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 { @@ -1112,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`. @@ -1125,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 @@ -1138,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. @@ -1162,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"); @@ -1170,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()); /// ``` @@ -1179,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: @@ -1206,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`): @@ -1220,60 +998,94 @@ 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. +/// +/// A nonce length A.1 does not permit does not compile, whichever form is used: +/// +/// ```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_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 _ = CCM_Packet::<6, 16, 64, 256>::encrypt_init_nonce(&state, &[0u8; 6]); +/// ``` +/// +/// Nor does an odd tag length: +/// +/// ```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_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 _ = 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. @@ -1281,12 +1093,32 @@ 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. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the nonce draw fails; nothing else can. + pub(crate) fn from_perm( + perm: &'k P, + rng: &mut dyn RNG, + ) -> Result<(Self, [u8; NONCE_LEN]), SymmetricCipherError> { + Self::check_random_nonce(); + let nonce = random_iv::(rng)?; + Ok((Self::from_perm_nonce(perm, &nonce), nonce)) + } + + /// 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(crate) fn from_perm_nonce(perm: &'k P, nonce: &[u8; NONCE_LEN]) -> Self { + Self(CcmAdapter::new(perm, nonce)) + } + /// The floor for a generated nonce, run from every form that draws one: the random-collision /// bound is only useful from 96 bits up. A const block in a generic function is evaluated /// when that function is instantiated, so a `NONCE_LEN` below the floor is a compile error @@ -1297,46 +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> { - Self::check_random_nonce(); - // `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. - let perm = P::new(key)?; - let nonce = random_iv::(rng)?; - Ok((Self(CcmAdapter::new(perm, &nonce)), nonce)) - } - /// 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 @@ -1366,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( @@ -1391,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`. /// @@ -1446,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 @@ -1504,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 @@ -1596,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 🚨 /// @@ -1606,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. @@ -1630,40 +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(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,27 +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. - let perm = P::new(key)?; - Ok(Self { inner: CcmAdapter::new(perm, nonce), tag: [0u8; TAG_LEN], tag_len: 0 }) - } - /// 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 { @@ -1719,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)); } @@ -1733,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); @@ -1760,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) @@ -1825,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); @@ -1906,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,40 +1506,16 @@ where } } -/// `N` must be [`CcmPacket::SUSPENDED_STATE_LEN`]; anything else is a compile error. -impl< - P, - Dir, - const KEY_LEN: usize, - const BLOCK_LEN: usize, - const NONCE_LEN: usize, - const TAG_LEN: usize, - const N: usize, -> SuspendableKeyed for CcmPacket -where - P: ElectronicCodeBook + ModeNames, -{ - type Key = KeyMaterial; - - fn suspend(self) -> [u8; N] { - suspend_component(&self) - } - - fn from_suspended(state: [u8; N], key: &Self::Key) -> Result { - resume_component(&state, key) - } -} - /// 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); @@ -1968,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 @@ -1986,6 +1542,641 @@ where } } +/// 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 + /// `q = 15 - n`. At `n = 7`, `2^8q` is `2^64`, which does not fit a `u64`; there the bound is + /// `u64::MAX`, no bound at all on a `usize` length. + pub const MAX_PAYLOAD_LEN: u64 = + if 15 - NONCE_LEN >= 8 { u64::MAX } else { (1u64 << (8 * (15 - NONCE_LEN))) - 1 }; +} + +/// 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 CcmEncryptor<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize>( + Ccm<'k, P, Encrypting, 16, NONCE_LEN, TAG_LEN>, +) +where + 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 CcmDecryptor<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> +where + P: BlockPermutation<16> + ModeNames, +{ + 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<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> Algorithm + for CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ + 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 + } + + fn do_encrypt_out( + &mut self, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result { + 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)?; + Ok(plaintext.len()) + } + + fn do_encrypt_final(self) -> Result<([u8; TAG_LEN], usize), SymmetricCipherError> { + 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], + ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { + ciphertext.fill(0); + Ok((0, self.0.do_encrypt_final()?)) + } + + fn encrypt_detached_out_len(plaintext_len: usize) -> usize { + plaintext_len + } +} + +impl<'k, P, const NONCE_LEN: usize, const TAG_LEN: usize> Algorithm + for CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ + 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) + } + + fn do_decrypt_out( + &mut self, + 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 { + return Err(SymmetricCipherError::StateError( + "CCM was given more than the declared payload length and a tag", + )); + } + 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)?; + self.tag[self.tag_len..self.tag_len + rest].copy_from_slice(&ciphertext[take..]); + self.tag_len += rest; + Ok(take) + } + + /// Sec 6.2 step 1 on the inline layout: the ciphertext must be exactly the declared + /// payload and a whole tag, else it is malformed. + fn do_decrypt_final(self) -> Result<([u8; TAG_LEN], usize), SymmetricCipherError> { + if self.ccm.owed != 0 || self.tag_len != TAG_LEN { + return Err(SymmetricCipherError::DecryptionFailed); + } + self.ccm.do_decrypt_final(&self.tag)?; + 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, + tag: &[u8; TAG_LEN], + plaintext: &mut [u8; TAG_LEN], + ) -> Result { + plaintext.fill(0); + if self.ccm.owed != 0 || self.tag_len != 0 { + return Err(SymmetricCipherError::DecryptionFailed); + } + self.ccm.do_decrypt_final(tag)?; + Ok(0) + } + + 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 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 +{ + type KeyState = P; + const KEY_TYPE: KeyType = KeyType::SymmetricCipherKey; + + fn key_state(perm: P) -> Self::KeyState { + perm + } +} + +impl + AeadDeclaredEncryptMode for CCM +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Encryptor<'k> + = CcmEncryptor<'k, P, NONCE_LEN, TAG_LEN> + where + P: 'k; + + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + aad_len: usize, + payload_len: usize, + ) -> 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<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError> { + Ok(CcmEncryptor(Ccm::from_perm_with_lengths(state, nonce, aad_len, payload_len)?)) + } + + fn encrypt_detached_in_place_nonce( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + let mut ccm = + Ccm::::from_perm(state, nonce, aad, buf.len())?; + ccm.do_encrypt_inplace(buf)?; + ccm.do_encrypt_final() + } +} + +impl + AeadDeclaredDecryptMode for CCM +where + P: ElectronicCodeBook + ModeNames + Clone, +{ + type Decryptor<'k> + = CcmDecryptor<'k, P, NONCE_LEN, TAG_LEN> + where + P: 'k; + + fn decrypt_init<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError> { + Ok(CcmDecryptor { + ccm: Ccm::from_perm_with_lengths(state, nonce, aad_len, payload_len)?, + tag: [0u8; TAG_LEN], + tag_len: 0, + }) + } + + /// `buf` is untouched if the lengths are refused, and zeroized on any error after that: the + /// plaintext is in it before the tag is checked. + fn decrypt_detached_in_place( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + 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); + } + result + } +} + +/// 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 `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 BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> SuspendableComponent<'k> + for CcmAdapter<'k, P, Dir, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + const STATE_LEN: usize = + as SuspendableComponent<'k>>::STATE_LEN + + AAD_LEN + + 8 + + 1; + type Key = P; + + fn write_state(&self, out: &mut [u8]) { + let (ccm, rest) = out.split_at_mut( + as SuspendableComponent<'k>>::STATE_LEN, + ); + self.ccm.write_state(ccm); + let mut w = CursorMut::new(rest); + w.bytes(&self.aad); + w.u64(self.aad_len as u64); + w.u8(u8::from(self.formatted)); + debug_assert!(w.is_done()); + } + + fn read_state(state: &[u8], key: &'k Self::Key) -> Result { + Self::check_shape(); + let (ccm, rest) = state.split_at( + as SuspendableComponent<'k>>::STATE_LEN, + ); + 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)?; + let formatted = match r.u8() { + 0 => false, + 1 => true, + _ => return Err(SuspendableError::InvalidData), + }; + debug_assert!(r.is_done()); + // `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 }) + } +} + +impl< + 'k, + P, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> SuspendableComponent<'k> + for CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + const STATE_LEN: usize = 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: &'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 BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> SuspendableComponent<'k> + for CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + const STATE_LEN: usize = as SuspendableComponent<'k>>::STATE_LEN + + TAG_LEN + + 8; + type Key = P; + + fn write_state(&self, out: &mut [u8]) { + 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); + w.u64(self.tag_len as u64); + debug_assert!(w.is_done()); + } + + 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(); + let tag_len = bounded_usize(r.u64(), TAG_LEN)?; + debug_assert!(r.is_done()); + Ok(Self { inner, tag, tag_len }) + } +} + +impl< + 'k, + P, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + /// 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; +} + +impl< + 'k, + P, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, +> CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + /// 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; +} + +/// `N` must be [`CcmPacketEncryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. +impl< + 'k, + P, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, + const N: usize, +> SuspendableKeyed<'k, N> + for CcmPacketEncryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + type Key = P; + + fn suspend(self) -> [u8; N] { + suspend_component(&self) + } + + fn from_suspended(state: [u8; N], key: &'k Self::Key) -> Result { + resume_component(&state, key) + } +} + +/// `N` must be [`CcmPacketDecryptor::SUSPENDED_STATE_LEN`]; anything else is a compile error. +impl< + 'k, + P, + const BLOCK_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const AAD_LEN: usize, + const DATA_LEN: usize, + const N: usize, +> SuspendableKeyed<'k, N> + for CcmPacketDecryptor<'k, P, BLOCK_LEN, NONCE_LEN, TAG_LEN, AAD_LEN, DATA_LEN> +where + P: BlockPermutation + ModeNames, +{ + type Key = P; + + fn suspend(self) -> [u8; N] { + suspend_component(&self) + } + + 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 @@ -1997,7 +2188,7 @@ mod tests { //! the AAD length encoding against the document rather than against this implementation. use super::*; - use bouncycastle_core::key_material::KeyType; + use bouncycastle_core::key_material::{KeyMaterial, 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 @@ -2014,15 +2205,20 @@ mod tests { 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]) {} @@ -2054,7 +2250,7 @@ mod tests { fn c1_b0_matches_the_spec() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; assert_eq!( - CcmPacket::::format_b0(&nonce, true, 4), + 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" ); @@ -2065,14 +2261,14 @@ mod tests { #[test] fn adata_flag_is_bit_6_of_the_flags_octet() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let with = CcmPacket::::format_b0(&nonce, true, 4); - let without = CcmPacket::::format_b0(&nonce, false, 4); + let with = 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 [`CcmPacket::format_b0`] built. With the identity + /// 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`. /// @@ -2080,12 +2276,11 @@ mod tests { #[test] fn the_constructor_absorbs_b0() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; + let identity = Identity::new(&key()).unwrap(); let ccm = - CcmPacket::::new(&key(), &nonce, &[], 4).unwrap(); - assert_eq!( - *ccm.y, - CcmPacket::::format_b0(&nonce, false, 4) - ); + 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"); } @@ -2098,7 +2293,7 @@ mod tests { fn c4_b0_matches_the_spec() { let nonce = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; assert_eq!( - CcmPacket::::format_b0(&nonce, true, 32), + Ccm::::format_b0(&nonce, true, 32), [ 0x71, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x00, 0x20 @@ -2115,9 +2310,9 @@ mod tests { #[test] fn counter_blocks_match_the_spec() { let nonce_c1 = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16]; - let mut ks = CcmKeyStream::::from_perm(Identity, &nonce_c1); + let mut ks = CcmKeyStream::::from_perm(&Identity, &nonce_c1); // `Ctr0` is the template with a zero counter field. - let ctr0 = CcmKeyStream::::counter_block(&ks.ctr_template, 0); + let ctr0 = CcmKeyStream::::counter_block(&ks.ctr_template, 0); assert_eq!( ctr0, [0x07, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0, 0, 0, 0, 0, 0, 0, 0], @@ -2133,9 +2328,9 @@ mod tests { let nonce_c4 = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c]; - let ks4 = CcmKeyStream::::from_perm(Identity, &nonce_c4); + let ks4 = CcmKeyStream::::from_perm(&Identity, &nonce_c4); assert_eq!( - CcmKeyStream::::counter_block(&ks4.ctr_template, 0), + CcmKeyStream::::counter_block(&ks4.ctr_template, 0), [ 0x01, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x00, 0x00 @@ -2152,10 +2347,10 @@ mod tests { #[test] fn a_fresh_keystream_has_every_counter_but_ctr0_left() { // n = 13, so q = 2: counters 1 ..= 65535. - let ks = CcmKeyStream::::from_perm(Identity, &[0u8; 13]); + let ks = CcmKeyStream::::from_perm(&Identity, &[0u8; 13]); assert_eq!(ks.remaining_blocks(), 65535); // n = 7, so q = 8: counters 1 ..= 2^64 - 1, which is `u64::MAX` of them. - let ks = CcmKeyStream::::from_perm(Identity, &[0u8; 7]); + let ks = CcmKeyStream::::from_perm(&Identity, &[0u8; 7]); assert_eq!(ks.remaining_blocks(), u64::MAX); } @@ -2169,9 +2364,14 @@ mod tests { } use bouncycastle_core_test_framework::key_stream::TestFrameworkKeyStream; use toy::ToyBlockCipher; + let perm = ToyBlockCipher::new(&key()).unwrap(); let framework = TestFrameworkKeyStream::new(); - framework.test::<16, 7, 16, CcmKeyStream>(); - framework.test::<16, 13, 16, CcmKeyStream>(); + 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. @@ -2185,7 +2385,7 @@ mod tests { /// which through the public API would need a 4 GiB AAD. #[test] fn aad_length_encoding_matches_a_2_2() { - type Mode = CcmPacket; + type Mode<'k> = Ccm<'k, Identity, Encrypting, 16, 7, 4>; // Case 1: 0 < a < 2^16 - 2^8, two octets, `[a]_16`. assert_eq!( @@ -2238,18 +2438,20 @@ mod tests { } } - /// The constructor really uses [`CcmPacket::encode_aad_len`], and puts it *before* the AAD. + /// 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 = CcmPacket; + 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 ccm = Mode::new(&key(), &nonce, &[0u8; 14], 0).unwrap(); + 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]; 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 new file mode 100644 index 000000000..dcdc91278 --- /dev/null +++ b/crypto/cipher/src/modes/cmac.rs @@ -0,0 +1,378 @@ +//! CMAC, the block-cipher-based message authentication code of NIST SP 800-38B. +//! +//! [`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. +//! +//! 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, 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 toy_key::ToyKey; +//! use bouncycastle_cipher::modes::CMAC; +//! use bouncycastle_core::key_material::{KeyMaterial128, KeyType}; +//! use bouncycastle_core::symmetric_key::{FromKeyMaterial, MacGenerationKey, MacVerificationKey}; +//! +//! 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!(key.verify(b"a message", &tag)); +//! ``` +//! +//! 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 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 material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); +//! let key = ToyKey::::from_key_material(&material).unwrap(); +//! +//! 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]; +//! assert_eq!(cmac.do_final_out(&mut tag).unwrap(), 8); +//! ``` +//! +//! # 🚨 Security Considerations 🚨 +//! +//! * **Tag length** (Appendix A): a forger guesses a `Tlen`-bit tag with probability `2^-Tlen`, so +//! shorter tags need a limit on failed verifications. Truncation below 64 bits is refused +//! ([`CMAC_MIN_TAG_LEN`]). SP 800-38B Sec 5.5 also requires `Tlen` to be fixed for every use of +//! a key; that is the caller's to keep. +//! * **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 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::{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; +use bouncycastle_utils::secret::Secret; +use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; + +/// The shortest tag [`MAC::mac_out`] / [`MAC::do_final_out`] will truncate to: 64 bits, the length +/// SP 800-38B Appendix A.2 says "should provide sufficient protection against guessing attacks" for +/// most applications ("A value of Tlen that is less than 64 shall only be used in conjunction with +/// a careful analysis of the risks"). +pub const CMAC_MIN_TAG_LEN: usize = 8; + +/// 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; + +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. +#[derive(Clone)] +pub struct CmacKeyState { + perm: P, + /// Subkey `K1` (Sec 6.1 step 2), applied to a complete final block. + k1: Secret<[u8; BLOCK_LEN]>, + /// Subkey `K2` (Sec 6.1 step 3), applied to a padded final block. + 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. + const RB: u8 = match BLOCK_LEN { + 16 => 0x87, + 8 => 0x1B, + _ => 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): [`CMAC`]'s `Mode` impl. + pub(crate) fn new(perm: P) -> Self + where + 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`. + let mut l: Secret<[u8; BLOCK_LEN]> = Secret::new(); + perm.encrypt_block(&mut l); + // Steps 2 and 3. + let mut k1: Secret<[u8; BLOCK_LEN]> = Secret::new(); + Self::dbl(&l, &mut k1); + let mut k2: Secret<[u8; BLOCK_LEN]> = Secret::new(); + Self::dbl(&k1, &mut k2); + Self { perm, k1, k2 } + } + + /// Sec 6.1 steps 2 and 3: `out = x << 1` if `MSB_1(x) = 0`, else `(x << 1) (+) R_b`. The + /// conditional XOR is done with a mask rather than a branch, since `x` is secret. + fn dbl(x: &[u8; BLOCK_LEN], out: &mut [u8; BLOCK_LEN]) { + let msb = x[0] >> 7; + for i in 0..BLOCK_LEN - 1 { + // The two operands share no bits, so `|` and `^` are interchangeable here. + out[i] = (x[i] << 1) | (x[i + 1] >> 7); + } + out[BLOCK_LEN - 1] = (x[BLOCK_LEN - 1] << 1) ^ (Self::RB & 0u8.wrapping_sub(msb)); + } +} + +/// 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: BlockPermutation + ModeNames, +{ + 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 + /// `do_final` knows whether it is `M_n*` (step 4) and so which subkey it takes. + pending: Secret<[u8; BLOCK_LEN]>, + /// `0` only before any message byte has arrived; `1..=BLOCK_LEN` after. + pending_len: usize, +} + +impl<'k, P, const BLOCK_LEN: usize> Cmac<'k, P, BLOCK_LEN> +where + 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 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 } + } + + /// 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` + /// can be passed as `block` without first being copied out of its `Secret`. + fn absorb(perm: &P, c: &mut Secret<[u8; BLOCK_LEN]>, block: &[u8; BLOCK_LEN]) { + for (ci, mi) in c.iter_mut().zip(block.iter()) { + *ci ^= mi; + } + perm.encrypt_block(c); + } + + /// Sec 6.2 steps 4-6 for the final block, leaving `C_n` in `self.c`: `M_n = K1 (+) M_n*` if + /// `M_n*` is complete, else `M_n = K2 (+) (M_n* || 10^j)`. An empty message (step 2's `n = 1`) + /// is the incomplete case with an empty `M_n*`. + fn finish(&mut self) { + let subkey = if self.pending_len == BLOCK_LEN { + &self.key.k1 + } else { + self.pending[self.pending_len] = 0x80; + self.pending[self.pending_len + 1..].fill(0); + &self.key.k2 + }; + for (m, k) in self.pending.iter_mut().zip(subkey.iter()) { + *m ^= k; + } + Self::absorb(&self.key.perm, &mut self.c, &self.pending); + } +} + +impl<'k, P, const BLOCK_LEN: usize> Algorithm for Cmac<'k, P, BLOCK_LEN> +where + P: BlockPermutation + ModeNames, +{ + const ALG_NAME: &'static str = P::CMAC_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} + +impl<'k, P, const BLOCK_LEN: usize> MAC for Cmac<'k, P, BLOCK_LEN> +where + P: BlockPermutation + ModeNames, +{ + fn output_len(&self) -> usize { + BLOCK_LEN + } + + fn mac(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + fn mac_out(mut self, data: &[u8], out: &mut [u8]) -> Result { + self.do_update(data); + self.do_final_out(out) + } + + fn verify(mut self, data: &[u8], mac: &[u8]) -> bool { + self.do_update(data); + self.do_verify_final(mac) + } + + /// Absorbs every complete block except the last one seen, which stays pending until more data + /// arrives or [`MAC::do_final`] decides which subkey it takes. + fn do_update(&mut self, mut data: &[u8]) { + let take = (BLOCK_LEN - self.pending_len).min(data.len()); + self.pending[self.pending_len..self.pending_len + take].copy_from_slice(&data[..take]); + self.pending_len += take; + data = &data[take..]; + if data.is_empty() { + return; + } + + // More data follows, so the (full) pending block is not the last one. + Self::absorb(&self.key.perm, &mut self.c, &self.pending); + while data.len() > BLOCK_LEN { + let (block, rest) = + data.split_first_chunk::().expect("data.len() > BLOCK_LEN"); + Self::absorb(&self.key.perm, &mut self.c, block); + data = rest; + } + self.pending[..data.len()].copy_from_slice(data); + self.pending_len = data.len(); + } + + /// The full `BLOCK_LEN`-byte tag. + fn do_final(mut self) -> Vec { + self.finish(); + self.c.to_vec() + } + + /// Sec 6.2 step 7, `T = MSB_Tlen(C_n)`, with `Tlen` the length of `out` up to `BLOCK_LEN`. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than [`CMAC_MIN_TAG_LEN`]. + fn do_final_out(mut self, out: &mut [u8]) -> Result { + out.fill(0); + if out.len() < CMAC_MIN_TAG_LEN { + return Err(MACError::InvalidLength( + "CMAC: tags shorter than 64 bits are not supported (SP 800-38B Appendix A.2)", + )); + } + self.finish(); + let n = out.len().min(BLOCK_LEN); + out[..n].copy_from_slice(&self.c[..n]); + Ok(n) + } + + /// Sec 6.3: recomputes the full tag and compares it in constant time. A truncated `mac` never + /// matches, as the trait requires. + fn do_verify_final(mut self, mac: &[u8]) -> bool { + self.finish(); + mac.len() == BLOCK_LEN && ct_eq_bytes(mac, &self.c[..]) + } +} + +/// 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<'k, P, const BLOCK_LEN: usize, const STATE_LEN: usize> SuspendableKeyed<'k, STATE_LEN> + for Cmac<'k, P, BLOCK_LEN> +where + P: BlockPermutation + ModeNames, +{ + type Key = CmacKeyState; + + fn suspend(self) -> [u8; STATE_LEN] { + const { + assert!( + STATE_LEN == 3 + 2 * BLOCK_LEN + 1, + "STATE_LEN must be Cmac::SUSPENDED_STATE_LEN" + ) + }; + let mut state = [0u8; STATE_LEN]; + let body = add_lib_ver(&mut state); + body[..BLOCK_LEN].copy_from_slice(&self.c[..]); + body[BLOCK_LEN..2 * BLOCK_LEN].copy_from_slice(&self.pending[..]); + // `pending_len <= BLOCK_LEN <= 16`, so it fits in a byte. + body[2 * BLOCK_LEN] = self.pending_len as u8; + state + } + + /// # Errors + /// [`SuspendableError::IncompatibleVersion`] for a state from an incompatible library version, + /// 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, + "STATE_LEN must be Cmac::SUSPENDED_STATE_LEN" + ) + }; + let body = check_lib_ver(&state, None)?; + let pending_len = body[2 * BLOCK_LEN] as usize; + if pending_len > BLOCK_LEN { + return 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; + Ok(cmac) + } +} 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 b4f582d41..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}; -//! -//! type ToyGcm

= Gcm; +//! 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 = 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}; +//! use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +//! use bouncycastle_cipher::modes::GCM; +//! use bouncycastle_rng::HashDRBG_SHA512; //! -//! type ToyGcm = Gcm; -//! -//! 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,25 +186,26 @@ //! 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): feed only AAD and call -//! `do_encrypt_final_detached`: there is no separate `Gmac` type. +//! * **GMAC is GCM with no plaintext** (Sec 5.2): [`Gmac`](crate::modes::gmac) is this type fed +//! only AAD, with the plaintext-free API. 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,6 +218,110 @@ 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 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, 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(crate) fn new(perm: P) -> Self + where + 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). + let mut h: Secret<[u8; 16]> = Secret::new(); + perm.encrypt_block(&mut h); + Self { perm, h } + } +} + /// Which category of bytes `Gcm` is currently absorbing into GHASH: additional authenticated data, /// or plaintext/ciphertext. AAD is only accepted in the first phase (SP 800-38D Algorithm 4 absorbs /// `A` before `C`); the transition also pads the AAD to a block boundary (the `0^v` of step 5). @@ -211,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)`, @@ -241,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 @@ -269,14 +407,11 @@ where }; } - /// Algorithm 4 steps 1-2 and the precomputation for step 6's tag mask. - fn setup(perm: P, nonce: [u8; GCM_NONCE_LEN]) -> Self { + /// Algorithm 4 step 2 and the precomputation for step 6's tag mask, over a key state that + /// 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(); - - // Step 1: H = CIPH_K(0^128). 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). - let mut h: Secret<[u8; 16]> = Secret::new(); - perm.encrypt_block(&mut h); + let GcmKeyState { perm, h } = state; // Step 2 (len(IV) = 96 branch, the only one this type implements): J0 = IV || 0^31 || 1, // built directly in the `Secret` that will hold CIPH_K(J0). @@ -294,7 +429,7 @@ where Self { ctr, - ghash: Ghash::new(&h), + ghash: Ghash::new(h), ek_j0, aad_len: 0, data_len: 0, @@ -309,7 +444,7 @@ where /// `AEADCipher*::do_update_aad`. Any number of calls before the first `do_update_out`; a /// non-empty call after data has started is [`SymmetricCipherError::StateError`] (Algorithm 4 /// absorbs `A` before `C` in one GHASH pass, D4). Empty AAD is always a no-op. - fn absorb_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { + pub(crate) fn absorb_aad(&mut self, aad: &[u8]) -> Result<(), SymmetricCipherError> { if self.phase == Phase::Data { if aad.is_empty() { return Ok(()); @@ -319,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) { @@ -341,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(()) } @@ -355,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; @@ -370,34 +523,58 @@ 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 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(crate) fn from_key_state( + state: &'k GcmKeyState

, + rng: &mut dyn RNG, + ) -> Result<(Self, [u8; GCM_NONCE_LEN]), SymmetricCipherError> { + Self::check_shape(); + let nonce = crate::modes::iv::random_iv::(rng)?; + Ok((Self::setup_from_state(state, nonce), 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) } /// Algorithm 4 steps 4-6: finishes the message and returns the detached authentication tag, /// truncated to `TAG_LEN` bytes (`MSB_t`, step 6). Consumes the encryptor. - fn finish(mut self) -> [u8; TAG_LEN] { + pub(crate) fn finish(mut self) -> [u8; TAG_LEN] { // Covers an AAD-only or entirely empty message, where no data was ever encrypted. self.begin_data_if_needed(); let mut full: Secret<[u8; 16]> = Secret::new(); @@ -408,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 @@ -445,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()) @@ -463,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, @@ -495,10 +642,17 @@ 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 [`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 /// reverse of the encryptor's: GHASH must see ciphertext on both sides, so it is absorbed /// *before* GCTR turns it into plaintext here. @@ -509,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(()) @@ -520,7 +675,7 @@ where /// /// # Errors /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not match. - fn finish(mut self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError> { + pub(crate) fn finish(mut self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError> { self.begin_data_if_needed(); let mut full: Secret<[u8; 16]> = Secret::new(); self.tag_block(&mut full); @@ -530,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) @@ -592,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 @@ -642,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) @@ -686,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) { @@ -694,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); @@ -767,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()); @@ -801,18 +858,19 @@ 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) } } @@ -1078,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 new file mode 100644 index 000000000..1a5c816d8 --- /dev/null +++ b/crypto/cipher/src/modes/gmac.rs @@ -0,0 +1,215 @@ +//! GMAC, the authentication-only specialization of GCM (NIST SP 800-38D Sec 5.2): "An +//! implementation may restrict the input to the non-confidential data, i.e., without any +//! confidential data. The resulting variant of GCM is called GMAC. For GMAC, the authenticated +//! encryption and decryption functions become the functions for generating and verifying an +//! authentication tag on the non-confidential data." +//! +//! [`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<'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 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`, 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 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 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; +//! +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let material = KeyMaterial128::from_bytes_as_type(&[0x42; 16], KeyType::MACKey).unwrap(); +//! let key = ToyKey::::from_key_material(&material).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 = key.create_verifier(&nonce); +//! verifier.do_update(b"a message in two parts"); +//! assert!(verifier.do_verify_final(&tag)); +//! ``` +//! +//! # 🚨 Security Considerations 🚨 +//! +//! Everything in the [`gcm`](crate::modes::gcm) module's security considerations applies: above +//! all, a nonce repeated under one key lets an attacker recover the hash subkey `H` and forge tags +//! (SP 800-38D Sec 8 and Appendix A), and the key is limited to 2^32 invocations (Sec 8.3). The +//! `_nonce` constructors put the uniqueness obligation on the caller. +//! +//! [`MAC`]: bouncycastle_core::traits::MAC +//! [`KeyType::MACKey`]: bouncycastle_core::key_material::KeyType::MACKey + +use crate::modes::ModeNames; +use crate::modes::gcm::{GCM_NONCE_LEN, Gcm, GcmKeyState}; +use crate::{Decrypting, Encrypting}; +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::{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`] 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<'k, P, Dir, const TAG_LEN: usize> +where + P: BlockPermutation<16> + ModeNames, +{ + 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"; + +impl<'k, P, Dir, const TAG_LEN: usize> Algorithm for Gmac<'k, P, Dir, TAG_LEN> +where + P: BlockPermutation<16> + ModeNames, +{ + const ALG_NAME: &'static str = P::GMAC_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = P::MAX_SECURITY_STRENGTH; +} + +impl<'k, P, const TAG_LEN: usize> Gmac<'k, P, Encrypting, TAG_LEN> +where + 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(crate) fn from_key_state( + state: &'k GcmKeyState

, + rng: &mut dyn 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(crate) fn from_key_state_nonce( + state: &'k GcmKeyState

, + nonce: &[u8; GCM_NONCE_LEN], + ) -> Self { + Self { gcm: Gcm::from_key_state_nonce(state, nonce) } + } + + /// Absorbs more of the message. + pub fn do_update(&mut self, data: &[u8]) { + self.gcm.absorb_aad(data).expect(AAD_INFALLIBLE); + } + + /// The tag over everything passed to [`do_update`](Self::do_update). + pub fn do_final(self) -> [u8; TAG_LEN] { + self.gcm.finish() + } + + /// One-shot: the tag over `data`, consuming the generator. + pub fn mac(mut self, data: &[u8]) -> [u8; TAG_LEN] { + self.do_update(data); + self.do_final() + } +} + +impl<'k, P, const TAG_LEN: usize> Gmac<'k, P, Decrypting, TAG_LEN> +where + 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(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. + pub fn do_update(&mut self, data: &[u8]) { + self.gcm.absorb_aad(data).expect(AAD_INFALLIBLE); + } + + /// Whether `tag` is the tag over everything passed to [`do_update`](Self::do_update), + /// compared in constant time. + pub fn do_verify_final(self, tag: &[u8; TAG_LEN]) -> bool { + self.gcm.finish(tag).is_ok() + } + + /// One-shot: whether `tag` is the tag over `data`, consuming the verifier. + pub fn verify(mut self, data: &[u8], tag: &[u8; TAG_LEN]) -> bool { + self.do_update(data); + self.do_verify_final(tag) + } +} 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 238dd6006..b6f4335cb 100644 --- a/crypto/cipher/src/modes/mod.rs +++ b/crypto/cipher/src/modes/mod.rs @@ -13,11 +13,13 @@ //! |---|---|---|---| //! | CBC | [`cbc`] | SP 800-38A Sec 6.2 | Cipher Block Chaining | //! | CCM | [`ccm`] | SP 800-38C | Counter with CBC-MAC. **Authenticated**: CTR plus CBC-MAC, with a tag and AAD | +//! | CMAC | [`cmac`] | SP 800-38B | A MAC ([`MAC`]), not a cipher: 64- or 128-bit block ciphers, tags truncatable to 64 bits | //! | CFB | [`cfb`] | SP 800-38A Sec 6.3 | Cipher Feedback, full-block segment (`s = b`), i.e. CFB128 for AES | //! | CFB8 | [`cfb8`] | SP 800-38A Sec 6.3 | Cipher Feedback, 8-bit segment (`s = 8`) | //! | CTR | [`ctr`] | SP 800-38A Sec 6.5 | Counter. Nonce plus counter, both directions parallel | //! | ECB | [`hazmat`] | SP 800-38A Sec 6.1 | Electronic Codebook. **Not confidential for data**; interoperability and test vectors only | //! | GCM | [`gcm`] | SP 800-38D | **Authenticated**: 96-bit nonce, 96-128-bit tag, no padding; AAD before data | +//! | GMAC | [`gmac`] | SP 800-38D Sec 5.2 | A MAC, not a cipher: GCM with no plaintext, so the tag is bound to a 96-bit nonce | //! //! They divide three ways. //! @@ -41,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. //! @@ -65,34 +76,37 @@ //! `AES_GCM_128` and friends from `bouncycastle-aes`. Those aliases are not all the same shape: the //! two block modes take a padding scheme as well as a direction, since neither is usable on data of //! arbitrary length without one, the three stream modes take only the direction, CCM takes the -//! direction too, plus its nonce and tag lengths and, for the trait-implementing alias, its AAD -//! capacity and frame length (`AES_CCM_128_Packet` is the inherent, per-message-length API), and -//! GCM takes the direction and its tag length: +//! direction in its type name, plus its nonce and tag lengths, its AAD capacity and its frame +//! length, and GCM takes the direction and its tag length: //! //! ``` //! # mod toy { include!("../../tests/common/toy_block_cipher.rs"); } //! # use toy::ToyBlockCipher; -//! use bouncycastle_cipher::modes::{Cbc, CcmPacket, 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, a direction, key length, and a block length like the rest, -//! // plus the nonce length and the tag length -- both CCM-specific choices rather than cipher params. +//! // 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. -//! type ToyCcm = CcmPacket; +//! // 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<'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 @@ -147,17 +161,31 @@ pub mod cbc; pub mod ccm; pub mod cfb; pub mod cfb8; +pub mod cmac; pub mod ctr; pub mod gcm; +pub mod gmac; pub mod hazmat; mod iv; -pub use cbc::Cbc; -pub use ccm::{CcmDecryptor, CcmEncryptor, CcmPacket}; -pub use cfb::Cfb; -pub use cfb8::Cfb8; -pub use ctr::Ctr; -pub use gcm::{GCM_NONCE_LEN, Gcm}; +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, 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. /// @@ -165,18 +193,22 @@ pub use gcm::{GCM_NONCE_LEN, Gcm}; pub trait ModeNames { /// Reported by [`Cbc`]. const CBC_ALG_NAME: &'static str; - /// Reported by [`CcmPacket`], [`CcmEncryptor`] and [`CcmDecryptor`]. + /// Reported by [`CcmPacketEncryptor`] and [`CcmPacketDecryptor`]. const CCM_ALG_NAME: &'static str; /// Reported by [`Cfb`]. const CFB_ALG_NAME: &'static str; /// Reported by [`Cfb8`]. const CFB8_ALG_NAME: &'static str; + /// Reported by [`Cmac`]. + const CMAC_ALG_NAME: &'static str; /// Reported by [`Ctr`], through its keystream. const CTR_ALG_NAME: &'static str; /// Reported by [`hazmat::ecb::Ecb`]. const ECB_ALG_NAME: &'static str; /// Reported by [`Gcm`]. const GCM_ALG_NAME: &'static str; + /// Reported by [`Gmac`]. + const GMAC_ALG_NAME: &'static str; } // Imports needed for docs @@ -185,7 +217,7 @@ use bouncycastle_core::hazmat::ElectronicCodeBook; #[allow(unused_imports)] use bouncycastle_core::traits::{ AEADCipherDecryptor, AEADCipherEncryptor, Algorithm, BlockCipherDecryptor, - BlockCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + BlockCipherEncryptor, MAC, StreamCipherDecryptor, StreamCipherEncryptor, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; // end of imports needed for docs diff --git a/crypto/cipher/src/modes/possible_enhancements.md b/crypto/cipher/src/modes/possible_enhancements.md index da3c8e53c..f3b1dad0c 100644 --- a/crypto/cipher/src/modes/possible_enhancements.md +++ b/crypto/cipher/src/modes/possible_enhancements.md @@ -13,6 +13,6 @@ Possible additional modes or features to be added to this crate: protocol to bound packet sizes and invocation counts, which this crate cannot enforce. * **CCM with a formatting function other than Appendix A's.** SP 800-38C Sec 5.4 allows alternatives and says "Alternative formatting functions may be developed in the future"; - Appendix A's is the only one that exists in practice and the only one [`CcmPacket`] implements. + Appendix A's is the only one that exists in practice and the only one the mode implements. * Add GMAC and CMAC as top-level [`MAC']'s -- note that these behave differently to generic [MAC]s since they require - both a key and a (non-zero) IV. \ No newline at end of file + both a key and a (non-zero) IV. diff --git a/crypto/cipher/src/padding/padded_block_cipher.rs b/crypto/cipher/src/padding/padded_block_cipher.rs index fe42b95b2..a037a47ac 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, + E: BlockCipherEncryptor, 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, + 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, + 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, + D: BlockCipherDecryptor, 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, + 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, + 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 21d93ccaa..bf6addd76 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, + KS: KeyStream, { /// 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, + KS: KeyStream, { - /// 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, + KS: KeyStream, { /// 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, + KS: KeyStream, { - /// 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, + KS: KeyStream, { /// 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 28dace9ae..29367ed00 100644 --- a/crypto/cipher/tests/ccm_suspend_tests.rs +++ b/crypto/cipher/tests/ccm_suspend_tests.rs @@ -1,70 +1,109 @@ -//! What a resumed CCM state refuses: the bounds `CcmPacket` and its keystream check when they are -//! rebuilt from a suspended array, each tampered with on its own. +//! 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 and the owed payload all validated in one `if`, 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 keystream (counter template, counter index), the stream cipher's pending -//! block and its `used` count, then the chaining block, `mac_pos`, `aad_owed` and `owed`, every -//! integer a little-endian `u64`. Spec references are to NIST SP 800-38C (May 2004, errata -//! update 07-20-2007). +//! 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::CcmPacket; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_cipher::modes::{CCM, CCM_Packet, CcmPacketDecryptor, CcmPacketEncryptor}; use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::SuspendableKeyed; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, SuspendableKeyed, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, +}; 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`. const NONCE_LEN: usize = 12; const BLOCK_LEN: usize = 16; -type ToyCcm = CcmPacket; -const N: usize = ToyCcm::::SUSPENDED_STATE_LEN; - -/// A.1's `2^8q - 1`, which bounds both the owed payload and, since there is one counter block per -/// payload block, the counter field; `MAX_COUNTER` is the same number for every `q < 8`. -const LIMIT: u64 = ToyCcm::::MAX_PAYLOAD_LEN; +const AAD_LEN: usize = 16; +const DATA_LEN: usize = 32; +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; + +/// A.1's `2^8q - 1`, which bounds the counter field: there is one counter block per payload +/// block, so `MAX_COUNTER` is the same number for every `q < 8`. +const LIMIT: u64 = CCM::::MAX_PAYLOAD_LEN; // Field offsets within the suspended array. const TEMPLATE: usize = LIB_VERSION_LEN; const NEXT_CTR: usize = TEMPLATE + BLOCK_LEN; -const MAC_POS: usize = N - 24; -const OWED: usize = N - 8; +const PACKET_END: usize = N - AAD_LEN - 8 - 1; +const MAC_POS: usize = PACKET_END - 24; +const AAD_OWED: usize = PACKET_END - 16; +const OWED: usize = PACKET_END - 8; +const AAD_LEN_AT: usize = PACKET_END + AAD_LEN; +const FORMATTED: usize = AAD_LEN_AT + 8; +const TAG_LEN_AT: usize = ND - 8; fn key() -> KeyMaterial<16> { KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap() } -/// A freshly constructed encryptor's state: `next_ctr = 1`, `mac_pos = 0` after `B0`, `owed` as -/// declared. -fn fresh(payload_len: usize) -> [u8; N] { - ToyCcm::::new(&key(), &[0x24u8; NONCE_LEN], b"aad", payload_len).unwrap().suspend() +/// 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 key = frame_key(); + let mut enc = key.create_encryptor_nonce(&[0x24u8; NONCE_LEN]); + enc.do_update_aad(b"aad").unwrap(); + enc.suspend() } -fn with_u64(mut state: [u8; N], at: usize, value: u64) -> [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 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(); + enc.suspend() +} + +fn with_u64(mut state: [u8; L], at: usize, value: u64) -> [u8; L] { state[at..at + 8].copy_from_slice(&value.to_le_bytes()); state } fn resumes(state: [u8; N]) -> Result<(), SuspendableError> { - ToyCcm::::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 /// enciphered at once, so `BLOCK_LEN - 1` is the largest value a real state can hold. #[test] fn mac_pos_is_held_below_the_block_length() { - let state = fresh(32); + let state = mid_payload(); assert!(resumes(with_u64(state, MAC_POS, (BLOCK_LEN - 1) as u64)).is_ok()); assert_eq!( resumes(with_u64(state, MAC_POS, BLOCK_LEN as u64)), @@ -72,21 +111,26 @@ fn mac_pos_is_held_below_the_block_length() { ); } -/// `owed` is what `B0` committed to minus what has been supplied, so it can never exceed A.1's -/// `2^8q - 1`; the limit itself is a legal value, since nothing has to have been supplied yet. +/// `owed` is what `B0` committed to minus what has been supplied, so it can never exceed the +/// frame; the frame itself is a legal value, since nothing has to have been supplied yet. The +/// packet's own bound, A.1's `2^8q - 1`, sits behind that. #[test] -fn owed_is_held_to_the_payload_limit() { - let state = fresh(32); - assert!(resumes(with_u64(state, OWED, LIMIT)).is_ok()); +fn owed_is_held_to_the_frame() { + let state = mid_payload(); + assert!(resumes(with_u64(state, OWED, DATA_LEN as u64)).is_ok()); + assert_eq!( + resumes(with_u64(state, OWED, DATA_LEN as u64 + 1)), + Err(SuspendableError::InvalidData) + ); assert_eq!(resumes(with_u64(state, OWED, LIMIT + 1)), Err(SuspendableError::InvalidData)); } /// The counter index runs from 1 (`S0` is the tag mask, step 7 starts the payload keystream at /// `S1`) to one past the last counter value, which is where it stands once every block has been -/// used; 0 and anything beyond are not states a `CcmPacket` can have been in. +/// used; 0 and anything beyond are not states the mode can have been in. #[test] fn the_counter_index_is_held_to_its_range() { - let state = fresh(32); + let state = mid_payload(); assert!(resumes(with_u64(state, NEXT_CTR, 1)).is_ok()); assert!(resumes(with_u64(state, NEXT_CTR, LIMIT + 1)).is_ok(), "every counter used"); assert_eq!(resumes(with_u64(state, NEXT_CTR, 0)), Err(SuspendableError::InvalidData)); @@ -99,7 +143,7 @@ fn the_counter_index_is_held_to_its_range() { /// non-zero byte in each position of the counter field. #[test] fn the_counter_template_is_checked_byte_by_byte() { - let state = fresh(32); + let state = mid_payload(); assert!(resumes(state).is_ok()); assert_eq!(state[TEMPLATE], 2, "q - 1 for a 12-byte nonce"); @@ -125,14 +169,62 @@ fn the_counter_template_is_checked_byte_by_byte() { assert!(resumes(nonce_byte).is_ok()); } -/// The same checks run for the decrypting direction, which shares the impl. +/// Until the header is formatted nothing has gone into the CBC-MAC and no AAD is owed to it, so +/// a state that says "not formatted" with either count non-zero is one the pair never produces. +/// The flag itself is a bool, and only 0 and 1 encode one. #[test] -fn the_decrypting_direction_checks_the_same_bounds() { - let state = - ToyCcm::::new(&key(), &[0x24u8; NONCE_LEN], b"aad", 32).unwrap().suspend(); - assert!(ToyCcm::::from_suspended(state, &key()).is_ok()); - assert!( - ToyCcm::::from_suspended(with_u64(state, OWED, LIMIT + 1), &key()).is_err() +fn an_unformatted_state_has_absorbed_nothing() { + let state = unformatted(); + assert!(resumes(state).is_ok()); + assert_eq!(state[FORMATTED], 0); + assert_eq!(resumes(with_u64(state, MAC_POS, 1)), Err(SuspendableError::InvalidData)); + assert_eq!(resumes(with_u64(state, AAD_OWED, 1)), Err(SuspendableError::InvalidData)); + let mut not_a_bool = state; + not_a_bool[FORMATTED] = 2; + assert_eq!(resumes(not_a_bool), Err(SuspendableError::InvalidData)); + // The formatted state carries those counts legitimately. + 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() { + let state = unformatted(); + assert!(resumes(with_u64(state, AAD_LEN_AT, AAD_LEN as u64)).is_ok()); + assert_eq!( + resumes(with_u64(state, AAD_LEN_AT, AAD_LEN as u64 + 1)), + Err(SuspendableError::InvalidData) ); - assert!(ToyCcm::::from_suspended(with_u64(state, NEXT_CTR, 0), &key()).is_err()); +} + +/// The same checks run for the decrypting direction, which shares the adapter, plus the bound +/// on the tag bytes it holds back. +#[test] +fn the_decrypting_direction_checks_the_same_bounds() { + 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 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 db600cbf2..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; @@ -42,9 +42,11 @@ impl ModeNames for ToyBlockCipher { const CCM_ALG_NAME: &'static str = "ToyBlockCipher_CCM"; const CFB_ALG_NAME: &'static str = "ToyBlockCipher_CFB"; const CFB8_ALG_NAME: &'static str = "ToyBlockCipher_CFB8"; + const CMAC_ALG_NAME: &'static str = "ToyBlockCipher_CMAC"; const CTR_ALG_NAME: &'static str = "ToyBlockCipher_CTR"; const ECB_ALG_NAME: &'static str = "ToyBlockCipher_ECB"; const GCM_ALG_NAME: &'static str = "ToyBlockCipher_GCM"; + const GMAC_ALG_NAME: &'static str = "ToyBlockCipher_GMAC"; } impl ElectronicCodeBook for ToyBlockCipher { @@ -68,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 4bfe59ba9..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, CcmPacket, Cfb, Cfb8, Ctr, Gcm, 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; @@ -30,9 +33,11 @@ fn the_toy_names_every_mode_differently() { Toy::CCM_ALG_NAME, Toy::CFB_ALG_NAME, Toy::CFB8_ALG_NAME, + Toy::CMAC_ALG_NAME, Toy::CTR_ALG_NAME, Toy::ECB_ALG_NAME, Toy::GCM_ALG_NAME, + Toy::GMAC_ALG_NAME, ]; for (i, a) in names.iter().enumerate() { for b in &names[i + 1..] { @@ -43,37 +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::CCM_ALG_NAME); - check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CBC_ALG_NAME); + check::>(Toy::CBC_ALG_NAME); + check::>(Toy::ECB_ALG_NAME); + check::>(Toy::ECB_ALG_NAME); + check::>(Toy::CFB_ALG_NAME); + check::>(Toy::CFB_ALG_NAME); + check::>(Toy::CFB8_ALG_NAME); + check::>(Toy::CFB8_ALG_NAME); + check::>(Toy::GCM_ALG_NAME); + check::>(Toy::GCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::CCM_ALG_NAME); + check::>(Toy::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 43ad60dcc..dc0175cad 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_core_test_framework::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_core_test_framework::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 d67bfe535..35c275cb7 100644 --- a/crypto/cipher/tests/modes/ccm_tests.rs +++ b/crypto/cipher/tests/modes/ccm_tests.rs @@ -3,32 +3,49 @@ //! These check the properties of the *mode* -- that only the forward cipher function is ever //! used, that the counter half batches while the CBC-MAC stays serial, that call chunking is //! invisible in both directions, what `TAG_LEN` and `NONCE_LEN` do and do not change, that the -//! decryptor holds to the declared length, and which entry points release unauthenticated -//! plaintext -- independently of the known-answer vectors in the `aes` crate's `sp800_38c_tests.rs`, +//! 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 +//! the fixed-frame pair, whose frame is the message under test. +//! //! 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::{CcmDecryptor, CcmPacket, ModeNames}; -use bouncycastle_cipher::{Decrypting, Encrypting}; +use bouncycastle_cipher::modes::{CCM, CCM_Packet, ModeNames}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::hazmat::ElectronicCodeBook; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{AEADCipherDecryptor, SymmetricCipherDecryptor}; +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; const TAG_LEN: usize = 16; +/// The streams' AAD capacity, above any AAD used here. +const AAD_LEN: usize = 64; -type ToyCcm = CcmPacket; -type SwappedCcm = CcmPacket; -type SwappedFourCcm = CcmPacket; -type ForwardOnlyCcm = CcmPacket; +/// 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)) @@ -38,68 +55,84 @@ fn message(len: usize) -> Vec { (0..len).map(|i| (i as u8).wrapping_mul(3).wrapping_add(1)).collect() } -/// One-shot Sec 6.1 over the toy permutation `P`, detached: the ciphertext and the tag. -fn encrypt + ModeNames>( +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. +fn seal + ModeNames + Clone>( nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], ) -> (Vec, [u8; TAG_LEN]) { - let mut ct = vec![0u8; plaintext.len()]; - let (written, tag) = - CcmPacket::::encrypt_detached_out( - &toy_key(), - nonce, - aad, - plaintext, - &mut ct, - ) - .unwrap(); - assert_eq!(written, plaintext.len(), "CCM never expands the payload"); - (ct, tag) + let mut buf = plaintext.to_vec(); + let tag = declared_key::

().encrypt_detached_in_place_nonce(nonce, aad, &mut buf).unwrap(); + (buf, tag) } -/// Sec 6.1 through the streaming API over `P`, `chunk` bytes per `do_encrypt_inplace`. -fn stream_encrypt + ModeNames>( +/// One-shot Sec 6.2 over `P`, in place. +fn open + ModeNames + Clone>( + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], +) -> Result, SymmetricCipherError> { + let mut buf = ciphertext.to_vec(); + 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< + P: ElectronicCodeBook + ModeNames + Clone, + const DATA_LEN: usize, +>( nonce: &[u8; NONCE_LEN], aad: &[u8], plaintext: &[u8], chunk: usize, ) -> (Vec, [u8; TAG_LEN]) { - let mut ccm = CcmPacket::::new( - &toy_key(), - nonce, - aad, - plaintext.len(), - ) - .unwrap(); - let mut data = plaintext.to_vec(); - for piece in data.chunks_mut(chunk) { - ccm.do_encrypt_inplace(piece).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; + for piece in plaintext.chunks(chunk) { + written += enc.do_encrypt_out(piece, &mut out[written..]).unwrap(); } - let tag = ccm.do_encrypt_final().unwrap(); - (data, tag) + assert_eq!(written, plaintext.len(), "CCM never holds back payload"); + let (_, _, tag) = enc.do_encrypt_final_detached().unwrap(); + (out, tag) } -/// Sec 6.2 through the streaming API over `P`, `chunk` bytes per `do_decrypt_inplace`. -fn stream_decrypt + ModeNames>( +/// Sec 6.2 through the fixed-frame stream over `P`, `chunk` bytes per update, detached tag. +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 ccm = CcmPacket::::new( - &toy_key(), - nonce, - aad, - ciphertext.len(), - )?; - let mut data = ciphertext.to_vec(); - for piece in data.chunks_mut(chunk) { - ccm.do_decrypt_inplace(piece)?; + 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; + for piece in ciphertext.chunks(chunk) { + written += dec.do_decrypt_out(piece, &mut out[written..])?; } - ccm.do_decrypt_final(tag)?; - Ok(data) + dec.do_decrypt_final_detached(tag)?; + out.truncate(written); + Ok(out) } // ---- the forward-cipher-only rule --------------------------------------------------------- @@ -112,34 +145,28 @@ fn stream_decrypt + ModeNames>( /// the four-block, pair, single-block and partial-block keystream paths all run. #[test] fn neither_direction_uses_the_inverse_cipher() { + const LEN: usize = 11 * TOY_LEN + 5; let nonce = pinned_nonce(); // Long enough to span two MAC blocks, so the AAD's own zero pad (A.2.2) runs as well. let aad = b"a header long enough to run over into a second CBC-MAC block"; - let plaintext = message(11 * TOY_LEN + 5); - - let (ct, tag) = encrypt::(&nonce, aad, &plaintext); - let mut back = vec![0u8; plaintext.len()]; - ForwardOnlyCcm::::decrypt_detached_out( - &toy_key(), - &nonce, - aad, - &ct, - &tag, - &mut back, - ) - .unwrap(); - assert_eq!(back, plaintext, "all paths, forward cipher only"); + let plaintext = message(LEN); + + let (ct, tag) = seal::(&nonce, aad, &plaintext); + assert_eq!(open::(&nonce, aad, &ct, &tag).unwrap(), plaintext); // Byte by byte, so the partial-block keystream path runs in both directions too. assert_eq!( - stream_encrypt::(&nonce, aad, &plaintext, 1), + stream_encrypt::(&nonce, aad, &plaintext, 1), (ct.clone(), tag), "byte path, forward cipher only" ); - assert_eq!(stream_decrypt::(&nonce, aad, &ct, &tag, 1).unwrap(), plaintext); + assert_eq!( + stream_decrypt::(&nonce, aad, &ct, &tag, 1).unwrap(), + plaintext + ); // The forward-only toy must agree with the real one, or the above proves nothing. - assert_eq!(encrypt::(&nonce, aad, &plaintext), (ct, tag), "the two toys must agree"); + assert_eq!(seal::(&nonce, aad, &plaintext), (ct, tag), "the two toys must agree"); } // ---- the batched keystream paths ---------------------------------------------------------- @@ -155,26 +182,33 @@ fn neither_direction_uses_the_inverse_cipher() { /// avoids the pair path and agrees with `Toy` entirely. #[test] fn the_pair_path_is_really_used_in_both_directions_and_the_mac_never_batches() { + const LEN: usize = 2 * TOY_LEN; let nonce = pinned_nonce(); - let plaintext = message(2 * TOY_LEN); - let (ct, tag) = encrypt::(&nonce, b"aad", &plaintext); + let plaintext = message(LEN); + let (ct, tag) = seal::(&nonce, b"aad", &plaintext); - let (swapped_ct, swapped_tag) = encrypt::(&nonce, b"aad", &plaintext); + let (swapped_ct, swapped_tag) = seal::(&nonce, b"aad", &plaintext); assert_ne!(swapped_ct, ct, "CCM encryption must use the pair path"); assert_eq!(swapped_tag, tag, "the CBC-MAC and S0 are single-block, so the tag must not change"); let (single_ct, single_tag) = - stream_encrypt::(&nonce, b"aad", &plaintext, TOY_LEN); + stream_encrypt::(&nonce, b"aad", &plaintext, TOY_LEN); assert_eq!(single_ct, ct, "the single-block path must not pair"); assert_eq!(single_tag, tag); // 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. - let mut dec = SwappedCcm::::new(&toy_key(), &nonce, b"aad", ct.len()).unwrap(); - let mut back = ct.clone(); - dec.do_decrypt_inplace(&mut back).unwrap(); + // 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 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(); assert_ne!(back, plaintext, "CCM decryption must use the pair path"); - assert!(matches!(dec.do_decrypt_final(&tag), Err(SymmetricCipherError::AEADTagCheckFailed))); + assert!(matches!( + dec.do_decrypt_final_detached(&tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); } /// The four-block path must be taken, in both directions, and only for full fours. @@ -182,25 +216,32 @@ fn the_pair_path_is_really_used_in_both_directions_and_the_mac_never_batches() { /// methods are correct. #[test] fn the_four_block_path_is_really_used_in_both_directions() { + const LEN: usize = 5 * TOY_LEN; let nonce = pinned_nonce(); - let plaintext = message(5 * TOY_LEN); - let (ct, tag) = encrypt::(&nonce, b"aad", &plaintext); + let plaintext = message(LEN); + let (ct, tag) = seal::(&nonce, b"aad", &plaintext); - let (swapped_ct, swapped_tag) = encrypt::(&nonce, b"aad", &plaintext); + let (swapped_ct, swapped_tag) = seal::(&nonce, b"aad", &plaintext); assert_ne!(swapped_ct, ct, "five blocks must go through encrypt_4blocks"); assert_eq!(swapped_tag, tag, "the CBC-MAC and S0 are single-block, so the tag must not change"); // Two blocks per call uses pairs only, so the rotated-four toy is correct there. let (pairs_ct, pairs_tag) = - stream_encrypt::(&nonce, b"aad", &plaintext, 2 * TOY_LEN); + stream_encrypt::(&nonce, b"aad", &plaintext, 2 * TOY_LEN); assert_eq!(pairs_ct, ct, "pairs must not use the four path"); assert_eq!(pairs_tag, tag); - let mut dec = SwappedFourCcm::::new(&toy_key(), &nonce, b"aad", ct.len()).unwrap(); - let mut back = ct.clone(); - dec.do_decrypt_inplace(&mut back).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(); assert_ne!(back, plaintext, "decryption must batch fours too"); - assert!(matches!(dec.do_decrypt_final(&tag), Err(SymmetricCipherError::AEADTagCheckFailed))); + assert!(matches!( + dec.do_decrypt_final_detached(&tag), + Err(SymmetricCipherError::AEADTagCheckFailed) + )); } // ---- call sequencing ---------------------------------------------------------------------- @@ -217,74 +258,86 @@ fn the_four_block_path_is_really_used_in_both_directions() { /// short final remainder, never one big enough to batch. #[test] fn every_split_agrees_with_the_one_shot_in_both_directions() { + const LEN: usize = 6 * TOY_LEN + 5; let nonce = pinned_nonce(); let aad = b"header"; - let plaintext = message(6 * TOY_LEN + 5); - let (ct, tag) = encrypt::(&nonce, aad, &plaintext); - - for split in 0..=plaintext.len() { - let mut enc = ToyCcm::::new(&toy_key(), &nonce, aad, plaintext.len()).unwrap(); - let mut streamed = plaintext.clone(); - let (head, rest) = streamed.split_at_mut(split); - enc.do_encrypt_inplace(head).unwrap(); - enc.do_encrypt_inplace(rest).unwrap(); - assert_eq!(enc.do_encrypt_final().unwrap(), tag, "tag, split at {split}"); + let plaintext = message(LEN); + let (ct, tag) = seal::(&nonce, aad, &plaintext); + + for split in 0..=LEN { + 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(); + enc.do_encrypt_out(&plaintext[split..], &mut streamed[split..]).unwrap(); + let (_, _, streamed_tag) = enc.do_encrypt_final_detached().unwrap(); + assert_eq!(streamed_tag, tag, "tag, split at {split}"); assert_eq!(streamed, ct, "ciphertext, split at {split}"); - let mut dec = ToyCcm::::new(&toy_key(), &nonce, aad, ct.len()).unwrap(); - let mut back = ct.clone(); - let (head, rest) = back.split_at_mut(split); - dec.do_decrypt_inplace(head).unwrap(); - dec.do_decrypt_inplace(rest).unwrap(); - dec.do_decrypt_final(&tag).unwrap_or_else(|e| panic!("tag check, split at {split}: {e:?}")); + 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(); + dec.do_decrypt_out(&ct[split..], &mut back[split..]).unwrap(); + dec.do_decrypt_final_detached(&tag) + .unwrap_or_else(|e| panic!("tag check, split at {split}: {e:?}")); assert_eq!(back, plaintext, "plaintext, split at {split}"); } } -/// The payload length declared to `new` is inside `B0` (A.2.1 Table 2's `Q`), so neither -/// direction may be given more data than declared, nor finalized with less: either would produce -/// or verify a tag against a `B0` no counterpart could reproduce. A refused update consumes -/// nothing, so the flow is still usable. +/// The frame is inside `B0` (A.2.1 Table 2's `Q`), so neither stream may be given more than it, +/// nor finalized with less: either would produce or verify a tag against a `B0` no counterpart +/// could reproduce. A refused update consumes nothing, so the flow is still usable. #[test] -fn both_directions_hold_to_the_declared_length() { +fn both_streams_hold_to_the_frame() { + const LEN: usize = 8; let nonce = pinned_nonce(); - let plaintext = message(8); - let (ct, tag) = encrypt::(&nonce, &[], &plaintext); + let plaintext = message(LEN); + let (ct, tag) = seal::(&nonce, &[], &plaintext); // Encrypting: too much is refused, and finalizing short is refused. - let mut enc = ToyCcm::::new(&toy_key(), &nonce, &[], 8).unwrap(); - let mut too_much = [0x77u8; 9]; + let key = frame_key::(); + let mut enc = key.create_encryptor_nonce(&nonce); + let mut out = [0u8; 9]; assert!( - matches!(enc.do_encrypt_inplace(&mut too_much), Err(SymmetricCipherError::StateError(_))), - "9 bytes against a declared 8" + matches!( + enc.do_encrypt_out(&[0x77u8; 9], &mut out), + Err(SymmetricCipherError::StateError(_)) + ), + "9 bytes against a frame of 8" ); - assert_eq!(too_much, [0x77u8; 9], "a refused update must not touch the data"); - let mut some = plaintext[..4].to_vec(); - enc.do_encrypt_inplace(&mut some).expect("4 of the 8 declared bytes"); + enc.do_encrypt_out(&plaintext[..4], &mut out).expect("4 of the 8 bytes"); assert!( - matches!(enc.do_encrypt_final(), Err(SymmetricCipherError::StateError(_))), + matches!(enc.do_encrypt_final_detached(), Err(SymmetricCipherError::StateError(_))), "finalizing 4 bytes short" ); - // Decrypting: the same two refusals. - let mut dec = ToyCcm::::new(&toy_key(), &nonce, &[], 8).unwrap(); - let mut too_much = [0x77u8; 9]; + // 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 key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); + let mut out = [0u8; LEN]; assert!( - matches!(dec.do_decrypt_inplace(&mut too_much), Err(SymmetricCipherError::StateError(_))), - "9 bytes against a declared 8" + matches!( + dec.do_decrypt_out(&[0x77u8; LEN + TAG_LEN + 1], &mut out), + Err(SymmetricCipherError::StateError(_)) + ), + "more than the frame and a tag" ); - assert_eq!(too_much, [0x77u8; 9], "a refused update must not touch the data"); - // ...and must not have debited the declared length either: the 8 genuine bytes still verify. - let mut back = ct.clone(); - dec.do_decrypt_inplace(&mut back).unwrap(); - dec.do_decrypt_final(&tag).unwrap(); - assert_eq!(back, plaintext); - - let mut dec = ToyCcm::::new(&toy_key(), &nonce, &[], 8).unwrap(); - let mut some = ct[..4].to_vec(); - dec.do_decrypt_inplace(&mut some).unwrap(); + // ...and must not have debited the frame either: the 8 genuine bytes still verify. + dec.do_decrypt_out(&ct, &mut out).unwrap(); + dec.do_decrypt_final_detached(&tag).unwrap(); + assert_eq!(out, plaintext[..]); + + 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(&tag), Err(SymmetricCipherError::StateError(_))), + matches!(dec.do_decrypt_final_detached(&tag), Err(SymmetricCipherError::DecryptionFailed)), "finalizing 4 bytes short" ); } @@ -306,19 +359,16 @@ fn tag_length_changes_the_tag_but_not_the_ciphertext_and_tags_do_not_nest() { let nonce = pinned_nonce(); let aad = b"associated"; let plaintext = message(23); - let (ct16, tag16) = encrypt::(&nonce, aad, &plaintext); + let (ct16, tag16) = seal::(&nonce, aad, &plaintext); macro_rules! check_tag_len { ($t:literal) => {{ - type Enc = CcmPacket; - type Dec = CcmPacket; - let mut ct = vec![0u8; plaintext.len()]; - let (_, tag) = - Enc::encrypt_detached_out(&toy_key(), &nonce, aad, &plaintext, &mut ct).unwrap(); - assert_eq!(ct, ct16, "ciphertext must not depend on TAG_LEN ({})", $t); - let mut pt = vec![0u8; plaintext.len()]; - Dec::decrypt_detached_out(&toy_key(), &nonce, aad, &ct, &tag, &mut pt).unwrap(); - assert_eq!(pt, plaintext, "TAG_LEN={} round trip", $t); + let key = ToyKey::>::from_key_material(&toy_key()).unwrap(); + let mut buf = plaintext.clone(); + 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); + key.decrypt_detached_in_place(&nonce, aad, &mut buf, &tag).unwrap(); + assert_eq!(buf, plaintext, "TAG_LEN={} round trip", $t); tag.to_vec() }}; } @@ -342,7 +392,7 @@ fn tag_length_changes_the_tag_but_not_the_ciphertext_and_tags_do_not_nest() { } /// Every nonce length A.1 permits, `n` in `7..=13`, works, and each one implies its own payload -/// limit: `q = 15 - n` and "by definition, p < 2^8q", which `CcmPacket::MAX_PAYLOAD_LEN` exposes. +/// limit: `q = 15 - n` and "by definition, p < 2^8q", which `CCM::MAX_PAYLOAD_LEN` exposes. /// `sp800_38c_tests.rs` reaches `n` of 7, 8, 12 and 13 through Appendix C; 9, 10 and 11 are /// reached only here. Over the toy alone, since where the nonce goes (A.2.1 Table 2, A.3 Table 3) /// is the mode's business and not the permutation's. @@ -352,43 +402,39 @@ fn every_permitted_nonce_length_works() { key: &KeyMaterial, expected_max_payload: u64, ) where - P: ElectronicCodeBook + ModeNames, + P: ElectronicCodeBook + ModeNames + Clone, { // The full 16-byte tag, deliberately. `Toy` permutes each byte independently, so under // it the CBC-MAC is sixteen independent byte-chains and a `t`-byte tag witnesses only the // first `t` of them; the last nonce octet flipped below sits at block octet `N`, which an // 8-byte tag would never see once `N >= 8`. Real AES mixes every byte into every other, // so this is a limit of the toy, not of the mode. - type Enc = CcmPacket; - type Dec = CcmPacket; + type Mode = CCM; assert_eq!( - Enc::::MAX_PAYLOAD_LEN, + Mode::::MAX_PAYLOAD_LEN, expected_max_payload, "n = {N}: the payload limit 2^8q - 1 that q = 15 - n implies" ); + 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 ct = vec![0u8; plaintext.len()]; - let (_, tag) = - Enc::::encrypt_detached_out(key, &nonce, b"aad", &plaintext, &mut ct) - .unwrap(); - assert_ne!(ct, plaintext, "nonce length {N}: must actually encrypt"); + let mut buf = plaintext.clone(); + 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(); - let mut back = vec![0u8; plaintext.len()]; - Dec::::decrypt_detached_out(key, &nonce, b"aad", &ct, &tag, &mut back) - .unwrap(); - assert_eq!(back, plaintext, "nonce length {N}: round trip"); + 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 // every `Ctr_i` (Table 3); flipping it must change both and so fail the check. let mut wrong = nonce; wrong[N - 1] ^= 0x01; + let mut buf = ct; assert!( matches!( - Dec::::decrypt_detached_out( - key, &wrong, b"aad", &ct, &tag, &mut back - ), + key.decrypt_detached_in_place(&wrong, b"aad", &mut buf, &tag), Err(SymmetricCipherError::AEADTagCheckFailed) ), "nonce length {N}: the nonce is authenticated" @@ -409,54 +455,36 @@ fn every_permitted_nonce_length_works() { /// Which entry points release unauthenticated plaintext on a forgery, pinned side by side. /// /// Sec 6.2: "When the error message INVALID is returned, the payload P and the MAC T shall not -/// be revealed." The one-shots honour that -- the caller's buffer comes back zeroized -- because -/// they have the whole ciphertext before they start. Neither streaming path can: Sec 6.2 -/// recovers `P` (step 5) before it can verify it (step 10), and both the inherent -/// `do_decrypt_inplace` and the fixed-frame `CcmDecryptor` release `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 their docs warn. Pinning the difference makes it a documented -/// property rather than an accident. +/// 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 `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. #[test] -fn one_shots_release_nothing_on_forgery_but_the_streams_do() { +fn the_one_shot_releases_nothing_on_forgery_but_the_stream_does() { + const LEN: usize = 19; let nonce = pinned_nonce(); let plaintext = *b"do not trust me yet"; - let (ct, mut tag) = encrypt::(&nonce, b"aad", &plaintext); + let (ct, mut tag) = seal::(&nonce, b"aad", &plaintext); tag[0] ^= 0xFF; // forge it - // The inherent one-shot: verify-then-return, so a forged tag leaves nothing but zeros. - let mut one_shot = [0xEEu8; 19]; + // The one-shot: verify-then-return, so a forged tag leaves nothing but zeros. + let mut buf = ct.clone(); assert!(matches!( - ToyCcm::::decrypt_detached_out( - &toy_key(), - &nonce, - b"aad", - &ct, - &tag, - &mut one_shot - ), + declared_key::().decrypt_detached_in_place(&nonce, b"aad", &mut buf, &tag), Err(SymmetricCipherError::AEADTagCheckFailed) )); - assert_eq!(one_shot, [0u8; 19], "the one-shot must zeroize its buffer on a forged tag"); - - // The inherent stream: the plaintext is in the buffer before the tag is ever looked at, and - // rejecting the tag cannot take it back. - let mut dec = ToyCcm::::new(&toy_key(), &nonce, b"aad", ct.len()).unwrap(); - let mut streamed = ct.clone(); - dec.do_decrypt_inplace(&mut streamed).unwrap(); - assert_eq!(&streamed[..], &plaintext[..], "the stream already produced plaintext"); - assert!(matches!(dec.do_decrypt_final(&tag), Err(SymmetricCipherError::AEADTagCheckFailed))); - assert_eq!(&streamed[..], &plaintext[..], "...and a rejected tag cannot take it back"); - - // The fixed-frame decryptor is the same stream behind the trait: 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 to zeroize. - type Dec = CcmDecryptor; + assert_eq!(buf, [0u8; LEN], "the one-shot must zeroize its buffer on a forged tag"); - let mut dec = Dec::do_decrypt_init(&toy_key(), &nonce).unwrap(); + // 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 key = frame_key::(); + let mut dec = key.create_decryptor(&nonce); dec.do_update_aad(b"aad").unwrap(); - let mut streamed = [0u8; 19]; - assert_eq!(dec.do_decrypt_out(&ct, &mut streamed).unwrap(), 19, "released mid-stream"); + let mut streamed = [0u8; LEN]; + assert_eq!(dec.do_decrypt_out(&ct, &mut streamed).unwrap(), LEN, "released mid-stream"); assert_eq!(&streamed[..], &plaintext[..], "the stream already produced plaintext"); let mut detached = [0xEEu8; 16]; assert!(matches!( @@ -468,35 +496,83 @@ fn one_shots_release_nothing_on_forgery_but_the_streams_do() { let mut inline = ct.clone(); inline.extend_from_slice(&tag); - let mut dec = Dec::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; 19]; - assert_eq!(dec.do_decrypt_out(&inline, &mut streamed).unwrap(), 19); + let mut streamed = [0u8; LEN]; + assert_eq!(dec.do_decrypt_out(&inline, &mut streamed).unwrap(), LEN); assert_eq!(&streamed[..], &plaintext[..]); assert!(matches!(dec.do_decrypt_final(), Err(SymmetricCipherError::AEADTagCheckFailed))); } -/// Tests a large payload that would blow the Linux stack limit if we try to hard-copy it. -/// Tests the inherent APIs on CCM. +/// A payload large enough to blow the stack if it were copied there, through the in-place +/// one-shots, which hold nothing but the caller's one buffer. #[test] -fn test_large_payload_inherent() { - // 5 mb payload +fn a_large_payload_round_trips_in_place() { const LARGE_LEN: usize = 5 * 1024 * 1024; - let key = toy_key(); let nonce = pinned_nonce(); let aad = b"header"; let plaintext = message(LARGE_LEN); - // round-tripped though the inherent CCM interface - let mut ct = vec![0u8; LARGE_LEN]; - let (written, tag) = - ToyCcm::::encrypt_detached_out(&key, &nonce, aad, &plaintext, &mut ct).unwrap(); - assert_eq!(written, LARGE_LEN); + let (ct, tag) = seal::(&nonce, aad, &plaintext); 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"); - let mut back = vec![0u8; LARGE_LEN]; - let n = ToyCcm::::decrypt_detached_out(&key, &nonce, aad, &ct, &tag, &mut back) - .unwrap(); - assert_eq!(n, LARGE_LEN); - assert_eq!(back, plaintext, "inherent round trip"); + // 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 d3ee96f6f..026b52622 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::FixedSeedRNG; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; 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 { 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::<16>::new(iv), - ) - .expect("encrypt init"); + let (enc, got) = + key.create_encryptor(&mut FixedSeedRNG::<16>::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 40349d2cd..8b697dcc6 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::FixedSeedRNG; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; 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 { 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::<16>::new(iv), - ) - .expect("encrypt init"); + let (enc, got) = + key.create_encryptor(&mut FixedSeedRNG::<16>::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 new file mode 100644 index 000000000..b00a685d2 --- /dev/null +++ b/crypto/cipher/tests/modes/cmac_tests.rs @@ -0,0 +1,186 @@ +//! Structural tests for CMAC, driven by a toy permutation: the two routes onto it agree, the +//! held-back final block comes out the same wherever the call boundaries fall, the tag rules, key +//! validation and suspend/resume. The known-answer tests (NIST CSRC, ACVP, Wycheproof) are in the +//! `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, 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<'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; + +fn mac_key() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(toy_key().ref_to_bytes(), KeyType::MACKey).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_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_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); + } +} + +/// Every two-way split, plus byte-at-a-time, matches the one-shot: the pending block must come +/// out the same wherever the call boundaries fall, including a boundary exactly on a block edge. +#[test] +fn streaming_split_points() { + let key = cmac_key(); + for mlen in [16, 20, 32, 33, 64] { + let msg = message(mlen); + let expected = key.create_generator().mac(&msg); + for split in 0..=mlen { + 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 = key.create_generator(); + for b in &msg { + cmac.do_update(core::slice::from_ref(b)); + } + assert_eq!(cmac.do_final(), expected, "mlen {mlen}, byte at a time"); + } +} + +/// A complete final block takes `K1`, a padded one `K2` (Sec 6.2 step 4), so a message and the +/// same message with the `10^j` padding spelled out must differ. +#[test] +fn a_complete_final_block_and_its_padded_twin_differ() { + let key = cmac_key(); + let short = message(20); + let mut padded = short.clone(); + padded.push(0x80); + padded.resize(32, 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 the_key_enforces_the_material_policy() { + TestFrameworkFromKeyMaterial::new() + .test::(Toy::CMAC_ALG_NAME, SecurityStrength::_128bit); +} + +#[test] +fn truncation() { + let key = cmac_key(); + let msg = message(20); + let full = key.create_generator().mac(&msg); + assert_eq!(full.len(), TOY_LEN); + assert_eq!(key.create_generator().output_len(), TOY_LEN); + + let mut out = [0xAAu8; CMAC_MIN_TAG_LEN]; + assert_eq!(key.create_generator().mac_out(&msg, &mut out).unwrap(), 8); + assert_eq!(out[..], full[..8]); + let mut long = [0xAAu8; 24]; + 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!( + 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!(key.create_verifier().verify(&msg, &full)); + assert!(!key.create_verifier().verify(&msg, &full[..15])); + let mut long = full.clone(); + long.push(0); + assert!(!key.create_verifier().verify(&msg, &long)); + let mut wrong = full.clone(); + wrong[15] ^= 1; + assert!(!key.create_verifier().verify(&msg, &wrong)); +} + +#[test] +fn suspend_resume() { + let key = cmac_key(); + for mlen in [0, 16, 20, 64] { + let msg = message(mlen); + let expected = key.mac(&msg); + for split in 0..=mlen { + 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.state()).unwrap(); + resumed.do_update(&msg[split..]); + assert_eq!(resumed.do_final(), expected, "mlen {mlen}, suspended at {split}"); + } + } +} + +#[test] +fn suspend_resume_rejects() { + 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.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.state()).is_ok()); + + let mut no_ver = state; + no_ver[..3].fill(0); + assert_eq!( + ToyCmac::from_suspended(no_ver, key.state()).err(), + Some(SuspendableError::IncompatibleVersion) + ); +} diff --git a/crypto/cipher/tests/modes/common/mod.rs b/crypto/cipher/tests/modes/common/mod.rs index 91e6f5510..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; @@ -50,6 +50,7 @@ fn validate(key: &dyn KeyMaterialTrait) -> Result<(), SymmetricCipherError> { /// /// A true permutation on each byte, so it is a true permutation on the block, and its inverse is /// distinctly different code (XOR then `rotate_right(1)`). +#[derive(Clone)] pub struct Toy { key: [u8; TOY_LEN], } @@ -64,9 +65,11 @@ impl ModeNames for Toy { const CCM_ALG_NAME: &'static str = "Toy_CCM"; const CFB_ALG_NAME: &'static str = "Toy_CFB"; const CFB8_ALG_NAME: &'static str = "Toy_CFB8"; + const CMAC_ALG_NAME: &'static str = "Toy_CMAC"; const CTR_ALG_NAME: &'static str = "Toy_CTR"; const ECB_ALG_NAME: &'static str = "Toy_ECB"; const GCM_ALG_NAME: &'static str = "Toy_GCM"; + const GMAC_ALG_NAME: &'static str = "Toy_GMAC"; } impl ElectronicCodeBook for Toy { @@ -76,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; @@ -123,6 +128,7 @@ impl ElectronicCodeBook for Toy { /// it is false. /// /// Its single-block methods are identical to [`Toy`]'s, so the two agree on odd-length input. +#[derive(Clone)] pub struct SwappedPairToy { inner: Toy, } @@ -137,16 +143,20 @@ impl ModeNames for SwappedPairToy { const CCM_ALG_NAME: &'static str = "SwappedPairToy_CCM"; const CFB_ALG_NAME: &'static str = "SwappedPairToy_CFB"; const CFB8_ALG_NAME: &'static str = "SwappedPairToy_CFB8"; + const CMAC_ALG_NAME: &'static str = "SwappedPairToy_CMAC"; const CTR_ALG_NAME: &'static str = "SwappedPairToy_CTR"; const ECB_ALG_NAME: &'static str = "SwappedPairToy_ECB"; const GCM_ALG_NAME: &'static str = "SwappedPairToy_GCM"; + const GMAC_ALG_NAME: &'static str = "SwappedPairToy_GMAC"; } 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); } @@ -196,6 +206,7 @@ impl ElectronicCodeBook for SwappedPairToy { /// `TestFrameworkElectronicCodeBook`, which exercises both directions -- so it is only ever used with /// `Cfb`. Its forward methods delegate to [`Toy`], including the pair and four-block methods, so a CFB round trip /// over it must agree with one over `Toy`. +#[derive(Clone)] pub struct ForwardOnlyToy { inner: Toy, } @@ -210,16 +221,20 @@ impl ModeNames for ForwardOnlyToy { const CCM_ALG_NAME: &'static str = "ForwardOnlyToy_CCM"; const CFB_ALG_NAME: &'static str = "ForwardOnlyToy_CFB"; const CFB8_ALG_NAME: &'static str = "ForwardOnlyToy_CFB8"; + const CMAC_ALG_NAME: &'static str = "ForwardOnlyToy_CMAC"; const CTR_ALG_NAME: &'static str = "ForwardOnlyToy_CTR"; const ECB_ALG_NAME: &'static str = "ForwardOnlyToy_ECB"; const GCM_ALG_NAME: &'static str = "ForwardOnlyToy_GCM"; + const GMAC_ALG_NAME: &'static str = "ForwardOnlyToy_GMAC"; } 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); } @@ -251,6 +266,7 @@ impl ElectronicCodeBook for ForwardOnlyToy { /// The four-block analogue of [`SwappedPairToy`]: a CBC decryptor that uses `decrypt_4blocks` /// must produce something other than the correct plaintext for four or more blocks, while fewer /// than four, which go through the pair and single paths, still round-trip. +#[derive(Clone)] pub struct SwappedFourToy { inner: Toy, } @@ -265,16 +281,20 @@ impl ModeNames for SwappedFourToy { const CCM_ALG_NAME: &'static str = "SwappedFourToy_CCM"; const CFB_ALG_NAME: &'static str = "SwappedFourToy_CFB"; const CFB8_ALG_NAME: &'static str = "SwappedFourToy_CFB8"; + const CMAC_ALG_NAME: &'static str = "SwappedFourToy_CMAC"; const CTR_ALG_NAME: &'static str = "SwappedFourToy_CTR"; const ECB_ALG_NAME: &'static str = "SwappedFourToy_ECB"; const GCM_ALG_NAME: &'static str = "SwappedFourToy_GCM"; + const GMAC_ALG_NAME: &'static str = "SwappedFourToy_GMAC"; } 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 ee37e51bd..5ca4f219f 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::FixedSeedRNG; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; 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 { 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,15 +344,12 @@ 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 }; @@ -387,15 +412,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 }; @@ -417,9 +438,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(), @@ -434,25 +455,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(); @@ -510,29 +531,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::<12>::new(nonce), - ) - .expect("encrypt init"); + let (e, got) = + key.create_encryptor(&mut FixedSeedRNG::<12>::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"); @@ -572,7 +588,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); @@ -580,15 +595,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(); @@ -596,7 +611,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"); @@ -605,28 +621,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"); @@ -638,23 +656,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. @@ -663,12 +681,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 -------------------------------------------------------------------------- @@ -677,18 +694,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"); } } @@ -701,22 +717,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"); } @@ -728,15 +740,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 89e100f21..85d062936 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::FixedSeedRNG; use bouncycastle_core_test_framework::block_cipher::TestFrameworkBlockCipher; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; 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::<0>::new([]); - 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::<0>::new([]), - &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::<0>::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 fb7d6060d..2c5fe4c9d 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::FixedSeedRNG; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; +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::<12>::new(seed), - aad, - message, - &mut ct, - ) - .unwrap(); + let (_, nonce, tag) = gcm_key::() + .encrypt_detached_out(&mut FixedSeedRNG::<12>::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,11 +92,8 @@ 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::<12>::new(seed), - ) - .unwrap(); + let (mut enc, got_nonce) = + key.create_encryptor(&mut FixedSeedRNG::<12>::new(seed)).unwrap(); assert_eq!(got_nonce, nonce); enc.do_update_aad(&aad[..aad_split]).unwrap(); enc.do_update_aad(&aad[aad_split..]).unwrap(); @@ -106,7 +111,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]; @@ -123,8 +127,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); }}; } @@ -139,14 +142,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:?}"), } @@ -156,16 +158,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:?}"), } @@ -176,14 +178,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] { @@ -210,16 +212,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:?}"), } @@ -227,7 +227,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(); @@ -243,12 +243,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 @@ -270,27 +270,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::<12>::new([0x4Du8; 12]), - aad, - message, - &mut ct, - ) - .unwrap(); + let (_, nonce, tag) = key + .encrypt_detached_out(&mut FixedSeedRNG::<12>::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) } @@ -299,40 +291,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"); @@ -340,15 +330,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 new file mode 100644 index 000000000..2a1c042bc --- /dev/null +++ b/crypto/cipher/tests/modes/gmac_tests.rs @@ -0,0 +1,162 @@ +//! 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, 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::symmetric_key::{AeadEncryptionKey, FromKeyMaterial}; +use bouncycastle_core_test_framework::FixedSeedRNG; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromKeyMaterial; +use common::{TOY_LEN, Toy, toy_key}; +use toy_key::ToyKey; + +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]; + +fn mac_key() -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(toy_key().ref_to_bytes(), KeyType::MACKey).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 { + (0..len).map(|i| (i as u8).wrapping_mul(29).wrapping_add(3)).collect() +} + +fn rng() -> FixedSeedRNG { + FixedSeedRNG::new(NONCE) +} + +/// GMAC is GCM's tag over AAD alone: every constructor gives that tag, and the 12-byte tag is the +/// 16-byte one's prefix (Algorithm 4 step 6, `MSB_t`). +#[test] +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) = ToyKey::::from_key_material(&toy_key()) + .unwrap() + .encrypt_detached_out(&mut rng(), &msg, &[], &mut []) + .unwrap(); + assert_eq!(gcm_nonce, NONCE); + + 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, "key.create_generator, len {len}"); + assert_eq!( + key.create_generator_nonce(&NONCE).mac(&msg), + expected, + "key.create_generator_nonce, 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!( + >::generate_init_nonce(&state, &NONCE) + .mac(&msg), + expected, + "generate_init_nonce, len {len}" + ); + assert!( + >::verify_init(&state, &NONCE) + .verify(&msg, &expected) + ); + + let short = + >::generate_init_nonce(&state, &NONCE) + .mac(&msg); + assert_eq!(short[..], expected[..12], "len {len}"); + assert!( + >::verify_init(&state, &NONCE) + .verify(&msg, &short) + ); + } +} + +#[test] +fn streaming_split_points() { + let key = gmac_key::<16>(); + for mlen in [16, 20, 32, 33, 64] { + let msg = message(mlen); + let expected = key.create_generator_nonce(&NONCE).mac(&msg); + for split in 0..=mlen { + 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 = 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}"); + } + } +} + +#[test] +fn rejects_changed_tag_nonce_or_message() { + let key = gmac_key::<16>(); + let msg = message(100); + let tag = key.create_generator_nonce(&NONCE).mac(&msg); + let verify = |nonce: &[u8; GCM_NONCE_LEN], msg: &[u8], tag: &[u8; 16]| { + key.create_verifier(nonce).verify(msg, tag) + }; + assert!(verify(&NONCE, &msg, &tag)); + + for i in 0..tag.len() { + let mut bad = tag; + bad[i] ^= 0x01; + assert!(!verify(&NONCE, &msg, &bad), "tag byte {i}"); + } + let mut other_nonce = NONCE; + other_nonce[0] ^= 0x80; + assert!(!verify(&other_nonce, &msg, &tag)); + 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 = key.create_generator_nonce(&NONCE).mac(&[]); + assert!(verify(&NONCE, &[], &empty_tag)); + assert!(!verify(&NONCE, &[0], &empty_tag)); +} + +/// 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 key = gmac_key::<16>(); + let mut rng = FixedSeedRNG::<64>::new(core::array::from_fn(|i| i as u8)); + 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 key's construction suite: a `MACKey` of the permutation's length and strength, bound to +/// the GMAC name or to nothing. +#[test] +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 24eb59870..c35c8b680 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,9 @@ 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_core_test_framework::FixedSeedRNG::::new(init), - ) - .unwrap(); + let (mut fresh, _) = key + .create_encryptor(&mut bouncycastle_core_test_framework::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 +176,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 +203,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 +215,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 a0b2d2574..e97673deb 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::FixedSeedRNG; 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::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,23 +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(); @@ -366,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"); @@ -393,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 6f5412723..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, CcmPacket, 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,35 +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 ToyCcm = CcmPacket; -type ToyPaddedEnc = PaddedBlockCipherEncryptor, PKCS7, 16, 16, 16>; -type ToyPaddedDec = PaddedBlockCipherDecryptor, PKCS7, 16, 16, 16>; +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 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 @@ -58,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() @@ -98,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..]); } @@ -165,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); } @@ -202,37 +278,71 @@ fn ccm_both_directions() { let nonce = [0x24u8; 12]; let aad = b"header"; - let mut enc = ToyCcm::::new(&key(), &nonce, aad, msg.len()).unwrap(); - let mut head = msg[..9].to_vec(); - enc.do_encrypt_inplace(&mut head).unwrap(); - let tail = round_trip::<{ ToyCcm::::SUSPENDED_STATE_LEN }, _>(enc, |mut e| { - let mut data = msg[9..].to_vec(); - e.do_encrypt_inplace(&mut data).unwrap(); - data.extend_from_slice(&e.do_encrypt_final().unwrap()); - data + // Suspended with half the AAD held back and no payload yet, and again mid-payload; both + // must continue to the one answer. + 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, 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(); + let (_, _, tag) = e.do_encrypt_final_detached().unwrap(); + out.extend_from_slice(&tag); + out }); - let (ct_tail, tag) = tail.split_at(msg.len() - 9); + 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, 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(); + out.extend_from_slice(&tag); + out + }); + assert_eq!([&head[..], &tail[..]].concat(), sealed, "both suspend points give the one answer"); + let (ct, tag) = sealed.split_at(37); let tag: [u8; 16] = tag.try_into().unwrap(); - let mut dec = ToyCcm::::new(&key(), &nonce, aad, msg.len()).unwrap(); - dec.do_decrypt_inplace(&mut head).unwrap(); - let plain = round_trip::<{ ToyCcm::::SUSPENDED_STATE_LEN }, _>(dec, |mut d| { - let mut data = ct_tail.to_vec(); - d.do_decrypt_inplace(&mut data).unwrap(); - d.do_decrypt_final(&tag).expect("the tag must verify after a resume"); - data + 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, 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"); + out + }); + assert_eq!([&opened_head[..], &plain[..]].concat(), msg); + + // ...and with part of an inline tag already held back at the suspend point. + 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, 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() }); - assert_eq!(plain, msg[9..]); + assert_eq!(opened, msg); } #[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); @@ -245,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 520c16d36..6c9d78223 100644 --- a/crypto/core-test-framework/src/aead.rs +++ b/crypto/core-test-framework/src/aead.rs @@ -15,11 +15,13 @@ //! [`SymmetricCipherEncryptor`]: bouncycastle_core::traits::SymmetricCipherEncryptor //! [`SymmetricCipherDecryptor`]: bouncycastle_core::traits::SymmetricCipherDecryptor -use crate::symmetric_ciphers::TestFrameworkSymmetricCipher; use crate::{DUMMY_SEED, FixedSeedRNG}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{AEADCipherDecryptor, AEADCipherEncryptor}; +use bouncycastle_core::symmetric_key::{AeadDecryptionKey, AeadEncryptionKey}; +use bouncycastle_core::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, RNG, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, +}; /// Instance of the test framework. pub struct TestFrameworkAEADCipher { @@ -38,103 +40,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, - D: AEADCipherDecryptor, + 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!( @@ -143,8 +118,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 @@ -152,139 +127,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)]; @@ -301,7 +267,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(); @@ -310,10 +276,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())]; @@ -326,93 +291,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(); @@ -435,8 +357,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(); } @@ -454,9 +375,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(); @@ -465,7 +402,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(); @@ -473,9 +410,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(); @@ -487,77 +422,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 +496,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 +512,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 +540,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 +570,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,55 +601,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(); @@ -744,10 +635,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(); @@ -760,9 +651,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]; - // The key-type and security-strength checks on `do_encrypt_init` / `do_decrypt_init` are - // covered by the `TestFrameworkSymmetricCipher` suite run above. + // 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:?}"), + } + } + + // 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" + ); + } } } @@ -786,105 +827,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, - D: AEADCipherDecryptor, - >( - &self, - ) { - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - Self::round_trip_at_every_length_and_chunking::( - &key, - ); - Self::tampering_and_short_input_are_rejected::( - &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 { 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, + 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, - D: AEADCipherDecryptor, - >( - 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]; @@ -892,8 +896,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); @@ -902,10 +905,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; @@ -924,7 +925,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; @@ -943,42 +944,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, - D: AEADCipherDecryptor, - >( - 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"); @@ -987,56 +975,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, - D: AEADCipherDecryptor, - >( - 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 9f303d024..3ba082f2e 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, FixedSeedRNG}; use bouncycastle_core::errors::SymmetricCipherError; -use bouncycastle_core::hazmat::do_hazardous_operations; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +use bouncycastle_core::hazmat::{EcbDecryptionKey, EcbEncryptionKey}; +use bouncycastle_core::symmetric_key::{BlockDecryptionKey, BlockEncryptionKey}; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, RNG}; /// Instance of the test framework. pub struct TestFrameworkBlockCipher { @@ -19,98 +18,128 @@ 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, - D: BlockCipherDecryptor, + 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" @@ -118,91 +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..bcb69c35d --- /dev/null +++ b/crypto/core-test-framework/src/from_key_material.rs @@ -0,0 +1,217 @@ +//! 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, FixedSeedRNG}; +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}; + +/// 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::<64>::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/hash.rs b/crypto/core-test-framework/src/hash.rs index 2b6b0c1dc..a0e2141b7 100644 --- a/crypto/core-test-framework/src/hash.rs +++ b/crypto/core-test-framework/src/hash.rs @@ -1,7 +1,7 @@ //! Generic behaviour tests for anything that implements [`Hash`]. use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, HashAlgParams}; +use bouncycastle_core::traits::{Hash, HashAlgParams, HashOneShot}; /// Instance of the test framework. pub struct TestFrameworkHash { @@ -16,10 +16,10 @@ impl TestFrameworkHash { Self { enable_partial_byte_tests: true } } - /// Checks [`Hash::do_final_out`] and [`Hash::hash_out`] against every buffer length, for a - /// hash whose output length is bound into the computation. + /// Checks [`Hash::do_final_out`] against every buffer length, for a hash whose output length + /// is bound into the computation. /// - /// [`test_hash`](Self::test_hash) covers this too, but only for a `Default + HashAlgParams` + /// [`test_hash`](Self::test_hash) covers this too, but only for a `HashOneShot + HashAlgParams` /// implementor. The SP 800-185 functions take constructor arguments and so cannot reach it; /// `TupleHash` and `ParallelHash` both panicked on a short buffer until this existed. /// @@ -43,12 +43,6 @@ impl TestFrameworkHash { let written = h.do_final_out(&mut buf); assert_eq!(written, length, "a {length}-byte buffer must take {length} bytes"); assert_eq!(buf, expected[..length], "short buffer must truncate the digest"); - - // hash_out is the one-shot spelling of the same thing. - let mut buf = vec![0xAA_u8; length]; - let written = make().hash_out(input, &mut buf); - assert_eq!(written, length, "hash_out must agree with do_final_out"); - assert_eq!(buf, expected[..length], "hash_out must truncate the digest"); } // Exact. @@ -78,21 +72,17 @@ impl TestFrameworkHash { /// This gives good baseline test coverage, but is not exhaustive; for example it does not test /// do_final_partial_bits() or do_final_partial_bits_out() /// because those require different input-output pairs. - pub fn test_hash( - &self, - input: &[u8], - expected_output: &[u8], - ) { + pub fn test_hash(&self, input: &[u8], expected_output: &[u8]) { /*** fn result_len() -> usize ***/ assert_eq!(H::default().output_len(), H::OUTPUT_LEN); - /*** fn hash(self, data: &[u8]) -> Vec **/ - let output_vec = H::default().hash(input); + /*** fn hash(data: &[u8]) -> Vec **/ + let output_vec = H::hash(input); assert_eq!(output_vec, expected_output); - /*** fn hash_out(self, data: &[u8], output: &mut [u8]) -> Result ***/ + /*** fn hash_out(data: &[u8], output: &mut [u8]) -> usize ***/ let mut output_buf = vec![0_u8; H::OUTPUT_LEN]; - H::default().hash_out(input, &mut output_buf); + H::hash_out(input, &mut output_buf); assert_eq!(output_buf, expected_output); /*** fn do_update(&mut self, data: &[u8]) -> Result<(), HashError> ***/ @@ -309,7 +299,7 @@ impl TestFrameworkHash { // test an output slice that's smaller than the result, that it gets truncated let mut out = vec![0; H::OUTPUT_LEN - 2]; - H::default().hash_out(input, out.as_mut_slice()); + H::hash_out(input, out.as_mut_slice()); assert_eq!(&out, &expected_output[..H::OUTPUT_LEN - 2]); } } diff --git a/crypto/core-test-framework/src/key_stream.rs b/crypto/core-test-framework/src/key_stream.rs index 4d9019106..3f46646ac 100644 --- a/crypto/core-test-framework/src/key_stream.rs +++ b/crypto/core-test-framework/src/key_stream.rs @@ -1,11 +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; /// Instance of the test framework. pub struct TestFrameworkKeyStream { @@ -24,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 @@ -34,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, + 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" @@ -66,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"); @@ -74,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(); @@ -103,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 5f7ffb288..08be80235 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 fcb048920..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,129 +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" - ), - } - - 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(13).unwrap(); // truncates should be infallible - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_112bit => { - low_security_key.set_key_len(28).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::None).unwrap(); - } - SecurityStrength::_128bit => { - low_security_key.set_key_len(32).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::_112bit).unwrap(); - } - SecurityStrength::_192bit => { - low_security_key.set_key_len(48).unwrap(); // truncate should be infallible - low_security_key.set_security_strength(SecurityStrength::_128bit).unwrap(); - } - SecurityStrength::_256bit => { - low_security_key.set_key_len(64).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/signature.rs b/crypto/core-test-framework/src/signature.rs index 7c255efba..3b0d9e039 100644 --- a/crypto/core-test-framework/src/signature.rs +++ b/crypto/core-test-framework/src/signature.rs @@ -3,7 +3,7 @@ use crate::DUMMY_SEED; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::traits::{ - Hash, PHSignatureVerifier, PHSigner, SignaturePrivateKey, SignaturePublicKey, + HashOneShot, PHSignatureVerifier, PHSigner, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; @@ -180,7 +180,7 @@ impl TestFrameworkSignature { // todo split this into two params: SIGNER: Signer and VERIFIER: SignatureVerifier PHSIGNER: PHSigner, PHVERIFIER: PHSignatureVerifier, - HASH: Hash + Default, + HASH: HashOneShot, const PK_LEN: usize, const SK_LEN: usize, const SIG_LEN: usize, @@ -270,14 +270,14 @@ impl TestFrameworkSignature { // sign_ph let (pk, sk) = keygen().unwrap(); - let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); + let ph: [u8; PH_LEN] = HASH::hash(msg)[..PH_LEN].try_into().unwrap(); let sig_val = PHSIGNER::sign_ph(&sk, &ph, None).unwrap(); PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); PHVERIFIER::verify_ph(&pk, &ph, None, &sig_val).unwrap(); // sign_ph_out let (pk, sk) = keygen().unwrap(); - let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap(); + let ph: [u8; PH_LEN] = HASH::hash(msg)[..PH_LEN].try_into().unwrap(); let mut sig_val = [0u8; SIG_LEN]; let bytes_written = PHSIGNER::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); 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 1f0c6336d..1cf50135d 100644 --- a/crypto/core-test-framework/src/symmetric_ciphers.rs +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -4,23 +4,21 @@ use crate::{DUMMY_SEED, FixedSeedRNG}; 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::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, + RNG, StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; /// 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 @@ -36,60 +34,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, - D: SymmetricCipherDecryptor, + 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!( @@ -101,19 +83,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(), @@ -121,21 +101,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"); } @@ -146,7 +126,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()); @@ -164,13 +144,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, @@ -179,7 +159,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()); @@ -204,10 +184,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); @@ -216,22 +195,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!( @@ -239,7 +217,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); @@ -256,46 +234,43 @@ 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(); + 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") } @@ -307,29 +282,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]; @@ -340,11 +314,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| { @@ -359,31 +330,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"); @@ -394,7 +348,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]]; @@ -411,12 +365,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]]; @@ -435,59 +389,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"), - }; - } } } @@ -502,43 +403,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, - D: StreamCipherDecryptor, + 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(); @@ -548,7 +445,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(); @@ -563,128 +460,65 @@ 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-test-framework/src/xof.rs b/crypto/core-test-framework/src/xof.rs index 2c941f370..32686fe16 100644 --- a/crypto/core-test-framework/src/xof.rs +++ b/crypto/core-test-framework/src/xof.rs @@ -234,12 +234,6 @@ impl TestFrameworkXOF { let n = c.do_final_out(&mut buf); assert_eq!(n, output_len); assert_eq!(buf, via_hash, "do_final_out must agree with do_final"); - - // The one-shot Hash entry points. - assert_eq!(make().hash(input), via_hash, "hash must equal update-then-do_final"); - let mut buf = vec![0xFFu8; output_len]; - assert_eq!(make().hash_out(input, &mut buf), output_len); - assert_eq!(buf, via_hash, "hash_out must agree with hash"); } /// A partial final byte of input, in both the XOF and the Hash spelling. 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 fccb2f4e4..b830196e3 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::Algorithm; // Imports needed for docs @@ -17,39 +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: - Algorithm + Sized -{ - /// 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`], both as a - /// [`SymmetricCipherError::KeyMaterialError`]. - fn new( - key: &KeyMaterial, - init_data: &[u8; INIT_DATA_LEN], - ) -> Result; - +/// 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: Algorithm + Sized { /// 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 669a54bdf..ea55088bf 100644 --- a/crypto/core/src/hazmat/mod.rs +++ b/crypto/core/src/hazmat/mod.rs @@ -11,10 +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::{ + 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 new file mode 100644 index 000000000..2d91084ef --- /dev/null +++ b/crypto/core/src/hazmat/mode.rs @@ -0,0 +1,456 @@ +//! The mode traits: how a mode turns a keyed permutation into a key state, and a key state into +//! an encryptor or decryptor. These are what a mode type such as `GCM` implements, and what the +//! role traits in [`crate::symmetric_key`] forward to. They take a bare key state, which is why +//! they are hazmat: the safe route to an encryptor is `key.create_encryptor(rng)`. +//! +//! 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 [`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::traits::{ + AEADCipherDecryptor, AEADCipherEncryptor, BlockCipherDecryptor, BlockCipherEncryptor, MAC, RNG, + SymmetricCipherDecryptor, SymmetricCipherEncryptor, +}; + +/// A mode's per-key state over the permutation `P`. +/// +/// Implemented by a mode type for every permutation it can run over. The key object stores +/// `KeyState` and nothing else, so a mode that derives something from the key once -- GCM's hash +/// subkey -- keeps it here and pays for it per key rather than per message. +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. 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 + /// cipher mode, [`KeyType::MACKey`] for a MAC mode. The key's constructor checks it. + const KEY_TYPE: KeyType; + + /// Builds the per-key state from an already-keyed permutation. + fn key_state(perm: P) -> Self::KeyState; +} + +/// 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< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// 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<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + ) -> 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<'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 + /// [`encrypt_init`](Self::encrypt_init)'s encryptor a chunk at a time; a mode whose worker + /// runs in place natively overrides it. + /// + /// # Errors + /// As [`encrypt_init`](Self::encrypt_init), or any error of the encryptor's streaming methods. + fn encrypt_detached_in_place( + state: &Self::KeyState, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = Self::encrypt_init(state, rng)?; + let tag = crate::provided::encrypt_detached_in_place(enc, aad, buf)?; + Ok((nonce, tag)) + } + + /// As [`encrypt_detached_in_place`](Self::encrypt_detached_in_place) under a nonce the caller + /// supplies, over [`encrypt_init_nonce`](Self::encrypt_init_nonce). + /// + /// # Errors + /// Any error of the encryptor's streaming methods. + fn encrypt_detached_in_place_nonce( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + crate::provided::encrypt_detached_in_place(Self::encrypt_init_nonce(state, nonce), aad, buf) + } +} + +/// The decryption side of an AEAD mode over `P`: the mode's own constructor for its decryptor. +pub trait AeadDecryptMode< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// 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<'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 + /// be checked. Provided over [`decrypt_init`](Self::decrypt_init) as + /// [`AeadEncryptMode::encrypt_detached_in_place`] is over its constructor. + /// + /// # Errors + /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, or any error of + /// the decryptor's streaming methods. + fn decrypt_detached_in_place( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + crate::provided::decrypt_detached_in_place(Self::decrypt_init(state, nonce), aad, buf, tag) + } +} + +/// 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 AeadDeclaredEncryptMode< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// 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`. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the nonce draw fails, or the mode's own refusal of + /// a length it cannot encode. + fn encrypt_init<'k>( + state: &'k Self::KeyState, + rng: &mut dyn RNG, + aad_len: usize, + payload_len: usize, + ) -> 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<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> 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 + /// at a time; a mode whose worker runs in place natively overrides it. + /// + /// # Errors + /// As [`encrypt_init`](Self::encrypt_init), or any error of the encryptor's streaming methods. + fn encrypt_detached_in_place( + state: &Self::KeyState, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = Self::encrypt_init(state, rng, aad.len(), buf.len())?; + let tag = crate::provided::encrypt_detached_in_place(enc, aad, buf)?; + Ok((nonce, tag)) + } + + /// As [`encrypt_detached_in_place`](Self::encrypt_detached_in_place) under a nonce the caller + /// supplies. + /// + /// # Errors + /// As [`encrypt_init_nonce`](Self::encrypt_init_nonce), or any error of the encryptor's + /// streaming methods. + fn encrypt_detached_in_place_nonce( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + let enc = Self::encrypt_init_nonce(state, nonce, aad.len(), buf.len())?; + crate::provided::encrypt_detached_in_place(enc, aad, buf) + } +} + +/// The decryption side of an AEAD mode over `P` whose worker must know the AAD and payload +/// lengths before its first byte; see [`AeadDeclaredEncryptMode`]. +pub trait AeadDeclaredDecryptMode< + P, + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>: Mode

+{ + /// 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<'k>( + state: &'k Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> 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 + /// before the tag could be checked. + /// + /// # Errors + /// As [`decrypt_init`](Self::decrypt_init), [`SymmetricCipherError::AEADTagCheckFailed`] if + /// the tag does not verify, or any error of the decryptor's streaming methods. + fn decrypt_detached_in_place( + state: &Self::KeyState, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + let dec = Self::decrypt_init(state, nonce, aad.len(), buf.len())?; + crate::provided::decrypt_detached_in_place(dec, aad, buf, tag) + } +} + +/// 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`. 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<'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 bba2c91a6..ea41754e3 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -305,7 +305,7 @@ impl KeyMaterial { } /// Creates a new instance of KeyMaterial containing random bytes from the provided random number generator. - pub fn from_rng(rng: &mut impl RNG) -> Result { + pub fn from_rng(rng: &mut (impl RNG + ?Sized)) -> Result { let mut key = Self::new(); do_hazardous_operations(&mut key, |key| { @@ -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/lib.rs b/crypto/core/src/lib.rs index 3ec84e9f0..3a5254e60 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -9,5 +9,7 @@ pub mod errors; pub mod hazmat; pub mod key_material; +mod provided; pub mod security_strength; +pub mod symmetric_key; pub mod traits; diff --git a/crypto/core/src/provided.rs b/crypto/core/src/provided.rs new file mode 100644 index 000000000..bc4022e7e --- /dev/null +++ b/crypto/core/src/provided.rs @@ -0,0 +1,322 @@ +//! 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 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 +//! to a small stack buffer and processed back into the bytes it came from. The output never +//! overtakes the input: a streaming cipher emits at most what it has been given, so after +//! `consumed` bytes have been read, at most `consumed` have been written, and the slice +//! `[written, consumed)` is both already read and long enough for what this call can emit. That +//! holds for a cipher that holds bytes back as well as for one that releases them at once. The +//! price over a true in-place update is one memcpy of the data through `CHUNK` bytes of stack, +//! and no second buffer the size of the message. + +use crate::errors::SymmetricCipherError; +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; + +/// Encrypts `plaintext` through `enc` after absorbing `aad` into `ciphertext`, tag inline, and +/// returns the number of bytes written. `ciphertext` is zeroized first and must hold +/// `encrypt_out_len(plaintext.len())` bytes. +/// +/// # Errors +/// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, or any error of +/// the worker's streaming methods. +pub(crate) fn encrypt_inline< + E, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut enc: E, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], +) -> Result +where + E: AEADCipherEncryptor, +{ + ciphertext.fill(0); + let needed = E::encrypt_out_len(plaintext.len()); + if ciphertext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + 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(written + last_len) +} + +/// As [`encrypt_inline`] with the tag detached: `ciphertext` needs only +/// `encrypt_detached_out_len(plaintext.len())` bytes, and the tag is returned. +/// +/// # Errors +/// As [`encrypt_inline`]. +pub(crate) fn encrypt_detached< + E, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut enc: E, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], +) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> +where + E: AEADCipherEncryptor, +{ + ciphertext.fill(0); + let needed = E::encrypt_detached_out_len(plaintext.len()); + if ciphertext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + 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)?; + // `encrypt_detached_out_len` bounds `written + final_len`, so this fits in + // `ciphertext[..needed]`. + ciphertext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); + Ok((written + final_len, tag)) +} + +/// Encrypts `buf` in place through `enc` after absorbing `aad`, returning the detached tag. +/// +/// # Errors +/// Any error of the worker's streaming methods. +pub(crate) fn encrypt_detached_in_place< + E, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut enc: E, + aad: &[u8], + buf: &mut [u8], +) -> Result<[u8; TAG_LEN], SymmetricCipherError> +where + E: AEADCipherEncryptor, +{ + enc.do_update_aad(aad)?; + let mut tmp = [0u8; CHUNK]; + let mut consumed = 0; + let mut written = 0; + while consumed < buf.len() { + let n = CHUNK.min(buf.len() - consumed); + tmp[..n].copy_from_slice(&buf[consumed..consumed + n]); + consumed += n; + written += enc.do_encrypt_out(&tmp[..n], &mut buf[written..consumed])?; + } + let mut last = [0u8; FINAL_LEN]; + let (last_len, tag) = enc.do_encrypt_final_detached_out(&mut last)?; + buf[written..written + last_len].copy_from_slice(&last[..last_len]); + Ok(tag) +} + +/// Decrypts `ciphertext`, tag inline, through `dec` after absorbing `aad` 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. +/// +/// # Errors +/// [`SymmetricCipherError::OutputBufferTooSmall`] if `plaintext` is too short, +/// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, or any error of the +/// worker's streaming methods. +pub(crate) fn decrypt_inline< + D, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut dec: D, + aad: &[u8], + ciphertext: &[u8], + plaintext: &mut [u8], +) -> Result +where + D: AEADCipherDecryptor, +{ + plaintext.fill(0); + let needed = D::decrypt_out_len(ciphertext.len()); + if plaintext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + 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) => { + // What `do_decrypt_out` released is unauthenticated, and this one-shot owns the + // whole message, so it does not leave that in the caller's hands. + plaintext[..written].fill(0); + Err(e) + } + } +} + +/// As [`decrypt_inline`] with the tag supplied separately; `plaintext` needs +/// `decrypt_detached_out_len(ciphertext.len())` bytes. +/// +/// # Errors +/// As [`decrypt_inline`]. +pub(crate) fn decrypt_detached< + D, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut dec: D, + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + plaintext: &mut [u8], +) -> Result +where + D: AEADCipherDecryptor, +{ + plaintext.fill(0); + let needed = D::decrypt_detached_out_len(ciphertext.len()); + if plaintext.len() < needed { + return Err(SymmetricCipherError::OutputBufferTooSmall(needed)); + } + 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) => { + plaintext[written..written + final_len].copy_from_slice(&final_buf[..final_len]); + Ok(written + final_len) + } + Err(e) => { + plaintext[..written].fill(0); + Err(e) + } + } +} + +/// Decrypts `buf` in place through `dec` after absorbing `aad`, and checks `tag`. On any error +/// `buf` is zeroized: the plaintext was written into it before the tag could be checked. +/// +/// # Errors +/// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, or any error of the +/// worker's streaming methods. +pub(crate) fn decrypt_detached_in_place< + D, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +>( + mut dec: D, + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], +) -> Result<(), SymmetricCipherError> +where + D: AEADCipherDecryptor, +{ + let result = (|| { + dec.do_update_aad(aad)?; + let mut tmp = [0u8; CHUNK]; + let mut consumed = 0; + let mut written = 0; + while consumed < buf.len() { + let n = CHUNK.min(buf.len() - consumed); + tmp[..n].copy_from_slice(&buf[consumed..consumed + n]); + consumed += n; + written += dec.do_decrypt_out(&tmp[..n], &mut buf[written..consumed])?; + } + let mut last = [0u8; FINAL_LEN]; + let last_len = dec.do_decrypt_final_detached_out(tag, &mut last)?; + buf[written..written + last_len].copy_from_slice(&last[..last_len]); + Ok(()) + })(); + if result.is_err() { + buf.fill(0); + } + result +} + +/// The payload length inside an inline `ciphertext || tag`, or the malformed-input error if +/// there is not room for a tag. +/// +/// # Errors +/// [`SymmetricCipherError::DecryptionFailed`] if `ciphertext_len < TAG_LEN`. +pub(crate) fn inline_payload_len( + ciphertext_len: usize, +) -> 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 new file mode 100644 index 000000000..401b5b319 --- /dev/null +++ b/crypto/core/src/symmetric_key.rs @@ -0,0 +1,1165 @@ +//! Key objects for symmetric ciphers and MACs: how a key is made, and what it can produce. +//! +//! A key object is bound to one algorithm and, for a cipher, one mode -- `AES128Key`, +//! `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. 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: +//! +//! * **Construction**, for keys the process holds: [`FromKeyMaterial`] for a key of exactly +//! `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 +//! 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, 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 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)?; +//! let (mut enc, nonce) = key.create_encryptor(&mut rng)?; // Result for the nonce draw only +//! let mut dec = key.create_decryptor(&nonce); // infallible +//! +//! let key = HmacKey::::generate(&mut rng)?; +//! let mut gen = key.create_generator(); // infallible, any number of times +//! ``` +//! +//! Nothing here instantiates a DRBG for the caller: a method that draws randomness takes +//! `&mut dyn RNG`. The caller holds one RNG across operations. +//! +//! [`Mode`]: crate::hazmat::Mode +//! [`AeadEncryptMode`]: crate::hazmat::AeadEncryptMode +//! [`AeadDecryptMode`]: crate::hazmat::AeadDecryptMode + +use crate::errors::{MACError, SymmetricCipherError}; +use crate::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +#[cfg(feature = "std")] +use crate::traits::AEADEncryptedTuple; +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. +/// +/// Implemented per concrete key length (`AES128Key` implements `FromKeyMaterial<16>`), because +/// a `KeyMaterial` argument cannot be sized from an associated constant on stable Rust; the +/// fixed-capacity argument is what makes a wrong-length key a compile error. +pub trait FromKeyMaterial: Sized { + /// The [`KeyType`] the material must carry: [`KeyType::SymmetricCipherKey`] for a cipher key, + /// [`KeyType::MACKey`] for a MAC built on a cipher. A mode-bound key takes it from its mode. + const KEY_TYPE: KeyType; + + /// Validates `key` and does the construction's per-key work (schedule expansion and whatever + /// the mode derives from it) once. + /// + /// # Errors + /// [`SymmetricCipherError::KeyMaterialError`] if the key is not of type + /// [`Self::KEY_TYPE`], carries a security strength below the key length, or fails the + /// algorithm's own key checks. + fn from_key_material(key: &KeyMaterial) -> Result; + + /// Draws `KEY_LEN` bytes from `rng`, tags them [`Self::KEY_TYPE`] and constructs the key. The + /// material carries the RNG's security strength, so an RNG instantiated below the key's + /// strength is refused by [`from_key_material`](Self::from_key_material). + /// + /// # Errors + /// [`SymmetricCipherError::KeyMaterialError`] if the RNG fails or its strength is too low. + fn generate(rng: &mut dyn RNG) -> Result { + let mut key = KeyMaterial::::from_rng(rng)?; + key.set_key_type(Self::KEY_TYPE)?; + Self::from_key_material(&key) + } +} + +/// Construction of a key object from key material of any length: HMAC and KMAC accept any byte +/// string as a key, so [`FromKeyMaterial`] does not fit them. +/// +/// This is the only constructor with a weak-key form. A key built on a cipher takes a full-length +/// cipher key through [`FromKeyMaterial`] and the cipher's own policy, with no exemption. +pub trait FromAnyKeyMaterial: Sized { + /// Checks the key type and that the key's security strength meets the construction's, then + /// does the construction's per-key work once. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the key type is wrong or the strength is too low. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result; + + /// As [`from_key_material`](Self::from_key_material) without the strength check, for a + /// protocol that calls for a weak or all-zero key such as an absent HKDF salt. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the key type is wrong. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result; + + /// Draws a key of the length the construction recommends from `rng`, tagged + /// [`KeyType::MACKey`], and constructs the key object from it. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails or its strength is below the + /// construction's. + fn generate(rng: &mut dyn RNG) -> Result; +} + +// 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. +/// +/// The encryptor is the mode's ordinary worker; this trait adds where it comes from. The +/// one-shots are provided over [`create_encryptor`](Self::create_encryptor) and the worker's +/// streaming methods, and return the same bytes as streaming the message by hand. +/// +/// # The nonce is drawn by default +/// +/// Every form without `_nonce` in its name draws the nonce from the RNG it is given and returns +/// it. The `_nonce` forms take the nonce instead, for a protocol whose nonce is a counter or +/// another value it manages itself; the caller then takes on the obligation the drawing forms +/// discharge for it, that the nonce never repeats under this key. +pub trait AeadEncryptionKey< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// 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 + /// [`AeadDecryptionKey::create_decryptor`]. + /// + /// # 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>; + + /// Begins an encryption under a nonce the caller supplies. Infallible, as + /// [`AeadDecryptionKey::create_decryptor`] is. + 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 + /// of bytes written with the generated nonce. 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_with_aad_out( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; NONCE_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng)?; + let written = crate::provided::encrypt_inline(enc, aad, plaintext, ciphertext)?; + Ok((written, nonce)) + } + + /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out), allocating the ciphertext. + /// + /// # Errors + /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). + #[cfg(feature = "std")] + fn encrypt_with_aad( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> { + 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)) + } + + /// One-shot with the tag detached: as [`encrypt_with_aad_out`](Self::encrypt_with_aad_out), + /// with `ciphertext` needing only `encrypt_detached_out_len(plaintext.len())` bytes and the tag + /// returned separately. + /// + /// # Errors + /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). + fn encrypt_detached_out( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng)?; + let (written, tag) = crate::provided::encrypt_detached(enc, aad, plaintext, ciphertext)?; + Ok((written, nonce, tag)) + } + + /// As [`encrypt_detached_out`](Self::encrypt_detached_out), allocating the ciphertext. + /// + /// # Errors + /// As [`encrypt_detached_out`](Self::encrypt_detached_out). + #[cfg(feature = "std")] + fn encrypt_detached( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ) -> Result, SymmetricCipherError> { + 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); + Ok((nonce, ciphertext, tag)) + } + + /// One-shot in place, tag detached: encrypts `buf` under `aad` and a nonce drawn from `rng`, + /// in its own buffer, and returns the nonce and tag. For a message that is already in a + /// buffer the caller owns, this needs no second buffer the size of the message. + /// + /// # Errors + /// As [`encrypt_detached_out`](Self::encrypt_detached_out). + fn encrypt_detached_in_place( + &self, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng)?; + let tag = crate::provided::encrypt_detached_in_place(enc, aad, buf)?; + Ok((nonce, tag)) + } + + /// [`encrypt_with_aad_out`](Self::encrypt_with_aad_out) under a nonce the caller supplies; + /// returns the number of bytes written. + /// + /// # Errors + /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short, or any error + /// of the encryptor's streaming methods. + fn encrypt_with_aad_nonce_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result { + crate::provided::encrypt_inline( + self.create_encryptor_nonce(nonce), + aad, + plaintext, + ciphertext, + ) + } + + /// As [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out), allocating the + /// ciphertext. + /// + /// # Errors + /// As [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out). + #[cfg(feature = "std")] + fn encrypt_with_aad_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// [`encrypt_detached_out`](Self::encrypt_detached_out) under a nonce the caller supplies; + /// returns the number of bytes written and the tag. + /// + /// # Errors + /// As [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out). + fn encrypt_detached_nonce_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { + crate::provided::encrypt_detached( + self.create_encryptor_nonce(nonce), + aad, + plaintext, + ciphertext, + ) + } + + /// As [`encrypt_detached_nonce_out`](Self::encrypt_detached_nonce_out), allocating the + /// ciphertext. + /// + /// # Errors + /// As [`encrypt_detached_nonce_out`](Self::encrypt_detached_nonce_out). + #[cfg(feature = "std")] + fn encrypt_detached_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError> { + 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); + Ok((ciphertext, tag)) + } + + /// [`encrypt_detached_in_place`](Self::encrypt_detached_in_place) under a nonce the caller + /// supplies; returns the tag. + /// + /// # Errors + /// Any error of the encryptor's streaming methods. + fn encrypt_detached_in_place_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + crate::provided::encrypt_detached_in_place(self.create_encryptor_nonce(nonce), aad, buf) + } +} + +/// A key that can begin an authenticated decryption: the decryption half of an AEAD key. +/// +/// Separate from [`AeadEncryptionKey`] so that a key type can carry one side and not the other. +/// Construction is infallible: the key is already validated and the nonce length is in the type. +pub trait AeadDecryptionKey< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// 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<'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 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, + /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, or any error of + /// the decryptor's streaming methods. + fn decrypt_with_aad_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result { + crate::provided::decrypt_inline(self.create_decryptor(nonce), aad, ciphertext, plaintext) + } + + /// As [`decrypt_with_aad_out`](Self::decrypt_with_aad_out), allocating the plaintext. + /// + /// # Errors + /// As [`decrypt_with_aad_out`](Self::decrypt_with_aad_out). + #[cfg(feature = "std")] + fn decrypt_with_aad( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// One-shot with the tag detached: as [`decrypt_with_aad_out`](Self::decrypt_with_aad_out), + /// with `plaintext` needing `decrypt_detached_out_len(ciphertext.len())` bytes and the tag + /// supplied separately. + /// + /// # Errors + /// As [`decrypt_with_aad_out`](Self::decrypt_with_aad_out). + fn decrypt_detached_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + plaintext: &mut [u8], + ) -> Result { + crate::provided::decrypt_detached( + self.create_decryptor(nonce), + aad, + ciphertext, + tag, + plaintext, + ) + } + + /// As [`decrypt_detached_out`](Self::decrypt_detached_out), allocating the plaintext. + /// + /// # Errors + /// As [`decrypt_detached_out`](Self::decrypt_detached_out). + #[cfg(feature = "std")] + fn decrypt_detached( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// One-shot in place, tag detached: decrypts `buf` under `nonce` and `aad` in its own buffer + /// and checks `tag`. On any error `buf` is left zeroized, as the `_out` forms leave their + /// output: the plaintext was written into it before the tag could be checked. + /// + /// # Errors + /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify, or any error of + /// the decryptor's streaming methods. + fn decrypt_detached_in_place( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + crate::provided::decrypt_detached_in_place(self.create_decryptor(nonce), aad, buf, tag) + } +} + +/// The encryption half of an AEAD key whose mode must know the AAD and payload lengths before +/// 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 AeadDeclaredEncryptionKey< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// 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. + /// + /// # Errors + /// [`SymmetricCipherError::RNGError`] if the nonce draw fails, or the mode's refusal of a + /// length it cannot encode. + fn create_encryptor<'k>( + &'k self, + rng: &mut dyn RNG, + aad_len: usize, + payload_len: usize, + ) -> 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<'k>( + &'k self, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError>; + + /// As [`AeadEncryptionKey::encrypt_with_aad_out`]. + /// + /// # Errors + /// As [`create_encryptor`](Self::create_encryptor), or + /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short. + fn encrypt_with_aad_out( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; NONCE_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng, aad.len(), plaintext.len())?; + let written = crate::provided::encrypt_inline(enc, aad, plaintext, ciphertext)?; + Ok((written, nonce)) + } + + /// As [`AeadEncryptionKey::encrypt_with_aad`]. + /// + /// # Errors + /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). + #[cfg(feature = "std")] + fn encrypt_with_aad( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError> { + 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)) + } + + /// As [`AeadEncryptionKey::encrypt_detached_out`]. + /// + /// # Errors + /// As [`encrypt_with_aad_out`](Self::encrypt_with_aad_out). + fn encrypt_detached_out( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng, aad.len(), plaintext.len())?; + let (written, tag) = crate::provided::encrypt_detached(enc, aad, plaintext, ciphertext)?; + Ok((written, nonce, tag)) + } + + /// As [`AeadEncryptionKey::encrypt_detached`]. + /// + /// # Errors + /// As [`encrypt_detached_out`](Self::encrypt_detached_out). + #[cfg(feature = "std")] + fn encrypt_detached( + &self, + rng: &mut dyn RNG, + aad: &[u8], + plaintext: &[u8], + ) -> Result, SymmetricCipherError> { + 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); + Ok((nonce, ciphertext, tag)) + } + + /// As [`AeadEncryptionKey::encrypt_detached_in_place`]. + /// + /// # Errors + /// As [`create_encryptor`](Self::create_encryptor), or any error of the encryptor's + /// streaming methods. + fn encrypt_detached_in_place( + &self, + rng: &mut dyn RNG, + aad: &[u8], + buf: &mut [u8], + ) -> Result<([u8; NONCE_LEN], [u8; TAG_LEN]), SymmetricCipherError> { + let (enc, nonce) = self.create_encryptor(rng, aad.len(), buf.len())?; + let tag = crate::provided::encrypt_detached_in_place(enc, aad, buf)?; + Ok((nonce, tag)) + } + + /// As [`AeadEncryptionKey::encrypt_with_aad_nonce_out`]. + /// + /// # Errors + /// As [`create_encryptor_nonce`](Self::create_encryptor_nonce), or + /// [`SymmetricCipherError::OutputBufferTooSmall`] if `ciphertext` is too short. + fn encrypt_with_aad_nonce_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result { + let enc = self.create_encryptor_nonce(nonce, aad.len(), plaintext.len())?; + crate::provided::encrypt_inline(enc, aad, plaintext, ciphertext) + } + + /// As [`AeadEncryptionKey::encrypt_with_aad_nonce`]. + /// + /// # Errors + /// As [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out). + #[cfg(feature = "std")] + fn encrypt_with_aad_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// As [`AeadEncryptionKey::encrypt_detached_nonce_out`]. + /// + /// # Errors + /// As [`encrypt_with_aad_nonce_out`](Self::encrypt_with_aad_nonce_out). + fn encrypt_detached_nonce_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<(usize, [u8; TAG_LEN]), SymmetricCipherError> { + let enc = self.create_encryptor_nonce(nonce, aad.len(), plaintext.len())?; + crate::provided::encrypt_detached(enc, aad, plaintext, ciphertext) + } + + /// As [`AeadEncryptionKey::encrypt_detached_nonce`]. + /// + /// # Errors + /// As [`encrypt_detached_nonce_out`](Self::encrypt_detached_nonce_out). + #[cfg(feature = "std")] + fn encrypt_detached_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + plaintext: &[u8], + ) -> Result<(Vec, [u8; TAG_LEN]), SymmetricCipherError> { + 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); + Ok((ciphertext, tag)) + } + + /// As [`AeadEncryptionKey::encrypt_detached_in_place_nonce`]. + /// + /// # Errors + /// As [`create_encryptor_nonce`](Self::create_encryptor_nonce), or any error of the + /// encryptor's streaming methods. + fn encrypt_detached_in_place_nonce( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + ) -> Result<[u8; TAG_LEN], SymmetricCipherError> { + let enc = self.create_encryptor_nonce(nonce, aad.len(), buf.len())?; + crate::provided::encrypt_detached_in_place(enc, aad, buf) + } +} + +/// The decryption half of an AEAD key whose mode must know the lengths up front; see +/// [`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 AeadDeclaredDecryptionKey< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + const FINAL_LEN: usize, +> +{ + /// 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<'k>( + &'k self, + nonce: &[u8; NONCE_LEN], + aad_len: usize, + payload_len: usize, + ) -> Result, SymmetricCipherError>; + + /// As [`AeadDecryptionKey::decrypt_with_aad_out`]. + /// + /// # Errors + /// As [`AeadDecryptionKey::decrypt_with_aad_out`], and + /// [`SymmetricCipherError::DecryptionFailed`] if `ciphertext` is too short for a tag. + fn decrypt_with_aad_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result { + let payload_len = crate::provided::inline_payload_len::(ciphertext.len())?; + let dec = self.create_decryptor(nonce, aad.len(), payload_len)?; + crate::provided::decrypt_inline(dec, aad, ciphertext, plaintext) + } + + /// As [`AeadDecryptionKey::decrypt_with_aad`]. + /// + /// # Errors + /// As [`decrypt_with_aad_out`](Self::decrypt_with_aad_out). + #[cfg(feature = "std")] + fn decrypt_with_aad( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// As [`AeadDecryptionKey::decrypt_detached_out`]. + /// + /// # Errors + /// As [`AeadDecryptionKey::decrypt_detached_out`], or + /// [`create_decryptor`](Self::create_decryptor)'s. + fn decrypt_detached_out( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + plaintext: &mut [u8], + ) -> Result { + let dec = self.create_decryptor(nonce, aad.len(), ciphertext.len())?; + crate::provided::decrypt_detached(dec, aad, ciphertext, tag, plaintext) + } + + /// As [`AeadDecryptionKey::decrypt_detached`]. + /// + /// # Errors + /// As [`decrypt_detached_out`](Self::decrypt_detached_out). + #[cfg(feature = "std")] + fn decrypt_detached( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + ) -> Result, SymmetricCipherError> { + 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) + } + + /// As [`AeadDecryptionKey::decrypt_detached_in_place`]; `buf` is untouched if construction + /// refuses the lengths, and zeroized on any error after that. + /// + /// # Errors + /// As [`AeadDecryptionKey::decrypt_detached_in_place`], or + /// [`create_decryptor`](Self::create_decryptor)'s. + fn decrypt_detached_in_place( + &self, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + buf: &mut [u8], + tag: &[u8; TAG_LEN], + ) -> Result<(), SymmetricCipherError> { + let dec = self.create_decryptor(nonce, aad.len(), buf.len())?; + crate::provided::decrypt_detached_in_place(dec, aad, buf, tag) + } +} + +/// 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 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) 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, 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<'k>(&'k self) -> Self::Generator<'k>; + + /// One-shot: the MAC of `data` into `out`, which is zeroized first; returns the number of + /// bytes written. + /// + /// # Errors + /// [`MACError::InvalidLength`] if `out` is shorter than the worker's minimum output. + fn mac_out(&self, data: &[u8], out: &mut [u8]) -> Result { + out.fill(0); + let mut generator = self.create_generator(); + generator.do_update(data); + generator.do_final_out(out) + } + + /// One-shot: the full-length MAC of `data`. + #[cfg(feature = "std")] + fn mac(&self, data: &[u8]) -> Vec { + let mut generator = self.create_generator(); + generator.do_update(data); + generator.do_final() + } +} + +/// 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, borrowing the key for `'k`. + type Verifier<'k>: MAC + where + Self: 'k; + + /// Begins the verification of a new message. Infallible. + 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 { + let mut ver = self.create_verifier(); + ver.do_update(data); + ver.do_verify_final(mac) + } +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 4a68f9a45..6b06da493 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 @@ -101,141 +95,13 @@ 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`. fn decrypt_detached_out_len(ciphertext_len: usize) -> usize { 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 { - 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 { - 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> { - 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> { - 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 @@ -261,7 +127,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, @@ -270,14 +136,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. /// @@ -293,12 +159,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 @@ -341,258 +203,13 @@ 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. fn encrypt_detached_out_len(plaintext_len: usize) -> usize { 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> { - 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> { - 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> { - 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> { - 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> { - 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> { - 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; - - /// 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> { - 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> { - 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 { - 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> { - 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. @@ -605,6 +222,26 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } +/// What a worker can say about itself at run time: the name and maximum strength +/// [`Algorithm`] carries as constants, as methods, so that a trait object can be asked. +/// Every `Algorithm` implements it; a type that is not one algorithm (a factory enum, which +/// picks its algorithm at run time) implements it directly. +pub trait AlgorithmInfo { + /// [`Algorithm::ALG_NAME`] of the algorithm this value runs. + fn alg_name(&self) -> &'static str; + /// [`Algorithm::MAX_SECURITY_STRENGTH`] of the algorithm this value runs. + fn max_security_strength(&self) -> SecurityStrength; +} + +impl AlgorithmInfo for T { + fn alg_name(&self) -> &'static str { + T::ALG_NAME + } + fn max_security_strength(&self) -> SecurityStrength { + T::MAX_SECURITY_STRENGTH + } +} + /// Some algorithms have an assigned OID. pub trait AlgorithmOID { /// The OID in component form -- each u32 is one OID component. @@ -615,17 +252,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, ->: Algorithm + Sized +pub trait BlockCipherDecryptor: + Algorithm + Sized { - /// 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; /// 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 @@ -635,23 +264,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 { - 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. @@ -694,31 +306,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, ->: Algorithm + Sized +pub trait BlockCipherEncryptor: + Algorithm + Sized { - /// 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>; - /// 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>; /// The implementor hook: encrypts consecutive whole blocks in place. A sequence of calls is /// equivalent to one call over the concatenation. /// @@ -735,43 +325,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> { - 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> { - 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. @@ -801,7 +354,7 @@ pub trait BlockCipherEncryptor< /// siblings -- without re-absorbing the prefix each time. Every implementor is a fixed-size state /// plus a small buffer, so the derive is the right implementation; the shared test framework checks /// that a clone and its original finish to the same digest, and diverge once fed different input. -pub trait Hash: Algorithm + Clone { +pub trait Hash: AlgorithmInfo + Clone { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -821,16 +374,6 @@ pub trait Hash: Algorithm + Clone { /// discussion on [`XOF`]. fn output_len(&self) -> usize; - /// A static one-shot API that hashes the provided data. - /// `data` can be of any length, including zero bytes. - fn hash(self, data: &[u8]) -> Vec; - - /// A static one-shot API that hashes the provided data into the provided output slice. - /// `data` can be of any length, including zero bytes. - /// The entire output buffer is zeroized before the hash output is written. - /// The return value is the number of bytes written. - fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize; - /// Provide a chunk of data to be absorbed into the hashes. /// `data` can be of any length, including zero bytes. /// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times. @@ -882,9 +425,31 @@ pub trait Hash: Algorithm + Clone { num_bits: usize, output: &mut [u8], ) -> Result; +} - /// Returns the maximum security strength that this KDF is capable of supporting, based on the underlying primitives. - fn max_security_strength(&self) -> SecurityStrength; +/// The one-shot form of a hash that takes no parameters: `SHA256::hash(msg)`. +/// +/// Both functions run [`Hash::do_update`] then [`Hash::do_final`] on a [`Default`] instance. +/// Implemented by the hashes whose `Default` is the function itself (SHA-2, SHA-3, SHAKE, SM3, +/// Ascon); the parameterised ones (cSHAKE, KMAC, TupleHash, ParallelHash, Ascon-CXOF) and the +/// factories offer an instance method `hash(self, data)` instead. Kept off [`Hash`] because +/// receiver-less functions would stop it ever being a trait object. +pub trait HashOneShot: Hash + Default { + /// Hashes `data` into `output`, which is zeroized first, and returns the number of bytes + /// written. A buffer shorter than the digest takes a truncated digest; a longer one takes + /// the digest at its start and zeros after, as [`Hash::do_final_out`] does. + fn hash_out(data: &[u8], output: &mut [u8]) -> usize { + let mut h = Self::default(); + h.do_update(data); + h.do_final_out(output) + } + + /// Hashes `data` and returns the digest. + fn hash(data: &[u8]) -> Vec { + let mut h = Self::default(); + h.do_update(data); + h.do_final() + } } /// Standard parameters for a hash function. @@ -899,7 +464,7 @@ pub trait HashAlgParams: Algorithm { /// A Key Derivation Function (KDF) is a function that takes in one or more input key and some unstructured /// additional input, and uses them to produces a derived key. -pub trait KDF: Default { +pub trait KDF: AlgorithmInfo + Default { /// Implementations of this function are capable of deriving an output key from an input key, /// assuming that they have been properly initialized. /// @@ -988,9 +553,6 @@ pub trait KDF: Default { additional_input: &[u8], output_key: &mut impl KeyMaterialTrait, ) -> Result; - - /// Returns the maximum security strength that this KDF is capable of supporting, based on the underlying primitives. - fn max_security_strength(&self) -> SecurityStrength; } /// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations: @@ -1091,8 +653,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. @@ -1102,37 +664,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: Sized { - /// 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; - - /// 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; - +pub trait MAC: AlgorithmInfo + Sized { /// The size of the output in bytes. fn output_len(&self) -> usize; @@ -1194,11 +730,15 @@ pub trait MAC: Sized { /// which has been truncated, or which contains extra bytes at the end is considered to not be a match /// and will return false. fn do_verify_final(self, mac: &[u8]) -> bool; - - /// Returns the maximum security strength that this KDF is capable of supporting, based on the underlying primitives. - fn max_security_strength(&self) -> SecurityStrength; } +/// The shortest output a [`MAC`] worker's `do_final_out` / `mac_out` will truncate a tag to, for +/// MACs whose specification sets no floor of its own (keyed BLAKE2 and BLAKE3); a shorter buffer is +/// rejected with [`MACError::InvalidLength`]. Four bytes matches HMAC's FIPS minimum +/// (`bouncycastle_hmac::MIN_FIPS_DIGEST_LEN`), so that a tag cannot be cut to a trivially +/// forgeable length by accident. +pub const MIN_MAC_OUTPUT_LEN: usize = 4; + /// A block padding scheme, used to extend arbitrary-length data to a whole number of blocks so that it /// can be processed by a [`BlockCipherEncryptor`]. Implementations are pure functions of the block /// contents: no key, no state. @@ -1479,8 +1019,8 @@ pub trait Signer, const SK_LEN: usize, const SIG /// The decryption half of a stream cipher's streaming API; see [`StreamCipherEncryptor`], whose /// notes on in-place operation, arbitrary lengths and the `Result` all apply here too. -pub trait StreamCipherDecryptor: - SymmetricCipherDecryptor +pub trait StreamCipherDecryptor: + 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 @@ -1488,16 +1028,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 { - 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 @@ -1525,8 +1055,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 @@ -1545,39 +1075,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> { - 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> { - 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 @@ -1619,8 +1116,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. @@ -1639,7 +1138,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; } @@ -1654,29 +1153,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, ->: Algorithm + Sized +/// 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: + Algorithm + Sized { - /// 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; - /// 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. @@ -1769,70 +1252,13 @@ 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. fn decrypt_out_len(ciphertext_len: usize) -> usize; - - /// 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 { - 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> { - 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 @@ -1845,47 +1271,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, ->: Algorithm + Sized +/// 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: + Algorithm + Sized { - /// 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>; - - /// 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>; - /// 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. @@ -1971,78 +1366,9 @@ 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.) fn encrypt_out_len(plaintext_len: usize) -> usize; - - /// 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> { - 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> { - 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> { - 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/algorithm_info_tests.rs b/crypto/core/tests/algorithm_info_tests.rs new file mode 100644 index 000000000..3c5d1b060 --- /dev/null +++ b/crypto/core/tests/algorithm_info_tests.rs @@ -0,0 +1,19 @@ +//! `AlgorithmInfo` is the run-time form of `Algorithm`: the blanket impl must report the +//! implementor's constants, and it must be reachable through a trait object (issue #173). + +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmInfo}; + +struct Toy; + +impl Algorithm for Toy { + const ALG_NAME: &'static str = "Toy"; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; +} + +#[test] +fn blanket_impl_reports_the_constants_through_a_trait_object() { + let info: &dyn AlgorithmInfo = &Toy; + assert_eq!(info.alg_name(), Toy::ALG_NAME); + assert_eq!(info.max_security_strength(), Toy::MAX_SECURITY_STRENGTH); +} 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..550b1adaa 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -5,7 +5,10 @@ edition.workspace = true [dependencies] bouncycastle-ascon.workspace = true +bouncycastle-blake2.workspace = true +bouncycastle-blake3.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/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 1938bea29..e4cc72891 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -31,9 +31,13 @@ use crate::{AlgorithmFactory, FactoryError}; use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT}; use bouncycastle_ascon as ascon; use bouncycastle_ascon::ASCON_HASH256_NAME; +use bouncycastle_blake2 as blake2; +use bouncycastle_blake2::{BLAKE2B_512_NAME, BLAKE2S_256_NAME}; +use bouncycastle_blake3 as blake3; +use bouncycastle_blake3::BLAKE3_NAME; use bouncycastle_core::errors::HashError; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash}; +use bouncycastle_core::traits::{AlgorithmInfo, Hash}; use bouncycastle_sha2 as sha2; use bouncycastle_sha2::{ SHA224_NAME, SHA256_NAME, SHA384_NAME, SHA512_224_NAME, SHA512_256_NAME, SHA512_NAME, @@ -72,6 +76,19 @@ pub enum HashFactory { SM3(sm3::SM3), /// AsconHash256(ascon::AsconHash256), + /// BLAKE2b with a 64-byte digest (RFC 7693), unkeyed. The keyed mode is in + /// [`MACFactory`](crate::mac_factory::MACFactory). + #[allow(non_camel_case_types)] + BLAKE2b_512(blake2::BLAKE2b_512), + /// BLAKE2s with a 32-byte digest (RFC 7693), unkeyed. The keyed mode is in + /// [`MACFactory`](crate::mac_factory::MACFactory). + #[allow(non_camel_case_types)] + BLAKE2s_256(blake2::BLAKE2s_256), + /// BLAKE3 in `hash` mode, with the default 32-byte output. For longer output use + /// [`XOFFactory`](crate::xof_factory::XOFFactory); for the `derive_key` mode, which needs a + /// context string the factory has no channel for, construct + /// `bouncycastle_blake3::BLAKE3DeriveKey` directly. + BLAKE3(blake3::BLAKE3), } impl Default for HashFactory { @@ -105,6 +122,9 @@ impl AlgorithmFactory for HashFactory { SHA3_512_NAME => Ok(Self::SHA3_512(sha3::SHA3_512::new())), SM3_NAME => Ok(Self::SM3(sm3::SM3::new())), ASCON_HASH256_NAME => Ok(Self::AsconHash256(ascon::AsconHash256::new())), + BLAKE2B_512_NAME => Ok(Self::BLAKE2b_512(blake2::BLAKE2b_512::new())), + BLAKE2S_256_NAME => Ok(Self::BLAKE2s_256(blake2::BLAKE2s_256::new())), + BLAKE3_NAME => Ok(Self::BLAKE3(blake3::BLAKE3::new())), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known Hash", alg_name @@ -113,13 +133,46 @@ impl AlgorithmFactory for HashFactory { } } -// TODO -- this is broken. -// The designed behaviour here is that the Factory object pass these through to the underlying algorithm -// that it's wrapping, but that can't be done with consts, so I think the Algorithm trait needs -// a rework to be functions instead of consts. -impl Algorithm for HashFactory { - const ALG_NAME: &'static str = "TODO"; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +impl AlgorithmInfo for HashFactory { + fn alg_name(&self) -> &'static str { + match self { + Self::SHA224(h) => h.alg_name(), + Self::SHA256(h) => h.alg_name(), + Self::SHA384(h) => h.alg_name(), + Self::SHA512(h) => h.alg_name(), + Self::SHA512_224(h) => h.alg_name(), + Self::SHA512_256(h) => h.alg_name(), + Self::SHA3_224(h) => h.alg_name(), + Self::SHA3_256(h) => h.alg_name(), + Self::SHA3_384(h) => h.alg_name(), + Self::SHA3_512(h) => h.alg_name(), + Self::SM3(h) => h.alg_name(), + Self::AsconHash256(h) => h.alg_name(), + Self::BLAKE2b_512(h) => h.alg_name(), + Self::BLAKE2s_256(h) => h.alg_name(), + Self::BLAKE3(h) => h.alg_name(), + } + } + + fn max_security_strength(&self) -> SecurityStrength { + match self { + Self::SHA224(h) => h.max_security_strength(), + Self::SHA256(h) => h.max_security_strength(), + Self::SHA384(h) => h.max_security_strength(), + Self::SHA512(h) => h.max_security_strength(), + Self::SHA512_224(h) => h.max_security_strength(), + Self::SHA512_256(h) => h.max_security_strength(), + Self::SHA3_224(h) => h.max_security_strength(), + Self::SHA3_256(h) => h.max_security_strength(), + Self::SHA3_384(h) => h.max_security_strength(), + Self::SHA3_512(h) => h.max_security_strength(), + Self::SM3(h) => h.max_security_strength(), + Self::AsconHash256(h) => h.max_security_strength(), + Self::BLAKE2b_512(h) => h.max_security_strength(), + Self::BLAKE2s_256(h) => h.max_security_strength(), + Self::BLAKE3(h) => h.max_security_strength(), + } + } } impl Hash for HashFactory { @@ -137,6 +190,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.block_bitlen(), Self::SM3(h) => h.block_bitlen(), Self::AsconHash256(h) => h.block_bitlen(), + Self::BLAKE2b_512(h) => h.block_bitlen(), + Self::BLAKE2s_256(h) => h.block_bitlen(), + Self::BLAKE3(h) => h.block_bitlen(), } } @@ -154,42 +210,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.output_len(), Self::SM3(h) => h.output_len(), Self::AsconHash256(h) => h.output_len(), - } - } - - fn hash(self, data: &[u8]) -> Vec { - match self { - Self::SHA224(h) => h.hash(data), - Self::SHA256(h) => h.hash(data), - Self::SHA384(h) => h.hash(data), - Self::SHA512(h) => h.hash(data), - Self::SHA512_224(h) => h.hash(data), - Self::SHA512_256(h) => h.hash(data), - Self::SHA3_224(h) => h.hash(data), - Self::SHA3_256(h) => h.hash(data), - Self::SHA3_384(h) => h.hash(data), - Self::SHA3_512(h) => h.hash(data), - Self::SM3(h) => h.hash(data), - Self::AsconHash256(h) => h.hash(data), - } - } - - fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize { - output.fill(0); - - match self { - Self::SHA224(h) => h.hash_out(data, output), - Self::SHA256(h) => h.hash_out(data, output), - Self::SHA384(h) => h.hash_out(data, output), - Self::SHA512(h) => h.hash_out(data, output), - Self::SHA512_224(h) => h.hash_out(data, output), - Self::SHA512_256(h) => h.hash_out(data, output), - Self::SHA3_224(h) => h.hash_out(data, output), - Self::SHA3_256(h) => h.hash_out(data, output), - Self::SHA3_384(h) => h.hash_out(data, output), - Self::SHA3_512(h) => h.hash_out(data, output), - Self::SM3(h) => h.hash_out(data, output), - Self::AsconHash256(h) => h.hash_out(data, output), + Self::BLAKE2b_512(h) => h.output_len(), + Self::BLAKE2s_256(h) => h.output_len(), + Self::BLAKE3(h) => h.output_len(), } } @@ -207,6 +230,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_update(data), Self::SM3(h) => h.do_update(data), Self::AsconHash256(h) => h.do_update(data), + Self::BLAKE2b_512(h) => h.do_update(data), + Self::BLAKE2s_256(h) => h.do_update(data), + Self::BLAKE3(h) => h.do_update(data), } } @@ -224,6 +250,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final(), Self::SM3(h) => h.do_final(), Self::AsconHash256(h) => h.do_final(), + Self::BLAKE2b_512(h) => h.do_final(), + Self::BLAKE2s_256(h) => h.do_final(), + Self::BLAKE3(h) => h.do_final(), } } @@ -243,6 +272,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final_out(output), Self::SM3(h) => h.do_final_out(output), Self::AsconHash256(h) => h.do_final_out(output), + Self::BLAKE2b_512(h) => h.do_final_out(output), + Self::BLAKE2s_256(h) => h.do_final_out(output), + Self::BLAKE3(h) => h.do_final_out(output), } } @@ -264,6 +296,9 @@ impl Hash for HashFactory { Self::SHA3_512(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), Self::SM3(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), Self::AsconHash256(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE2b_512(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE2s_256(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), + Self::BLAKE3(h) => h.do_final_partial_bits(partial_byte, num_partial_bits), } } @@ -300,23 +335,29 @@ impl Hash for HashFactory { Self::AsconHash256(h) => { h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) } + Self::BLAKE2b_512(h) => { + h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) + } + Self::BLAKE2s_256(h) => { + h.do_final_partial_bits_out(partial_byte, num_partial_bits, output) + } + Self::BLAKE3(h) => h.do_final_partial_bits_out(partial_byte, num_partial_bits, output), } } +} - fn max_security_strength(&self) -> SecurityStrength { - match self { - Self::SHA224(h) => h.max_security_strength(), - Self::SHA256(h) => h.max_security_strength(), - Self::SHA384(h) => h.max_security_strength(), - Self::SHA512(h) => h.max_security_strength(), - Self::SHA512_224(h) => h.max_security_strength(), - Self::SHA512_256(h) => h.max_security_strength(), - Self::SHA3_224(h) => h.max_security_strength(), - Self::SHA3_256(h) => h.max_security_strength(), - Self::SHA3_384(h) => h.max_security_strength(), - Self::SHA3_512(h) => h.max_security_strength(), - Self::SM3(h) => h.max_security_strength(), - Self::AsconHash256(h) => h.max_security_strength(), - } +impl HashFactory { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) } } diff --git a/crypto/factory/src/kdf_factory.rs b/crypto/factory/src/kdf_factory.rs index 8f8a2ec1b..ff0866ee9 100644 --- a/crypto/factory/src/kdf_factory.rs +++ b/crypto/factory/src/kdf_factory.rs @@ -52,7 +52,7 @@ use crate::{AlgorithmFactory, DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, Factory use bouncycastle_core::errors::KDFError; use bouncycastle_core::key_material::KeyMaterialTrait; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::KDF; +use bouncycastle_core::traits::{AlgorithmInfo, KDF}; use bouncycastle_sha2::hkdf::{ HKDF_SHA256, HKDF_SHA256_NAME, HKDF_SHA384, HKDF_SHA384_NAME, HKDF_SHA512, HKDF_SHA512_NAME, }; @@ -204,6 +204,22 @@ impl KDF for KDFFactory { Self::SHAKE256(h) => h.derive_key_from_multiple_out(keys, additional_input, output_key), } } +} + +impl AlgorithmInfo for KDFFactory { + fn alg_name(&self) -> &'static str { + match self { + Self::HKDF_SHA256(h) => h.alg_name(), + Self::HKDF_SHA384(h) => h.alg_name(), + Self::HKDF_SHA512(h) => h.alg_name(), + Self::SHA3_224(h) => h.alg_name(), + Self::SHA3_256(h) => h.alg_name(), + Self::SHA3_384(h) => h.alg_name(), + Self::SHA3_512(h) => h.alg_name(), + Self::SHAKE128(h) => h.alg_name(), + Self::SHAKE256(h) => h.alg_name(), + } + } fn max_security_strength(&self) -> SecurityStrength { match self { diff --git a/crypto/factory/src/lib.rs b/crypto/factory/src/lib.rs index 3325797e5..c858d6c9a 100644 --- a/crypto/factory/src/lib.rs +++ b/crypto/factory/src/lib.rs @@ -37,6 +37,23 @@ //! All enums exported from this crate are tagged `#[non_exhaustive]` to indicate that they //! are highly likely to gain more branches in the future; therefore, the compiler is to treat it as //! an error if a caller matches exhaustively against the current set of variants. +//! +//! # Memory Usage +//! +//! An enum is as large as its largest variant, whichever algorithm it holds, and every move or +//! clone copies all of it. Sizes from `core::mem::size_of` on x86_64: +//! +//! | Object | Size (bytes) | +//! |--------------------------------------------|--------------| +//! | [`hash_factory::HashFactory`] | 1888 | +//! | [`xof_factory::XOFFactory`] | 1888 | +//! | [`xof_factory::XOFFactorySqueezer`] | 448 | +//! | [`mac_factory::MACFactory`] | 896 | +//! | [`mac_factory::MACFactoryWorker`] | 1888 | +//! +//! The 1888-byte ones are set by BLAKE3, whose hasher carries a 1728-byte chaining-value stack +//! sized for the full 2^64-byte input range (BLAKE3 spec s. 5.4); without it the largest variant +//! is SHA-3 at 440 bytes. Where that matters, use the algorithm's own type rather than the factory. #![forbid(unsafe_code)] #![forbid(missing_docs)] diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index efc6a89b7..f95474ec5 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 { @@ -72,10 +73,16 @@ //! ``` use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; +use bouncycastle_blake2 as blake2; +use bouncycastle_blake2::{BLAKE2B_512_MAC_NAME, BLAKE2S_256_MAC_NAME}; +use bouncycastle_blake3 as blake3; +use bouncycastle_blake3::BLAKE3_KEYED_HASH_NAME; use bouncycastle_core::errors::MACError; use bouncycastle_core::key_material::KeyMaterialTrait; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::MAC; +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 +92,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 +106,93 @@ 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), + /// BLAKE2b-512's built-in keyed mode (RFC 7693 s. 3.3), not HMAC-BLAKE2b. The key must be + /// 1 to 64 bytes. + BLAKE2b_512_MAC(blake2::BLAKE2b_512_MACKey), + /// BLAKE2s-256's built-in keyed mode (RFC 7693 s. 3.3), not HMAC-BLAKE2s. The key must be + /// 1 to 32 bytes. + BLAKE2s_256_MAC(blake2::BLAKE2s_256_MACKey), + /// BLAKE3 in `keyed_hash` mode, with the default 32-byte tag. The key must be exactly + /// 32 bytes (BLAKE3 spec s. 2.3, "a 256-bit key"); any other length is refused with + /// [`MACError::InvalidLength`]. For keyed output longer than 32 bytes, construct + /// `bouncycastle_blake3::BLAKE3KeyedHashKey` directly and use its worker's `into_squeezer`. + BLAKE3KeyedHash(blake3::BLAKE3KeyedHashKey), +} + +/// 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>), + /// BLAKE2b-512 keyed-mode worker. + BLAKE2b_512_MAC(blake2::BLAKE2b_512_MAC), + /// BLAKE2s-256 keyed-mode worker. + BLAKE2s_256_MAC(blake2::BLAKE2s_256_MAC), + /// BLAKE3 `keyed_hash` worker. + BLAKE3KeyedHash(blake3::BLAKE3KeyedHash), } impl MACFactory { @@ -154,42 +214,81 @@ 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)?)) + 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)?)), + BLAKE2B_512_MAC_NAME => { + Ok(Self::BLAKE2b_512_MAC(blake2::BLAKE2b_512_MACKey::from_key_material(key)?)) } - HMAC_SHA512_256_NAME => { - Ok(Self::HMAC_SHA512_256(sha2::hmac::HMAC_SHA512_256::new(key)?)) + BLAKE2S_256_MAC_NAME => { + Ok(Self::BLAKE2s_256_MAC(blake2::BLAKE2s_256_MACKey::from_key_material(key)?)) + } + BLAKE3_KEYED_HASH_NAME => { + Ok(Self::BLAKE3KeyedHash(blake3::BLAKE3KeyedHashKey::from_key_material(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)?)), _ => 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()), + Self::BLAKE2b_512_MAC(k) => MACFactoryWorker::BLAKE2b_512_MAC(k.create_generator()), + Self::BLAKE2s_256_MAC(k) => MACFactoryWorker::BLAKE2s_256_MAC(k.create_generator()), + Self::BLAKE3KeyedHash(k) => MACFactoryWorker::BLAKE3KeyedHash(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(), @@ -205,6 +304,9 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.output_len(), Self::HMAC_SHA3_512(h) => h.output_len(), Self::HMAC_SM3(h) => h.output_len(), + Self::BLAKE2b_512_MAC(h) => h.output_len(), + Self::BLAKE2s_256_MAC(h) => h.output_len(), + Self::BLAKE3KeyedHash(h) => h.output_len(), } } @@ -223,12 +325,14 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.mac(data), Self::HMAC_SHA3_512(h) => h.mac(data), Self::HMAC_SM3(h) => h.mac(data), + Self::BLAKE2b_512_MAC(h) => h.mac(data), + Self::BLAKE2s_256_MAC(h) => h.mac(data), + Self::BLAKE3KeyedHash(h) => h.mac(data), } } 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), @@ -243,6 +347,9 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.mac_out(data, out), Self::HMAC_SHA3_512(h) => h.mac_out(data, out), Self::HMAC_SM3(h) => h.mac_out(data, out), + Self::BLAKE2b_512_MAC(h) => h.mac_out(data, out), + Self::BLAKE2s_256_MAC(h) => h.mac_out(data, out), + Self::BLAKE3KeyedHash(h) => h.mac_out(data, out), } } @@ -261,6 +368,9 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.verify(data, mac), Self::HMAC_SHA3_512(h) => h.verify(data, mac), Self::HMAC_SM3(h) => h.verify(data, mac), + Self::BLAKE2b_512_MAC(h) => h.verify(data, mac), + Self::BLAKE2s_256_MAC(h) => h.verify(data, mac), + Self::BLAKE3KeyedHash(h) => h.verify(data, mac), } } @@ -279,6 +389,9 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.do_update(data), Self::HMAC_SHA3_512(h) => h.do_update(data), Self::HMAC_SM3(h) => h.do_update(data), + Self::BLAKE2b_512_MAC(h) => h.do_update(data), + Self::BLAKE2s_256_MAC(h) => h.do_update(data), + Self::BLAKE3KeyedHash(h) => h.do_update(data), } } @@ -297,26 +410,31 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.do_final(), Self::HMAC_SHA3_512(h) => h.do_final(), Self::HMAC_SM3(h) => h.do_final(), + Self::BLAKE2b_512_MAC(h) => h.do_final(), + Self::BLAKE2s_256_MAC(h) => h.do_final(), + Self::BLAKE3KeyedHash(h) => h.do_final(), } } - 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), + Self::BLAKE2b_512_MAC(h) => h.do_final_out(out), + Self::BLAKE2s_256_MAC(h) => h.do_final_out(out), + Self::BLAKE3KeyedHash(h) => h.do_final_out(out), } } @@ -335,6 +453,32 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.do_verify_final(mac), Self::HMAC_SHA3_512(h) => h.do_verify_final(mac), Self::HMAC_SM3(h) => h.do_verify_final(mac), + Self::BLAKE2b_512_MAC(h) => h.do_verify_final(mac), + Self::BLAKE2s_256_MAC(h) => h.do_verify_final(mac), + Self::BLAKE3KeyedHash(h) => h.do_verify_final(mac), + } + } +} + +impl AlgorithmInfo for MACFactoryWorker<'_> { + fn alg_name(&self) -> &'static str { + match self { + Self::KMAC128(h) => h.alg_name(), + Self::KMAC256(h) => h.alg_name(), + Self::HMAC_SHA224(h) => h.alg_name(), + Self::HMAC_SHA256(h) => h.alg_name(), + Self::HMAC_SHA384(h) => h.alg_name(), + Self::HMAC_SHA512(h) => h.alg_name(), + Self::HMAC_SHA512_224(h) => h.alg_name(), + Self::HMAC_SHA512_256(h) => h.alg_name(), + Self::HMAC_SHA3_224(h) => h.alg_name(), + Self::HMAC_SHA3_256(h) => h.alg_name(), + Self::HMAC_SHA3_384(h) => h.alg_name(), + Self::HMAC_SHA3_512(h) => h.alg_name(), + Self::HMAC_SM3(h) => h.alg_name(), + Self::BLAKE2b_512_MAC(h) => h.alg_name(), + Self::BLAKE2s_256_MAC(h) => h.alg_name(), + Self::BLAKE3KeyedHash(h) => h.alg_name(), } } @@ -353,6 +497,9 @@ impl MAC for MACFactory { Self::HMAC_SHA3_384(h) => h.max_security_strength(), Self::HMAC_SHA3_512(h) => h.max_security_strength(), Self::HMAC_SM3(h) => h.max_security_strength(), + Self::BLAKE2b_512_MAC(h) => h.max_security_strength(), + Self::BLAKE2s_256_MAC(h) => h.max_security_strength(), + Self::BLAKE3KeyedHash(h) => h.max_security_strength(), } } } diff --git a/crypto/factory/src/xof_factory.rs b/crypto/factory/src/xof_factory.rs index f3128efd1..5b9e217c8 100644 --- a/crypto/factory/src/xof_factory.rs +++ b/crypto/factory/src/xof_factory.rs @@ -39,9 +39,10 @@ use crate::{AlgorithmFactory, FactoryError}; use bouncycastle_ascon::ASCON_XOF128_NAME; use bouncycastle_ascon::AsconXof128; +use bouncycastle_blake3::{BLAKE3, BLAKE3_NAME}; use bouncycastle_core::errors::HashError; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{AlgorithmInfo, Hash, XOF, XOFSqueezer}; use bouncycastle_sha3 as sha3; use bouncycastle_sha3::{SHAKE128_NAME, SHAKE256_NAME}; @@ -64,6 +65,11 @@ pub enum XOFFactory { SHAKE256(sha3::SHAKE256), /// AsconXof128(AsconXof128), + /// BLAKE3 in `hash` mode. Its [`Hash`] view is the default 32-byte output, which is the + /// first 32 bytes of the XOF stream (BLAKE3 spec s. 2.6). For the `derive_key` mode, which + /// needs a context string the factory has no channel for, construct + /// `bouncycastle_blake3::BLAKE3DeriveKey` directly. + BLAKE3(BLAKE3), } impl Default for XOFFactory { @@ -86,6 +92,7 @@ impl AlgorithmFactory for XOFFactory { SHAKE128_NAME => Ok(Self::SHAKE128(sha3::SHAKE128::new())), SHAKE256_NAME => Ok(Self::SHAKE256(sha3::SHAKE256::new())), ASCON_XOF128_NAME => Ok(Self::AsconXof128(AsconXof128::new())), + BLAKE3_NAME => Ok(Self::BLAKE3(BLAKE3::new())), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known XOF", alg_name @@ -94,13 +101,24 @@ impl AlgorithmFactory for XOFFactory { } } -/// `Hash` requires it, and the factory does not know which algorithm it holds until it is -/// constructed, so the constants are placeholders -- the same stance `HashFactory` takes. The -/// per-value answers come from [`Hash::output_len`] and [`Hash::max_security_strength`], which -/// dispatch on the variant. -impl Algorithm for XOFFactory { - const ALG_NAME: &'static str = "TODO"; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +impl AlgorithmInfo for XOFFactory { + fn alg_name(&self) -> &'static str { + match self { + Self::SHAKE128(h) => h.alg_name(), + Self::SHAKE256(h) => h.alg_name(), + Self::AsconXof128(h) => h.alg_name(), + Self::BLAKE3(h) => h.alg_name(), + } + } + + fn max_security_strength(&self) -> SecurityStrength { + match self { + Self::SHAKE128(h) => h.max_security_strength(), + Self::SHAKE256(h) => h.max_security_strength(), + Self::AsconXof128(h) => h.max_security_strength(), + Self::BLAKE3(h) => h.max_security_strength(), + } + } } /// The squeezing phase of whichever XOF the factory selected. @@ -116,6 +134,9 @@ pub enum XOFFactorySqueezer { /// Ascon-XOF128 output. AsconXof128(::Squeezer), + + /// BLAKE3 output. + BLAKE3(::Squeezer), } impl XOFSqueezer for XOFFactorySqueezer { @@ -124,6 +145,7 @@ impl XOFSqueezer for XOFFactorySqueezer { Self::SHAKE128(o) => o.do_output(num_bytes), Self::SHAKE256(o) => o.do_output(num_bytes), Self::AsconXof128(o) => o.do_output(num_bytes), + Self::BLAKE3(o) => o.do_output(num_bytes), } } @@ -132,6 +154,7 @@ impl XOFSqueezer for XOFFactorySqueezer { Self::SHAKE128(o) => o.do_output_out(output), Self::SHAKE256(o) => o.do_output_out(output), Self::AsconXof128(o) => o.do_output_out(output), + Self::BLAKE3(o) => o.do_output_out(output), } } } @@ -142,6 +165,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.block_bitlen(), Self::SHAKE256(h) => h.block_bitlen(), Self::AsconXof128(h) => h.block_bitlen(), + Self::BLAKE3(h) => h.block_bitlen(), } } @@ -150,22 +174,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.output_len(), Self::SHAKE256(h) => h.output_len(), Self::AsconXof128(h) => h.output_len(), - } - } - - fn hash(self, data: &[u8]) -> Vec { - match self { - Self::SHAKE128(h) => h.hash(data), - Self::SHAKE256(h) => h.hash(data), - Self::AsconXof128(h) => h.hash(data), - } - } - - fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize { - match self { - Self::SHAKE128(h) => h.hash_out(data, output), - Self::SHAKE256(h) => h.hash_out(data, output), - Self::AsconXof128(h) => h.hash_out(data, output), + Self::BLAKE3(h) => h.output_len(), } } @@ -174,6 +183,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_update(data), Self::SHAKE256(h) => h.do_update(data), Self::AsconXof128(h) => h.do_update(data), + Self::BLAKE3(h) => h.do_update(data), } } @@ -182,6 +192,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final(), Self::SHAKE256(h) => h.do_final(), Self::AsconXof128(h) => h.do_final(), + Self::BLAKE3(h) => h.do_final(), } } @@ -190,6 +201,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_out(output), Self::SHAKE256(h) => h.do_final_out(output), Self::AsconXof128(h) => h.do_final_out(output), + Self::BLAKE3(h) => h.do_final_out(output), } } @@ -202,6 +214,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_partial_bits(partial_byte, num_bits), Self::SHAKE256(h) => h.do_final_partial_bits(partial_byte, num_bits), Self::AsconXof128(h) => h.do_final_partial_bits(partial_byte, num_bits), + Self::BLAKE3(h) => h.do_final_partial_bits(partial_byte, num_bits), } } @@ -215,14 +228,7 @@ impl Hash for XOFFactory { Self::SHAKE128(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), Self::SHAKE256(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), Self::AsconXof128(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), - } - } - - fn max_security_strength(&self) -> SecurityStrength { - match self { - Self::SHAKE128(h) => Hash::max_security_strength(h), - Self::SHAKE256(h) => Hash::max_security_strength(h), - Self::AsconXof128(h) => Hash::max_security_strength(h), + Self::BLAKE3(h) => h.do_final_partial_bits_out(partial_byte, num_bits, output), } } } @@ -235,6 +241,7 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => XOFFactorySqueezer::SHAKE128(h.into_squeezer()), Self::SHAKE256(h) => XOFFactorySqueezer::SHAKE256(h.into_squeezer()), Self::AsconXof128(h) => XOFFactorySqueezer::AsconXof128(h.into_squeezer()), + Self::BLAKE3(h) => XOFFactorySqueezer::BLAKE3(h.into_squeezer()), } } @@ -253,6 +260,9 @@ impl XOF for XOFFactory { Self::AsconXof128(h) => XOFFactorySqueezer::AsconXof128( h.into_squeezer_partial_bits(partial_byte, num_bits)?, ), + Self::BLAKE3(h) => { + XOFFactorySqueezer::BLAKE3(h.into_squeezer_partial_bits(partial_byte, num_bits)?) + } }) } @@ -261,6 +271,7 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => h.xof(data, result_len), Self::SHAKE256(h) => h.xof(data, result_len), Self::AsconXof128(h) => h.xof(data, result_len), + Self::BLAKE3(h) => h.xof(data, result_len), } } @@ -271,6 +282,23 @@ impl XOF for XOFFactory { Self::SHAKE128(h) => h.xof_out(data, output), Self::SHAKE256(h) => h.xof_out(data, output), Self::AsconXof128(h) => h.xof_out(data, output), + Self::BLAKE3(h) => h.xof_out(data, output), } } } + +impl XOFFactory { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/factory/tests/hash_factory_tests.rs b/crypto/factory/tests/hash_factory_tests.rs index 14911812d..1b8fc1558 100644 --- a/crypto/factory/tests/hash_factory_tests.rs +++ b/crypto/factory/tests/hash_factory_tests.rs @@ -1,10 +1,37 @@ #[cfg(test)] mod hash_factory_tests { - use bouncycastle_core::traits::{Hash, XOF}; + use bouncycastle_core::errors::HashError; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{AlgorithmInfo, Hash, HashOneShot, XOF}; use bouncycastle_core_test_framework::DUMMY_SEED; - use bouncycastle_factory::AlgorithmFactory; use bouncycastle_factory::hash_factory::HashFactory; use bouncycastle_factory::xof_factory::XOFFactory; + use bouncycastle_factory::{AlgorithmFactory, FactoryError}; + + /// `AlgorithmInfo` answers for the algorithm the factory selected, not for the factory. + #[test] + fn alg_name_and_strength_follow_the_variant() { + use SecurityStrength::*; + let table = [ + (bouncycastle_sha2::SHA224_NAME, _112bit), + (bouncycastle_sha2::SHA256_NAME, _128bit), + (bouncycastle_sha2::SHA384_NAME, _192bit), + (bouncycastle_sha2::SHA512_NAME, _256bit), + (bouncycastle_sha2::SHA512_224_NAME, _112bit), + (bouncycastle_sha2::SHA512_256_NAME, _128bit), + (bouncycastle_sha3::SHA3_224_NAME, _112bit), + (bouncycastle_sha3::SHA3_256_NAME, _128bit), + (bouncycastle_sha3::SHA3_384_NAME, _192bit), + (bouncycastle_sha3::SHA3_512_NAME, _256bit), + (bouncycastle_sm3::SM3_NAME, _128bit), + (bouncycastle_ascon::ASCON_HASH256_NAME, _128bit), + ]; + for (name, strength) in table { + let h = HashFactory::new(name).unwrap(); + assert_eq!(h.alg_name(), name); + assert_eq!(h.max_security_strength(), strength, "{name}"); + } + } mod sha3_tests { use super::*; @@ -17,8 +44,7 @@ mod hash_factory_tests { fn sha2_hash_tests() { // SHA224 - let h = SHA224::new(); - h.hash(&DUMMY_SEED[..24]); + SHA224::hash(&DUMMY_SEED[..24]); let sha2 = HashFactory::new("SHA224").unwrap(); assert_eq!(sha2.output_len(), 28); @@ -168,9 +194,8 @@ mod hash_factory_tests { fn ascon_hash_tests() { use bouncycastle_ascon::ASCON_HASH256_NAME; use bouncycastle_ascon::AsconHash256; - use bouncycastle_factory::FactoryError; - let direct = AsconHash256::new().hash(&DUMMY_SEED[..512]); + let direct = AsconHash256::hash(&DUMMY_SEED[..512]); // Construct by literal name and by the crate's name constant; both must match the // direct implementation. @@ -188,6 +213,136 @@ mod hash_factory_tests { )); } + /// Checks every `Hash` pass-through of the `name` variant against `expected`, the digest of + /// `msg`; and the byte-oriented partial-bit policy of the BLAKE crates (0 bits is the + /// whole-byte digest, 1..=7 is `InvalidInput`, more than 7 is `InvalidLength`). + fn check_blake_hash( + name: &str, + msg: &[u8], + expected: &[u8], + block_bitlen: usize, + strength: SecurityStrength, + ) { + let make = || HashFactory::new(name).unwrap(); + + assert_eq!(make().output_len(), expected.len(), "{name}: output_len"); + assert_eq!(make().block_bitlen(), block_bitlen, "{name}: block_bitlen"); + assert_eq!(make().max_security_strength(), strength, "{name}: max_security_strength"); + + assert_eq!(make().hash(msg), expected, "{name}: hash"); + + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(make().hash_out(msg, &mut out), expected.len(), "{name}: hash_out length"); + assert_eq!(out, expected, "{name}: hash_out"); + + let mut h = make(); + msg.chunks(7).for_each(|c| h.do_update(c)); + assert_eq!(h.do_final(), expected, "{name}: do_update then do_final"); + + let mut h = make(); + h.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(h.do_final_out(&mut out), expected.len(), "{name}: do_final_out length"); + assert_eq!(out, expected, "{name}: do_final_out"); + + let mut h = make(); + h.do_update(msg); + assert_eq!(h.do_final_partial_bits(0x00, 0).unwrap(), expected, "{name}: 0 bits"); + + let mut h = make(); + h.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!( + h.do_final_partial_bits_out(0x00, 0, &mut out).unwrap(), + expected.len(), + "{name}: do_final_partial_bits_out length" + ); + assert_eq!(out, expected, "{name}: do_final_partial_bits_out"); + + let mut h = make(); + h.do_update(msg); + assert!( + matches!(h.do_final_partial_bits(0x05, 3), Err(HashError::InvalidInput(_))), + "{name}: BLAKE is byte-oriented" + ); + let mut out = vec![0u8; expected.len()]; + assert!( + matches!( + make().do_final_partial_bits_out(0x05, 3, &mut out), + Err(HashError::InvalidInput(_)) + ), + "{name}: BLAKE is byte-oriented (_out)" + ); + assert!( + matches!(make().do_final_partial_bits(0xff, 8), Err(HashError::InvalidLength(_))), + "{name}: eight partial bits is not a partial byte" + ); + } + + /// BLAKE2b-512, BLAKE2s-256 and BLAKE3 through the factory, by name constant and by string. + /// + /// Expected values are copied from the vectors embedded in the algorithm crates' tests: + /// - BLAKE2b-512 / BLAKE2s-256 of "abc": RFC 7693 Appendix A / B (`RFC_ABC` in + /// crypto/blake2/tests/blake2{b,s}_tests.rs); + /// - BLAKE2b-512 / BLAKE2s-256 of the 255 bytes 0, 1, ..., 254: the official BLAKE2 KAT, + /// blake2-kat.json (the `KAT` entry for length 255 in the same files); + /// - BLAKE3 of the 1025-byte input `i % 251`: the official BLAKE3 test_vectors.json (the + /// first 32 bytes of the `hash` field of the 1025 case in + /// crypto/blake3/tests/vectors/mod.rs). + #[test] + fn blake_hash_tests() { + use bouncycastle_blake2::{BLAKE2B_512_NAME, BLAKE2S_256_NAME}; + use bouncycastle_blake3::BLAKE3_NAME; + use bouncycastle_hex as hex; + + let blake2b_abc = hex::decode("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923").unwrap(); + let blake2b_255 = hex::decode("5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928").unwrap(); + let blake2s_abc = + hex::decode("508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982") + .unwrap(); + let blake2s_255 = + hex::decode("f03f5789d3336b80d002d59fdf918bdb775b00956ed5528e86aa994acb38fe2d") + .unwrap(); + let blake3_1025 = + hex::decode("d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444") + .unwrap(); + let blake3_input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + + for name in ["BLAKE2b-512", BLAKE2B_512_NAME] { + check_blake_hash(name, b"abc", &blake2b_abc, 1024, SecurityStrength::_256bit); + check_blake_hash( + name, + &DUMMY_SEED[..255], + &blake2b_255, + 1024, + SecurityStrength::_256bit, + ); + } + for name in ["BLAKE2s-256", BLAKE2S_256_NAME] { + check_blake_hash(name, b"abc", &blake2s_abc, 512, SecurityStrength::_128bit); + check_blake_hash( + name, + &DUMMY_SEED[..255], + &blake2s_255, + 512, + SecurityStrength::_128bit, + ); + } + for name in ["BLAKE3", BLAKE3_NAME] { + check_blake_hash(name, &blake3_input, &blake3_1025, 512, SecurityStrength::_128bit); + } + + // Names are case-sensitive, and the MAC names are not hashes. + for name in + ["blake2b-512", "BLAKE2b-256", "BLAKE2b-512-MAC", "blake3", "BLAKE3-KeyedHash"] + { + assert!( + matches!(HashFactory::new(name), Err(FactoryError::UnsupportedAlgorithm(_))), + "{name:?} must not construct a Hash" + ); + } + } + #[test] fn test_defaults() { // All the ways to get "default" diff --git a/crypto/factory/tests/kdf_factory_tests.rs b/crypto/factory/tests/kdf_factory_tests.rs index a8a703177..6ab26ee85 100644 --- a/crypto/factory/tests/kdf_factory_tests.rs +++ b/crypto/factory/tests/kdf_factory_tests.rs @@ -3,7 +3,8 @@ mod kdf_factory_tests { use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; - use bouncycastle_core::traits::KDF; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{AlgorithmInfo, KDF}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_factory as factory; use bouncycastle_factory::AlgorithmFactory; @@ -11,6 +12,33 @@ mod kdf_factory_tests { use bouncycastle_sha2::hkdf::HKDF_SHA384; use bouncycastle_utils::ct; + /// `AlgorithmInfo` answers for the algorithm the factory selected, not for the factory. + #[test] + fn alg_name_and_strength_follow_the_variant() { + use SecurityStrength::*; + use bouncycastle_sha2::hkdf::{HKDF_SHA256_NAME, HKDF_SHA384_NAME, HKDF_SHA512_NAME}; + use bouncycastle_sha3::{ + SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME, SHAKE128_NAME, + SHAKE256_NAME, + }; + let table = [ + (HKDF_SHA256_NAME, _128bit), + (HKDF_SHA384_NAME, _192bit), + (HKDF_SHA512_NAME, _256bit), + (SHA3_224_NAME, _112bit), + (SHA3_256_NAME, _128bit), + (SHA3_384_NAME, _192bit), + (SHA3_512_NAME, _256bit), + (SHAKE128_NAME, _128bit), + (SHAKE256_NAME, _256bit), + ]; + for (name, strength) in table { + let k = KDFFactory::new(name).unwrap(); + assert_eq!(k.alg_name(), name); + assert_eq!(k.max_security_strength(), strength, "{name}"); + } + } + #[test] fn sha3_kdf_tests() { let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); diff --git a/crypto/factory/tests/mac_factory_tests.rs b/crypto/factory/tests/mac_factory_tests.rs index a71214656..95ef24110 100644 --- a/crypto/factory/tests/mac_factory_tests.rs +++ b/crypto/factory/tests/mac_factory_tests.rs @@ -1,10 +1,46 @@ #[cfg(test)] mod hash_factory_tests { - use bouncycastle_core::key_material::{KeyMaterial, KeyType}; - use bouncycastle_core::traits::MAC; + use bouncycastle_core::errors::MACError; + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::symmetric_key::{MacGenerationKey, MacVerificationKey}; + use bouncycastle_core::traits::{AlgorithmInfo, MAC}; + use bouncycastle_factory::FactoryError; use bouncycastle_factory::mac_factory::MACFactory; use bouncycastle_hex as hex; + /// `AlgorithmInfo` answers for the algorithm the factory selected, not for the factory. + #[test] + fn alg_name_and_strength_follow_the_variant() { + use SecurityStrength::*; + use bouncycastle_sha2::hmac::*; + use bouncycastle_sha3::hmac::*; + use bouncycastle_sha3::kmac::{KMAC128_NAME, KMAC256_NAME}; + use bouncycastle_sm3::hmac::HMAC_SM3_NAME; + let key = KeyMaterial::<64>::from_bytes_as_type(&[0x42; 64], KeyType::MACKey).unwrap(); + let table = [ + (KMAC128_NAME, _128bit), + (KMAC256_NAME, _256bit), + (HMAC_SHA224_NAME, _112bit), + (HMAC_SHA256_NAME, _128bit), + (HMAC_SHA384_NAME, _192bit), + (HMAC_SHA512_NAME, _256bit), + (HMAC_SHA512_224_NAME, _112bit), + (HMAC_SHA512_256_NAME, _128bit), + (HMAC_SHA3_224_NAME, _112bit), + (HMAC_SHA3_256_NAME, _128bit), + (HMAC_SHA3_384_NAME, _192bit), + (HMAC_SHA3_512_NAME, _256bit), + (HMAC_SM3_NAME, _128bit), + ]; + 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}"); + } + } + mod sha3_tests { use super::*; @@ -31,7 +67,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(); @@ -46,7 +82,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(); @@ -66,7 +102,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); } @@ -80,7 +117,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); @@ -89,10 +127,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)); @@ -110,7 +150,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); } @@ -124,7 +165,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); @@ -133,16 +175,171 @@ 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)); // TODO: at least one test for each type } + /// Checks every pass-through of the `name` key and its workers against `expected`, the tag + /// of `msg` under `key`: the key's one-shots, and each `MAC` method on a fresh worker. + fn check_blake_mac( + name: &str, + key: &impl KeyMaterialTrait, + msg: &[u8], + expected: &[u8], + strength: SecurityStrength, + ) { + let mac_key = MACFactory::new(name, key).unwrap(); + let make = || mac_key.create_generator(); + + assert_eq!(make().output_len(), expected.len(), "{name}: output_len"); + assert_eq!(make().max_security_strength(), strength, "{name}: max_security_strength"); + + assert_eq!(make().mac(msg), expected, "{name}: mac"); + assert_eq!(mac_key.mac(msg), expected, "{name}: the key's mac"); + + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(make().mac_out(msg, &mut out).unwrap(), expected.len(), "{name}: mac_out"); + assert_eq!(out, expected, "{name}: mac_out"); + + let mut mac = make(); + msg.chunks(7).for_each(|c| mac.do_update(c)); + assert_eq!(mac.do_final(), expected, "{name}: do_update then do_final"); + + let mut mac = make(); + mac.do_update(msg); + let mut out = vec![0xffu8; expected.len()]; + assert_eq!(mac.do_final_out(&mut out).unwrap(), expected.len(), "{name}: do_final_out"); + assert_eq!(out, expected, "{name}: do_final_out"); + + let mut wrong = expected.to_vec(); + wrong[0] ^= 1; + assert!(make().verify(msg, expected), "{name}: verify"); + assert!(!make().verify(msg, &wrong), "{name}: verify a wrong tag"); + assert!(mac_key.verify(msg, expected), "{name}: the key's verify"); + assert!(!mac_key.verify(msg, &wrong), "{name}: the key's verify, wrong tag"); + let mut mac = mac_key.create_verifier(); + mac.do_update(msg); + assert!(mac.do_verify_final(expected), "{name}: do_verify_final"); + let mut mac = mac_key.create_verifier(); + mac.do_update(msg); + assert!(!mac.do_verify_final(&wrong), "{name}: do_verify_final a wrong tag"); + } + + /// The keyed modes of BLAKE2b-512, BLAKE2s-256 and BLAKE3 through the factory, by name + /// constant and by string. + /// + /// Expected values are copied from the vectors embedded in the algorithm crates' tests: + /// - BLAKE2b-512-MAC / BLAKE2s-256-MAC: the official BLAKE2 KAT, blake2-kat.json, keyed + /// with the bytes 0, 1, ..., kk - 1 (kk = 64 / 32) over the message 0, 1, ..., len - 1 + /// (the `KAT` entries for lengths 3 and 255 in crypto/blake2/tests/blake2{b,s}_tests.rs); + /// - BLAKE3-KeyedHash: the official BLAKE3 test_vectors.json, key + /// "whats the Elvish word for friend", input `i % 251` of 1025 bytes (the first 32 bytes + /// of the `keyed_hash` field of the 1025 case in crypto/blake3/tests/vectors/mod.rs). + #[test] + fn blake_mac_tests() { + use bouncycastle_blake2::{BLAKE2B_512_MAC_NAME, BLAKE2S_256_MAC_NAME}; + use bouncycastle_blake3::BLAKE3_KEYED_HASH_NAME; + use bouncycastle_core_test_framework::DUMMY_SEED; + + let key64 = + KeyMaterial::<64>::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); + let key32 = + KeyMaterial::<32>::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let blake3_key = KeyMaterial::<32>::from_bytes_as_type( + b"whats the Elvish word for friend", + KeyType::MACKey, + ) + .unwrap(); + let blake3_input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + + for name in ["BLAKE2b-512-MAC", BLAKE2B_512_MAC_NAME] { + check_blake_mac( + name, + &key64, + &DUMMY_SEED[..3], + &hex::decode("33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1").unwrap(), + SecurityStrength::_256bit, + ); + check_blake_mac( + name, + &key64, + &DUMMY_SEED[..255], + &hex::decode("142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461").unwrap(), + SecurityStrength::_256bit, + ); + } + for name in ["BLAKE2s-256-MAC", BLAKE2S_256_MAC_NAME] { + check_blake_mac( + name, + &key32, + &DUMMY_SEED[..3], + &hex::decode( + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + check_blake_mac( + name, + &key32, + &DUMMY_SEED[..255], + &hex::decode( + "3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + } + for name in ["BLAKE3-KeyedHash", BLAKE3_KEYED_HASH_NAME] { + check_blake_mac( + name, + &blake3_key, + &blake3_input, + &hex::decode( + "357dc55de0c7e382c900fd6e320acc04146be01db6a8ce7210b7189bd664ea69", + ) + .unwrap(), + SecurityStrength::_128bit, + ); + } + + // The factory builds keys with `from_key_material`, so the key objects' checks surface as + // `FactoryError::MACError`: BLAKE3 keyed_hash needs exactly 32 bytes (BLAKE3 spec + // s. 2.3, "a 256-bit key"); BLAKE2s takes at most 32 and BLAKE2b at most 64 (RFC 7693 + // s. 2.1 table, "Key bytes": "0 <= kk <= 64" / "0 <= kk <= 32"). + let key33 = + KeyMaterial::<64>::from_bytes_as_type(&DUMMY_SEED[..33], KeyType::MACKey).unwrap(); + for name in [BLAKE3_KEYED_HASH_NAME, BLAKE2S_256_MAC_NAME] { + assert!( + matches!( + MACFactory::new(name, &key33), + Err(FactoryError::MACError(MACError::InvalidLength(_))) + ), + "{name}: a 33-byte key must be refused" + ); + } + assert!(MACFactory::new(BLAKE2B_512_MAC_NAME, &key33).is_ok()); + + // Names are case-sensitive, and the hash names are not MACs. + for name in ["blake2b-512-mac", "BLAKE2b-512", "BLAKE3", "blake3-keyedhash"] { + assert!( + matches!( + MACFactory::new(name, &key32), + Err(FactoryError::UnsupportedAlgorithm(_)) + ), + "{name:?} must not construct a MAC" + ); + } + } + #[test] fn hmac_sm3_tests() { // RFC4231 Test Case 1 key/message; expected value from `openssl dgst -sm3 -mac HMAC`, @@ -154,7 +351,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/factory/tests/xof_factory_tests.rs b/crypto/factory/tests/xof_factory_tests.rs index d6550547a..a537ad3a6 100644 --- a/crypto/factory/tests/xof_factory_tests.rs +++ b/crypto/factory/tests/xof_factory_tests.rs @@ -4,30 +4,33 @@ use bouncycastle_ascon::ASCON_XOF128_NAME; use bouncycastle_ascon::AsconXof128; +use bouncycastle_blake3::{BLAKE3, BLAKE3_NAME}; use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{Algorithm, AlgorithmInfo, Hash, HashOneShot, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::xof::TestFrameworkXOF; use bouncycastle_factory::xof_factory::XOFFactory; use bouncycastle_factory::{AlgorithmFactory, FactoryError}; +use bouncycastle_hex as hex; use bouncycastle_sha3::{SHAKE128, SHAKE128_NAME, SHAKE256, SHAKE256_NAME}; const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; /// Every `Hash`, `XOF` and `XOFSqueezer` method of the factory against the direct type `S`. -fn check_against(make: impl Fn() -> XOFFactory, ctx: &str) { +fn check_against(make: impl Fn() -> XOFFactory, ctx: &str) { let n = S::default().output_len(); // metadata + assert_eq!(make().alg_name(), S::ALG_NAME, "{ctx}: alg_name"); assert_eq!(make().block_bitlen(), S::default().block_bitlen(), "{ctx}: block_bitlen"); assert_eq!(make().output_len(), n, "{ctx}: output_len"); assert_eq!( - Hash::max_security_strength(&make()), - Hash::max_security_strength(&S::default()), + make().max_security_strength(), + S::default().max_security_strength(), "{ctx}: max_security_strength" ); // the Hash view - let expected = S::default().hash(MSG); + let expected = S::hash(MSG); assert_eq!(expected.len(), n); assert_eq!(make().hash(MSG), expected, "{ctx}: hash"); @@ -140,6 +143,102 @@ fn ascon_xof128_by_name_matches_the_direct_type() { ); } +/// The official BLAKE3 vector (test_vectors.json, github.com/BLAKE3-team/BLAKE3) for the +/// 1025-byte input `i % 251` -- one full chunk plus one byte, so the root is a parent node: the +/// 131-byte `hash` output, copied from crypto/blake3/tests/vectors/mod.rs. +const BLAKE3_1025_HASH: &str = "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444f4c4a22b4b399155358a994e52bf255de60035742ec71bd08ac275a1b51cc6bfe332b0ef84b409108cda080e6269ed4b3e2c3f7d722aa4cdc98d16deb554e5627be8f955c98e1d5f9565a9194cad0c4285f93700062d9595adb992ae68ff12800ab67a"; + +/// BLAKE3 by name: the official extended-output vector through every `Hash`, `XOF` and +/// `XOFSqueezer` pass-through, plus BLAKE3's byte-oriented partial-bit policy (which is why it +/// cannot use `check_against`, whose partial-bit checks assume SHAKE's bit-oriented input). +#[test] +fn blake3_by_name_matches_the_official_vector() { + let input: Vec = (0..1025).map(|i| (i % 251) as u8).collect(); + let long = hex::decode(BLAKE3_1025_HASH).unwrap(); + // BLAKE3 spec s. 2.6: "Shorter outputs are prefixes of longer ones." The default-length hash + // is 32 bytes (s. 2.1), so the Hash view is the first 32 bytes of the stream. + let n = 32; + + for (name, ctx) in [(BLAKE3_NAME, "BLAKE3 by constant"), ("BLAKE3", "BLAKE3 by string")] { + let make = || XOFFactory::new(name).unwrap(); + + // metadata + assert_eq!(make().block_bitlen(), 512, "{ctx}: block_bitlen"); + assert_eq!(make().output_len(), n, "{ctx}: output_len"); + assert_eq!( + AlgorithmInfo::max_security_strength(&make()), + AlgorithmInfo::max_security_strength(&BLAKE3::new()), + "{ctx}: max_security_strength" + ); + + // the Hash view + assert_eq!(make().hash(&input), &long[..n], "{ctx}: hash"); + let mut out = vec![0xFFu8; n]; + assert_eq!(make().hash_out(&input, &mut out), n, "{ctx}: hash_out returns the length"); + assert_eq!(out, &long[..n], "{ctx}: hash_out"); + + let mut f = make(); + input.chunks(100).for_each(|c| f.do_update(c)); + assert_eq!(f.do_final(), &long[..n], "{ctx}: do_update then do_final"); + + let mut f = make(); + f.do_update(&input); + let mut out = vec![0xFFu8; n]; + assert_eq!(f.do_final_out(&mut out), n, "{ctx}: do_final_out returns the length"); + assert_eq!(out, &long[..n], "{ctx}: do_final_out"); + + // byte-oriented: 0 partial bits is the whole-byte digest, 1..=7 refused, > 7 refused + let mut f = make(); + f.do_update(&input); + assert_eq!(f.do_final_partial_bits(0, 0).unwrap(), &long[..n], "{ctx}: 0 bits"); + + let mut f = make(); + f.do_update(&input); + let mut out = vec![0xFFu8; n]; + assert_eq!(f.do_final_partial_bits_out(0, 0, &mut out).unwrap(), n, "{ctx}: ..._out"); + assert_eq!(out, &long[..n], "{ctx}: do_final_partial_bits_out"); + + assert!(matches!(make().do_final_partial_bits(0x05, 3), Err(HashError::InvalidInput(_)))); + let mut out = vec![0u8; n]; + assert!(matches!( + make().do_final_partial_bits_out(0x05, 3, &mut out), + Err(HashError::InvalidInput(_)) + )); + assert!(matches!(make().do_final_partial_bits(0xFF, 8), Err(HashError::InvalidLength(_)))); + + // the XOF view: one continuous stream across reads + let mut f = make(); + f.do_update(&input); + let mut fo = f.into_squeezer(); + assert_eq!(fo.do_output(n), &long[..n], "{ctx}: do_output"); + let mut buf = vec![0u8; long.len() - n]; + assert_eq!(fo.do_output_out(&mut buf), long.len() - n, "{ctx}: do_output_out length"); + assert_eq!(buf, &long[n..], "{ctx}: do_output_out continues the stream"); + + let mut f = make(); + f.do_update(&input); + assert_eq!( + f.into_squeezer_partial_bits(0, 0).unwrap().do_output(long.len()), + long, + "{ctx}: into_squeezer_partial_bits(0, 0)" + ); + assert!(matches!( + make().into_squeezer_partial_bits(0x05, 3), + Err(HashError::InvalidInput(_)) + )); + assert!(matches!( + make().into_squeezer_partial_bits(0xFF, 8), + Err(HashError::InvalidLength(_)) + )); + + // the one-shots + assert_eq!(make().xof(&input, long.len()), long, "{ctx}: xof"); + let mut out = vec![0xFFu8; long.len()]; + assert_eq!(make().xof_out(&input, &mut out), long.len(), "{ctx}: xof_out length"); + assert_eq!(out, long, "{ctx}: xof_out"); + } +} + /// The configured defaults: SHAKE128 for the general and 128-bit defaults, SHAKE256 for 256-bit. #[test] fn defaults() { @@ -150,7 +249,9 @@ fn defaults() { #[test] fn unknown_names_are_refused() { - for name in ["SHAKE512", "shake128", "", "cSHAKE128", "Ascon-XOF999"] { + for name in + ["SHAKE512", "shake128", "", "cSHAKE128", "Ascon-XOF999", "blake3", "BLAKE3-KeyedHash"] + { assert!( matches!(XOFFactory::new(name), Err(FactoryError::UnsupportedAlgorithm(_))), "{name:?} must not construct a XOF" @@ -174,4 +275,13 @@ fn test_framework_xof() { MSG, &SHAKE256::new().xof(MSG, 100), ); + + // BLAKE3 is byte-oriented, so the suite's partial-byte checks do not apply to it. + let byte_oriented = + TestFrameworkXOF { enable_partial_byte_tests: false, do_final_binds_output_length: false }; + byte_oriented.test_xof( + || XOFFactory::new(BLAKE3_NAME).unwrap(), + MSG, + &BLAKE3::new().xof(MSG, 100), + ); } diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 0d74a1574..bed49ab0e 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -2,8 +2,8 @@ //! RFC 5869, as allowed by NIST SP 800-56Cr2. //! //! This is a utility crate and is not intended to be used directly. It provides [`HKDF`] -- the -//! construction, generic over any struct that implements [`Hash`] and [`HashAlgParams`], the extension -//! point through which a hash declares the metadata from which the HKDF instance is built. +//! construction, generic over any hash that implements [`HMACParams`], the extension point through +//! which a hash declares the metadata from which the HMAC and HKDF instances are built. //! The library provides the following concrete instantiations of HKDF: //! //! | Hash family | Instantiations | @@ -12,11 +12,10 @@ //! //! # Instantiating HKDF over a hash //! -//! HKDF uses HMAC as its underlying primitive, so it works with any hash that HMAC works with: -//! [`HKDF`] needs only [`Hash`] + [`HashAlgParams`] + [`Default`]. Unlike HMAC there is no additional -//! HKDF params trait to implement -- HKDF has no per-hash OIDs and derives its name from nothing -- -//! so an instantiation is just a type alias plus, by convention, a name constant and a suspended-state -//! length. +//! HKDF uses HMAC as its underlying primitive, so it works with exactly the hashes HMAC works with: +//! [`HKDF`] needs [`HMACParams`], which also carries the HKDF name ([`HMACParams::HKDF_ALG_NAME`]). +//! There is no separate HKDF params trait -- HKDF has no per-hash OIDs -- so an instantiation is just +//! a type alias plus, by convention, a suspended-state length. //! //! What the alias has to supply is [`HKDF`]'s two const parameters: //! @@ -112,9 +111,12 @@ use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, MAC, Suspendable, SuspendableKeyed}; -use bouncycastle_hmac::HMAC; -use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle_core::traits::{Algorithm, KDF, MAC, Suspendable, SuspendableKeyed}; +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, bounded_usize, check_lib_ver}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; // Imports needed only for docs @@ -157,19 +159,23 @@ pub const MAX_HMAC_OUTPUT_LEN: usize = 64; /// todo: once rust stabilizes generic_const_exprs, delete both const parameters and write the impl as /// `SuspendableKeyed<{HASH_STATE_LEN + 14}>` over `H: Suspendable`. #[derive(Clone)] -pub struct HKDF< - H: Hash + HashAlgParams + Default, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, -> { - // Optional because an HMAC cannot be constructed until a key is provided - // to initialize it with. +pub struct HKDF { + // 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)] @@ -205,13 +211,13 @@ impl TryFrom for HkdfStates { } #[derive(Clone)] -struct HkdfEntropyTracker { +struct HkdfEntropyTracker { _phantomhash: PhantomData, entropy: usize, security_strength: SecurityStrength, } -impl HkdfEntropyTracker { +impl HkdfEntropyTracker { fn new() -> Self { Self { _phantomhash: PhantomData, entropy: 0, security_strength: SecurityStrength::None } } @@ -220,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(); @@ -231,27 +238,40 @@ impl HkdfEntropyTracker { self.entropy } - // According to NIST SP 800-56Cr2, a KDF is fully seeded when its underlying hash primitive has a full block. + // Library policy, not a rule from a standard: the output is credited as CryptographicRandom + // once at least OUTPUT_LEN bytes of typed key material have gone in. SP 800-133r2 Section 6.3 + // (method 3, note (g)) asks that the component keys' min-entropies sum to at least the + // min-entropy the output needs, and "should be at least twice that amount"; this counts bytes + // of material typed as key rather than min-entropy, so the threshold is the full output length. pub fn is_fully_seeded(&self) -> bool { self.entropy >= H::OUTPUT_LEN } - /// Either [`KeyMaterialTrait::BytesLowEntropy`] or [`KeyMaterialTrait::BytesFullEntropy`] depending on - /// whether enough input key material was provided for the internal hash function to have a full block. + /// [`KeyType::CryptographicRandom`] once at least `OUTPUT_LEN` bytes of typed key material have + /// been credited (see [`Self::is_fully_seeded`]), else [`KeyType::Unknown`]. fn get_output_key_type(&self) -> KeyType { if self.is_fully_seeded() { KeyType::CryptographicRandom } else { KeyType::Unknown } } } -impl - Default for HKDF +/// The name is [`HMACParams::HKDF_ALG_NAME`]; the strength is the hash's, which the extract +/// and expand HMACs cannot exceed. +impl Algorithm + for HKDF +{ + const ALG_NAME: &'static str = H::HKDF_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = H::MAX_SECURITY_STRENGTH; +} + +impl Default + for HKDF { fn default() -> Self { Self::new() } } -impl +impl HKDF { /// Get a new, uninstantiated HKDF object. @@ -269,6 +289,8 @@ impl PRK /// Options: /// Hash a hash function; HashLen denotes the length of the @@ -282,7 +304,8 @@ impl Result { - // From RFC5896 + // RFC 5869 Section 2.3: // N = ceil(L/HashLen) // T = T(1) | T(2) | T(3) | ... | T(N) // OKM = first L octets of T @@ -375,6 +402,13 @@ impl::new(); entropy.credit_entropy(prk); @@ -384,56 +418,63 @@ impl::from_bytes_as_type( - prk.ref_to_bytes(), - KeyType::MACKey, - )?; + // The PRK, re-typed as the MAC key it is (see mac_key_copy). One key object for the whole + // expansion: the pads are computed once here, before any byte of `okm` is written, and each + // block's HMAC starts from a clone of them. + let prk_key = HmacKey::::from_key_material(&Self::mac_key_copy(prk.ref_to_bytes())?)?; + // T(i) is output key material, so it zeroizes on drop. #[allow(non_snake_case)] - let mut T = [0u8; MAX_HMAC_OUTPUT_LEN]; + let mut T: Secret<[u8; MAX_HMAC_OUTPUT_LEN]> = Secret::new(); let mut t_len: usize = 0; let mut i = 1u8; - do_hazardous_operations(okm, |okm| { - let out = okm.ref_to_bytes_mut()?; - while i < N { - let mut hmac = HMAC::::new(&prk_as_mac_key) - .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; - hmac.do_update(&T[..t_len]); - hmac.do_update(info); - hmac.do_update(&[i]); - - t_len = hmac - .do_final_out(&mut T) - .map_err(|_| KeyMaterialError::GenericError("HMAC finalization failed"))?; - debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1) - out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); - bytes_written += t_len; - i += 1; - } - Ok(()) - })?; - - // Part of the output is not taken on the last iteration. "OKM = first L octets of T", so the - // last block T(N) is computed in full and only its first `remaining` octets are kept. Asking - // HMAC for a `remaining`-octet output instead would be a truncated MAC, which HMAC refuses - // below MIN_FIPS_DIGEST_LEN: that rejected every L with L mod HashLen in 1..=3. - let remaining = L - bytes_written; - let mut hmac = HMAC::::new(&prk_as_mac_key)?; - hmac.do_update(&T[..t_len]); - hmac.do_update(info); - hmac.do_update(&[i]); - - t_len = hmac.do_final_out(&mut T[..hash_len])?; - debug_assert_eq!(t_len, hash_len); - - do_hazardous_operations(okm, |okm| { - let out = okm.ref_to_bytes_mut()?; - out[bytes_written..bytes_written + remaining].copy_from_slice(&T[..remaining]); + let filled: Result<(), KDFError> = (|| { + do_hazardous_operations(okm, |okm| { + let out = okm.ref_to_bytes_mut()?; + while i < N { + let mut hmac = prk_key.create_generator(); + hmac.do_update(&T[..t_len]); + hmac.do_update(info); + hmac.do_update(&[i]); + + t_len = hmac + .do_final_out(&mut T[..]) + .map_err(|_| KeyMaterialError::GenericError("HMAC finalization failed"))?; + debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1) + out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]); + bytes_written += t_len; + i += 1; + } + Ok(()) + })?; + + // Part of the output is not taken on the last iteration. "OKM = first L octets of T", so the + // last block T(N) is computed in full and only its first `remaining` octets are kept. Asking + // HMAC for a `remaining`-octet output instead would be a truncated MAC, which HMAC refuses + // below MIN_FIPS_DIGEST_LEN: that rejected every L with L mod HashLen in 1..=3. + let remaining = L - bytes_written; + let mut hmac = prk_key.create_generator(); + hmac.do_update(&T[..t_len]); + hmac.do_update(info); + hmac.do_update(&[i]); + + t_len = hmac.do_final_out(&mut T[..hash_len])?; + debug_assert_eq!(t_len, hash_len); + + do_hazardous_operations(okm, |okm| { + let out = okm.ref_to_bytes_mut()?; + out[bytes_written..bytes_written + remaining].copy_from_slice(&T[..remaining]); + Ok(()) + })?; + bytes_written += remaining; Ok(()) - })?; - bytes_written += remaining; + })(); + // A partly written OKM is key material: on any failure above, none of it is left behind. + if filled.is_err() { + okm.zeroize(); + } + filled?; // Set the KeyType of the output // Since some computation has been performed, the result will not actually be zeroized, even if all input key material was zeroized. @@ -471,11 +512,52 @@ impl Result, MACError> { + if salt.key_type() == KeyType::Unknown { + return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( + "HKDF salt must not be KeyType::Unknown", + ))); + } + Ok(Self::mac_key_copy(salt.ref_to_bytes())?) + } + + /// `bytes` as a [`KeyType::MACKey`] of [`LARGEST_HASHER_BLOCK_LEN`] capacity, HMAC's own bound. + /// + /// HMAC replaces a key "longer than B bytes" by its hash (RFC 2104 Section 3), so bytes longer + /// than the hash block are hashed here first: the copy then keys HMAC identically to `bytes` + /// and never overflows. The salt and the PRK both go through this. + fn mac_key_copy( + bytes: &[u8], + ) -> Result, KeyMaterialError> { + const { assert!(H::BLOCK_LEN <= LARGEST_HASHER_BLOCK_LEN) }; + const { assert!(H::OUTPUT_LEN <= MAX_HMAC_OUTPUT_LEN) }; + if bytes.len() <= H::BLOCK_LEN { + return KeyMaterial::from_bytes_as_type(bytes, KeyType::MACKey); + } + let mut digest: Secret<[u8; MAX_HMAC_OUTPUT_LEN]> = Secret::new(); + let written = H::hash_out(bytes, &mut digest[..H::OUTPUT_LEN]); + KeyMaterial::from_bytes_as_type(&digest[..written], KeyType::MACKey) + } + /// This, together with [`HKDF::do_extract_update_key`], [`HKDF::do_extract_update_bytes`] and [`HKDF::do_extract_final`] /// provide a streaming interface for very long values of `ikm`. /// In this mode, the entropy of `ikm` is untracked, and so only the entropy ef `salt` is taken into account /// when computing the entropy of the output `prk`. - /// The KeyMaterial input parameters can be of any [`KeyType`]; but the type of the output will be set accordingly. + /// The KeyMaterial input parameters can be of any [`KeyType`] but [`KeyType::Unknown`], which is + /// refused as [`KeyMaterialError::InvalidKeyType`]; the type of the output is set from the inputs. /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any /// underlying hash function, but the actual key length will be appropriate to the underlying hash function. /// @@ -487,10 +569,12 @@ impl::new_allow_weak_key(salt)?); + // 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)?; + 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; @@ -523,9 +607,7 @@ impl = 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) } @@ -547,7 +629,7 @@ impl - KDF for HKDF +impl KDF + for HKDF { /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm. /// This provides a fixed-length output, which may be truncated as needed. @@ -674,43 +756,24 @@ impl Result { + // The object's own tracker credits every key: the salt in do_extract_init and each ikm in + // do_extract_update_key. do_extract_final_out types the PRK from it and expand_out types + // the output from the PRK, so no second tracker and no override are needed here. let mut hkdf = Self::new(); - let mut entropy = HkdfEntropyTracker::::new(); - - if keys.len() >= 1 { - hkdf.do_extract_init(keys[0])?; - entropy.credit_entropy(keys[0]); - } else { - hkdf.do_extract_init(&KeyMaterial0::new())?; - }; - - if keys.len() != 0 { - for key in &keys[1..] { - hkdf.do_extract_update_bytes(key.ref_to_bytes())?; - entropy.credit_entropy(*key); + match keys.split_first() { + Some((salt, ikms)) => { + hkdf.do_extract_init(*salt)?; + for ikm in ikms { + hkdf.do_extract_update_key(*ikm)?; + } + } + None => { + hkdf.do_extract_init(&KeyMaterial0::new())?; } } let mut prk = KeyMaterial::::new(); _ = hkdf.do_extract_final_out(&mut prk)?; - let bytes_written = - Self::expand_out(&prk, additional_input, output_key.capacity(), output_key)?; - - do_hazardous_operations(output_key, |output_key| { - output_key.set_key_type(entropy.get_output_key_type())?; - output_key.set_security_strength( - min( - &SecurityStrength::from_bytes(output_key.key_len()), - &entropy.security_strength, - ) - .clone(), - ) - })?; - - Ok(bytes_written) - } - - fn max_security_strength(&self) -> SecurityStrength { - H::default().max_security_strength() + Self::expand_out(&prk, additional_input, output_key.capacity(), output_key) } } @@ -739,10 +802,10 @@ 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: Hash + HashAlgParams + Default + Suspendable, + H: HMACParams + Suspendable, { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key @@ -760,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 @@ -777,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); @@ -788,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), }; @@ -805,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 9736a1e82..7fa4bb569 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -7,17 +7,33 @@ mod hkdf_tests { KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{HashAlgParams, KDF}; + use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, HashAlgParams, HashOneShot, KDF, MAC, + }; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_hex as hex; use bouncycastle_hkdf::HKDF; - use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA512}; + use bouncycastle_hmac::{HMACParams, HmacKey}; + use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA256_NAME, HKDF_SHA512, HKDF_SHA512_NAME}; use bouncycastle_sha2::{ SHA256, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, }; use bouncycastle_utils::ct; + #[test] + fn algorithm_tests() { + // The name is the hash's HKDF name; the strength is the hash's, both through the + // constants and through AlgorithmInfo on an instance. + assert_eq!(HKDF_SHA256::ALG_NAME, HKDF_SHA256_NAME); + assert_eq!(HKDF_SHA512::ALG_NAME, HKDF_SHA512_NAME); + assert_eq!(HKDF_SHA256::MAX_SECURITY_STRENGTH, SHA256::MAX_SECURITY_STRENGTH); + assert_eq!(HKDF_SHA512::MAX_SECURITY_STRENGTH, SHA512::MAX_SECURITY_STRENGTH); + assert_eq!(HKDF_SHA256::new().alg_name(), HKDF_SHA256_NAME); + assert_eq!(HKDF_SHA512::new().max_security_strength(), SecurityStrength::_256bit); + } + #[test] fn test_streaming_apis() { // setup variables @@ -295,8 +311,8 @@ mod hkdf_tests { // okm should be tracked as LowEntropy assert_eq!(okm.key_type(), KeyType::Unknown); - // salt and ikm are full-entropy, but not enough to seed the HKDF, according to FIPS - // first, error case; not a MACKey + // salt and ikm are full-entropy but together below the seeding threshold. A salt of any + // key type but Unknown keys the extract, so this is accepted and the output is Unknown. let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::CryptographicRandom) .unwrap(); @@ -304,35 +320,16 @@ mod hkdf_tests { KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[8..16], KeyType::CryptographicRandom) .unwrap(); - match HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm) { - Ok(_) => { - panic!("Should have thrown a KeyMaterialError that you didn't give it a MACKey"); - } - Err(KDFError::MACError(MACError::KeyMaterialError( - KeyMaterialError::InvalidKeyType(_), - ))) => { /* good */ } - Err(_) => { - panic!("Should have thrown a KeyMaterialError that you didn't give it a MACKey"); - } - }; + _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm).unwrap(); + assert_eq!(okm.key_type(), KeyType::Unknown); - // derive_key has a different behaviour here, since it hard-codes a zero salt as the HMAC key, which is valid, - // it will produce output of Keytype::BytesLowEntropy + // derive_key uses the zero salt and ikm alone: the same output type. _ = HKDF_SHA256::new().derive_key_out(&ikm, &[], &mut okm); assert_eq!(okm.key_type(), KeyType::Unknown); let keys = [&salt, &ikm]; - match HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm) { - Ok(_) => { - panic!("Should have thrown a KeyMaterialError that you didn't give it a MACKey"); - } - Err(KDFError::MACError(MACError::KeyMaterialError( - KeyMaterialError::InvalidKeyType(_), - ))) => { /* good */ } - Err(_) => { - panic!("Should have thrown a KeyMaterialError that you didn't give it a MACKey"); - } - }; + HKDF_SHA256::new().derive_key_from_multiple_out(&keys, &[], &mut okm).unwrap(); + assert_eq!(okm.key_type(), KeyType::Unknown); // success case -- insufficient entropy returns KeyType::BytesLowEntropy let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::MACKey).unwrap(); @@ -488,7 +485,7 @@ mod hkdf_tests { #[test] fn rfc5869_test_cases() { // Test Case 1 - rfc5896_sha256( + rfc5869_sha256( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "000102030405060708090a0b0c", "f0f1f2f3f4f5f6f7f8f9", @@ -498,7 +495,7 @@ mod hkdf_tests { ); // Test Case 2 - rfc5896_sha256( + rfc5869_sha256( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf", "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", @@ -508,7 +505,7 @@ mod hkdf_tests { ); // Test Case 3 - rfc5896_sha256( + rfc5869_sha256( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "", @@ -518,9 +515,145 @@ mod hkdf_tests { ); } + /// A 16-byte key is below the SHA-256 seeding threshold, so the output is `Unknown` with no + /// strength. The multi-key path must agree with the single-key one rather than erroring. + #[test] + fn test_derive_key_from_multiple_partial_entropy_matches_derive_key() { + let key = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) + .unwrap(); + let single = HKDF_SHA256::new().derive_key(&key, b"info").unwrap(); + assert_eq!(single.key_type(), KeyType::Unknown); + assert_eq!(single.security_strength(), SecurityStrength::None); + + // An empty first key is the absent salt, so this is derive_key(&key) through the other path. + let multi = HKDF_SHA256::new() + .derive_key_from_multiple(&[&KeyMaterial128::new(), &key], b"info") + .unwrap(); + assert_eq!(multi.key_type(), KeyType::Unknown); + assert_eq!(multi.security_strength(), SecurityStrength::None); + assert_eq!(multi.ref_to_bytes(), single.ref_to_bytes()); + + // The key alone, as the salt. + let salt_only = HKDF_SHA256::new().derive_key_from_multiple(&[&key], b"info").unwrap(); + assert_eq!(salt_only.key_type(), KeyType::Unknown); + assert_eq!(salt_only.security_strength(), SecurityStrength::None); + } + + /// RFC 5869 Section 2.2 types the salt only as "a non-secret random value"; material of any + /// `KeyType` must key the extract as the `MACKey`-typed copy of it does. + #[test] + fn test_extract_accepts_a_salt_of_any_key_type() { + let ikm = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); + let as_mac = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[32..64], KeyType::MACKey).unwrap(); + let expected = HKDF_SHA256::extract(&as_mac, &ikm).unwrap(); + for key_type in [KeyType::CryptographicRandom, KeyType::Seed, KeyType::SymmetricCipherKey] { + let salt = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[32..64], key_type).unwrap(); + let prk = HKDF_SHA256::extract(&salt, &ikm).unwrap(); + assert_eq!(prk.ref_to_bytes(), expected.ref_to_bytes(), "{key_type:?}"); + } + + // Two random keys through the KDF trait, the first of which is the salt. + let a = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::CryptographicRandom) + .unwrap(); + let b = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[32..64], KeyType::CryptographicRandom) + .unwrap(); + let okm = HKDF_SHA256::new().derive_key_from_multiple(&[&a, &b], b"").unwrap(); + assert_eq!(okm.key_type(), KeyType::CryptographicRandom); + } + + /// HMAC hashes a key longer than its block (RFC 2104 Section 3), so a salt longer than the + /// block must give the PRK that its hash, as the salt, gives. + #[test] + fn test_extract_with_a_salt_longer_than_the_block() { + let ikm = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); + let mut digest = [0u8; 32]; + SHA256::hash_out(&DUMMY_SEED[..100], &mut digest); + let hashed = KeyMaterial256::from_bytes_as_type(&digest, KeyType::MACKey).unwrap(); + let expected = HKDF_SHA256::extract(&hashed, &ikm).unwrap(); + + for key_type in [KeyType::MACKey, KeyType::CryptographicRandom] { + let long = + KeyMaterial::<100>::from_bytes_as_type(&DUMMY_SEED[..100], key_type).unwrap(); + let prk = HKDF_SHA256::extract(&long, &ikm).unwrap(); + assert_eq!(prk.ref_to_bytes(), expected.ref_to_bytes(), "{key_type:?}"); + } + } + + /// `(0..n)` as a MACKey-typed PRK of `n` bytes. + fn prk_of(n: usize) -> KeyMaterial<1024> { + let bytes: Vec = (0..n).map(|i| i as u8).collect(); + KeyMaterial::<1024>::from_bytes_as_type(&bytes, KeyType::MACKey).unwrap() + } + + /// RFC 5869 Section 2.3: the PRK is "of at least HashLen octets". + #[test] + fn test_expand_prk_length() { + let mut okm = KeyMaterial512::new(); + for (short, ok) in [(31usize, 32usize), (16, 64)] { + assert!(matches!( + HKDF_SHA256::expand_out(&prk_of(short), b"", 32, &mut okm), + Err(KDFError::InvalidLength(_)) + )); + assert_eq!(HKDF_SHA256::expand_out(&prk_of(ok), b"", 32, &mut okm).unwrap(), 32); + } + assert!(matches!( + HKDF_SHA512::expand_out(&prk_of(63), b"", 64, &mut okm), + Err(KDFError::InvalidLength(_)) + )); + assert_eq!(HKDF_SHA512::expand_out(&prk_of(64), b"", 64, &mut okm).unwrap(), 64); + } + + /// A refused expand leaves nothing of the output behind: the buffer reads as it did before. + #[test] + fn test_expand_failure_leaves_okm_untouched() { + let mut okm = KeyMaterial512::from_bytes_as_type(&[0xAA; 64], KeyType::MACKey).unwrap(); + assert!(HKDF_SHA256::expand_out(&prk_of(31), b"info", 32, &mut okm).is_err()); + assert_eq!(okm.ref_to_bytes(), &[0xAA; 64]); + } + + /// A PRK longer than the hash block gives what HMAC keyed with it gives (RFC 2104 Section 3 + /// hashes such a key first): T(1) || T(2) computed with HMAC directly. + fn check_expand_long_prk(lens: &[usize]) + where + H: HMACParams, + { + let hash_len = H::OUTPUT_LEN; + for &n in lens { + let prk = prk_of(n); + let key = HmacKey::::from_key_material(&prk).unwrap(); + let mut expected = [0u8; 128]; + let mut t1 = key.create_generator(); + t1.do_update(b"info"); + t1.do_update(&[1]); + t1.do_final_out(&mut expected[..hash_len]).unwrap(); + let mut t2 = key.create_generator(); + t2.do_update(&expected[..hash_len]); + t2.do_update(b"info"); + t2.do_update(&[2]); + t2.do_final_out(&mut expected[hash_len..2 * hash_len]).unwrap(); + + let mut okm = KeyMaterial::<128>::new(); + let written = + HKDF::::expand_out(&prk, b"info", 2 * hash_len, &mut okm).unwrap(); + assert_eq!(written, 2 * hash_len, "PRK of {n} bytes"); + assert_eq!(okm.ref_to_bytes(), &expected[..2 * hash_len], "PRK of {n} bytes"); + } + } + + #[test] + fn test_expand_long_prk() { + // 64 is SHA-256's block, taken verbatim; 65 and up are hashed. 128 is SHA-512's block, the + // boundary that pins `<=`: verbatim and hashed differ there. + check_expand_long_prk::(&[64, 65, 128, 129, 144, 145, 1000]); + check_expand_long_prk::(&[65, 128, 129, 144, 145, 1000]); + } + /*** Helper Functions ***/ #[allow(non_snake_case)] - fn rfc5896_sha256(ikm: &str, salt: &str, info: &str, prk: &str, L: usize, okm: &str) { + fn rfc5869_sha256(ikm: &str, salt: &str, info: &str, prk: &str, L: usize, okm: &str) { /*** First with the one-shot APIs ::extract() and ::expand_out(). ***/ let mut ikm_key = KeyMaterial::<100>::new(); @@ -541,7 +674,7 @@ mod hkdf_tests { let mut prk_key = HKDF_SHA256::extract(&salt_key, &ikm_key).unwrap(); assert_eq!(prk_key.ref_to_bytes(), hex::decode(prk).unwrap()); - // Some of the RFC5896 test vectors have input keys that are too short to meet the entropy seeding rules. + // Some of the RFC 5869 test vectors have input keys that are too short to meet the entropy seeding rules. // So, just for testing, we'll bump this up to full entropy, regardless of what entropy HKDF::extract() // thinks it should be based on the inputs. do_hazardous_operations(&mut prk_key, |prk_key| prk_key.set_key_type(KeyType::MACKey)) @@ -560,7 +693,7 @@ mod hkdf_tests { assert_eq!(okm_key.ref_to_bytes(), hex::decode(okm).unwrap()); } Err(KDFError::KeyMaterialError(_)) => { - /* some of the rfc5896 test vectors are in fact low entropy, so just skip */ + /* some of the RFC 5869 test vectors are in fact low entropy, so just skip */ } Err(_) => panic!("Should have returned a MACError::KeyMaterialError."), } @@ -729,7 +862,7 @@ mod hkdf_tests { } #[test] fn serializable_keyed_state() { - use bouncycastle_core::traits::{Hash, SuspendableKeyed}; + use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; use bouncycastle_sha2::hkdf::{ SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, @@ -743,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: Hash + HashAlgParams + Default, - HKDF: Clone + SuspendableKeyed, + H: HMACParams, + HKDF: + Clone + SuspendableKeyed<'k, LEN, Key = dyn KeyMaterialTrait + 'static>, { let hkdf = HKDF::::new(); @@ -827,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/hkdf/tests/hkdf_wycheproof.rs b/crypto/hkdf/tests/hkdf_wycheproof.rs index 85e161cb1..0f8109adf 100644 --- a/crypto/hkdf/tests/hkdf_wycheproof.rs +++ b/crypto/hkdf/tests/hkdf_wycheproof.rs @@ -12,9 +12,9 @@ use bouncycastle_core::errors::KDFError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, HashAlgParams}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; use bouncycastle_hkdf::HKDF; +use bouncycastle_hmac::HMACParams; use bouncycastle_sha2::hkdf::{ SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA384_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, @@ -30,11 +30,7 @@ const MAX_OKM_LEN: usize = 255 * 64; /// Runs every case in one `hkdf_*_test.json` file through `HKDF`. The state lengths are /// those of `bouncycastle_sha2::hkdf`'s aliases, which cannot be passed as a type here. -fn run< - H: Hash + HashAlgParams + Default, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, ->( +fn run( filename: &str, algorithm: &str, ) { 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 11cf01e1d..088ac045f 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -1,10 +1,12 @@ //! The generic Hash-Based Message Authentication Code (HMAC) construction, as specified in RFC 2104, //! taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. //! -//! This is a utility crate and is not intended to be used directly. It provides [`HMAC`] -- the -//! construction, 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: +//! 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, 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 | //! |-------------|------------------------------------------------------------------------------| @@ -14,6 +16,32 @@ //! Although users are free to implement [`Hash`] and [`HMACParams`] for a a hash function not included with the library, //! and will then be able to instantiate [`HMAC`] for it as well. //! +//! # Usage +//! +//! A key is built once, from key material of any length, and used for any number of messages: +//! +//! ``` +//! use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_hmac::HmacKey; +//! use bouncycastle_rng::HashDRBG_SHA512; +//! use bouncycastle_sha2::SHA256; +//! +//! let mut rng = HashDRBG_SHA512::new_from_os(); +//! let key = HmacKey::::generate(&mut rng).expect("a 32-byte MAC key from the RNG"); +//! +//! let mut generator = key.create_generator(); // infallible, any number of times +//! generator.do_update(b"part one"); +//! generator.do_update(b"part two"); +//! let mut tag = [0u8; 32]; +//! let n = generator.do_final_out(&mut tag).expect("32 bytes is above the truncation floor"); +//! +//! let mut verifier = key.create_verifier(); +//! verifier.do_update(b"part onepart two"); +//! assert!(verifier.do_verify_final(&tag[..n])); +//! assert!(key.verify(b"part onepart two", &tag[..n])); // one-shot, same path underneath +//! ``` +//! //! # Instantiating HMAC over a Hash //! //! HMAC works with any hash: [`HMAC`](HMAC) needs only [`Hash`]. What HMAC cannot @@ -30,21 +58,18 @@ //! strength and OID, plus the key type that [`HMAC::keygen_from_rng`] should return (typically a //! `KeyMaterial` for an L that matches the size of the underlying hash function. This allows //! this crate to provide blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. -//! 3. Publish a type alias for the instantiation, passing [`HashAlgParams::BLOCK_LEN`] as the key -//! buffer length. Per RFC 2104 a key no longer than the hash's block is used verbatim, and only -//! longer keys are pre-hashed down to the output length, so the buffer must hold a full block. -//! Reading the length off the hash rather than writing a literal means the two cannot drift apart. +//! 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::BLOCK_LEN }>; +//! pub type HMAC_SHA256<'k> = HMAC<'k, SHA256>; //! //! impl HMACParams for SHA256 { //! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; @@ -67,34 +92,45 @@ //! 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 buffer is held in [`bouncycastle_utils::secret::Secret`] and zeroized on drop. The -//! `K ⊕ ipad` / `K ⊕ opad` blocks are transient stack allocations and are not zeroized. +//! * 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 +//! prints them. The padded key block they are built from is a [`Secret`] scratch, zeroized when +//! construction returns. +//! +//! [`Secret`]: bouncycastle_utils::secret::Secret #![forbid(unsafe_code)] #![forbid(missing_docs)] +pub mod hazmat; + use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, 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::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, RNG, Suspendable, SuspendableKeyed, + Algorithm, AlgorithmOID, HashAlgParams, HashOneShot, MAC, RNG, Suspendable, SuspendableKeyed, }; use bouncycastle_utils::{ct, secret::Secret}; use core::fmt::{Debug, Display, Formatter}; @@ -115,8 +151,9 @@ use core::fmt::{Debug, Display, Formatter}; /// /// The block length and the generated-key length do not need to be restated here since they are already /// carried by the hash itself as [`HashAlgParams::BLOCK_LEN`] and [`HashAlgParams::OUTPUT_LEN`]. -pub trait HMACParams: Hash + HashAlgParams + Default { - /// The key type produced by [`HMAC::keygen_from_rng`], sized to this hash's output length. +pub trait HMACParams: HashOneShot + HashAlgParams { + /// The key type produced by [`HMAC::keygen_from_rng`] and drawn by + /// [`FromAnyKeyMaterial::generate`], sized to this hash's output length. /// /// Implementors should set this to `KeyMaterial<{Self::OUTPUT_LEN}>`. /// @@ -126,6 +163,9 @@ pub trait HMACParams: Hash + HashAlgParams + Default { /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. const HMAC_ALG_NAME: &'static str; + /// The name of HKDF (RFC 5869) over this hash, as reported by `bouncycastle_hkdf::HKDF`'s + /// [`Algorithm::ALG_NAME`]. + const HKDF_ALG_NAME: &'static str; /// The strength claimed by the HMAC over this hash, as reported by /// [`Algorithm::MAX_SECURITY_STRENGTH`]. const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength; @@ -135,183 +175,254 @@ pub trait HMACParams: Hash + HashAlgParams + Default { 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; } -// The internal key buffer must be able to hold a key up to the *block length* of the underlying hash: -// per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down -// to the output length). So the buffer size is a const parameter of the struct, set per hash to its -// block length by the type aliases. -// -// The default is used only when `HMAC` is written without an explicit buffer size; it is the -// largest block length across all supported hashes, so it is always large enough. -const LARGEST_HASHER_BLOCK_LEN: usize = 144; - -/// Internal struct for HKDF. -/// HMAC implements RFC 2104. -/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones). -#[derive(Clone)] -pub struct HMAC { - hasher: HASH, - // todo: once rust stable merges generic_const_exprs, we can remove this hack and delete the KEY_BUF_LEN param. - // key: [u8; HASH::OUTPUT_LEN]; - key: Secret<[u8; KEY_BUF_LEN]>, - key_len: Secret, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. -} - -impl Debug for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HMAC-{} instance", HASH::ALG_NAME,) - } -} +/// The largest block length, in bytes, of any hash HMAC is instantiated with (SHA3-224's 144). +/// +/// The scratch the pad blocks are built in must hold one block of the underlying hash: per RFC 2104 +/// Section 2 step (1) the key is zero-padded to B bytes, and only a key longer than B is pre-hashed +/// first (Section 3). The scratch is sliced to the hash's own block. HKDF sizes the MAC-key copies +/// it hands this crate from the same constant. +pub const LARGEST_HASHER_BLOCK_LEN: usize = 144; -impl Display for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HMAC-{} instance", HASH::ALG_NAME,) - } -} +/// The largest output length across the supported hashes (SHA-512, SHA3-512), which sizes the key +/// [`FromAnyKeyMaterial::generate`] draws before trimming it to the hash's own output length, and +/// the scratch the inner digest is finished into. +const LARGEST_HASHER_OUTPUT_LEN: usize = 64; -// See definitions in RFC 2104 Section 2. -const IPAD_BYTE: u8 = 0x36; -const OPAD_BYTE: u8 = 0x5C; +/*** Key ***/ -/// Per FIPS 140-2 IG A.8 Use of a truncated HMAC (matching NIST SP 800-107-r1 -/// Section 5.3.3. Truncation of HMAC), says that the minimum truncation of a -/// HMAC for tagging should be 32 bits; this exceeds the lower bound set by -/// IETF RFC 2104 Section 5 Truncated output, which sets the lower bound to be -/// half of the hash's length and no fewer than 80 bits. +/// 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. /// -/// However, as we feel there should be a minimum limit (and have an author -/// work around this via explicit truncation manually afterwards), but not -/// be too strict about it, -/// = 32 bits / 8 = 4 bytes; -pub const MIN_FIPS_DIGEST_LEN: usize = 4; +/// 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, + /// `H` after absorbing `K XOR opad`: the start of step (7). + outer: HASH, +} -impl HMAC { - fn pad_key_into_hasher(&mut self, padding: u8) { - // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. - // TODO: make this no_std-friendly - let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; +// `Clone` is written out rather than derived so that it has no bound beyond the struct's own. +impl Clone for HmacKey { + fn clone(&self) -> Self { + Self { inner: self.inner.clone(), outer: self.outer.clone() } + } +} - padded[..*self.key_len].copy_from_slice(&self.key[..*self.key_len]); +impl HmacKey { + /// RFC 2104 Section 2 steps (1), (2) and (5), plus Section 3's pre-hash of a key longer than + /// the block, with both pad blocks absorbed so that the states are ready for steps (4) and (7). + /// No key checks: the callers do those. + fn from_key_bytes(key_bytes: &[u8]) -> Self { + let block_len = HASH::default().block_bitlen() / 8; + assert!( + block_len <= LARGEST_HASHER_BLOCK_LEN, + "Fatal error: hash block length exceeds HMAC's pad scratch" + ); - // XXX: easier way to xor over Vec? - for entry in &mut padded { - *entry ^= padding; + // Step (1): "append zeros to the end of K to create a B byte string". The scratch is + // already zero, so only K (or, per Section 3, H(K) when K is "longer than B bytes") is + // written. + let mut k: Secret<[u8; LARGEST_HASHER_BLOCK_LEN]> = Secret::new(); + if key_bytes.len() > block_len { + let output_len = HASH::default().output_len(); + HASH::hash_out(key_bytes, &mut k[..output_len]); + } else { + k[..key_bytes.len()].copy_from_slice(key_bytes); } - // Per RFC 2104 Section 2, write the padded key into the stream prior - // to any other data. - self.hasher.do_update(&padded) - } - - /// Per RFC 2104 Section 2, if the application key exceeds the block - /// length of the underlying hashes algorithm, we apply a hash invocation - /// over the key first. - /// This does NOT absorb the key into the hasher; that is done separately via [`HMAC::pad_key_into_hasher`]. - fn load_key_material(&mut self, key_bytes: &[u8]) { - if key_bytes.len() > self.hasher.block_bitlen() / 8 { - // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one - HASH::default().hash_out(key_bytes, &mut self.key[..self.hasher.output_len()]); - *self.key_len = self.hasher.output_len(); - } else { - self.key[..key_bytes.len()].copy_from_slice(key_bytes); - *self.key_len = key_bytes.len(); + // Step (2): "XOR ... the B byte string computed in step (1) with ipad", absorbed into the + // inner hash; step (5): the same with opad, absorbed into the outer hash. Section 4: these + // two block compressions are what the key saves on every message. + let mut inner = HASH::default(); + let mut outer = HASH::default(); + for byte in k[..block_len].iter_mut() { + *byte ^= IPAD_BYTE; + } + inner.do_update(&k[..block_len]); + for byte in k[..block_len].iter_mut() { + *byte ^= IPAD_BYTE ^ OPAD_BYTE; // undo ipad, apply opad } + outer.do_update(&k[..block_len]); - // Just as a sanity-check. - assert!( - *self.key_len <= KEY_BUF_LEN, - "Fatal error: Key length exceeds HMAC internal buffer length" - ); + Self { inner, outer } } - /// Private init so that users are forced to go through one of the public new methods and thus we - /// don't need to track state errors. - fn init(&mut self, key: &impl KeyMaterialTrait, allow_weak_keys: bool) -> Result<(), MACError> { - // check that the key is of type KeyMaterial::MACKey - // Make an exception for all-zero keys, which is allowed (which can be zero-length or non-zero-length, - // because it's just a nuisance to force users to set KeyType::MACKey for an all-zero key. + /// The key-type check and, unless `allow_weak_keys`, the strength check, then the pads. + fn from_key_material_checked( + key: &dyn KeyMaterialTrait, + allow_weak_keys: bool, + ) -> Result { if !(key.key_type() == KeyType::Zeroized || key.key_type() == KeyType::MACKey) { return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType( "Key type must be a MAC key.", ))); } + if !allow_weak_keys && key.security_strength() < HASH::MAX_SECURITY_STRENGTH { + return Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength( + "HmacKey: provided key has a lower security strength than the instantiated HMAC", + ))); + } + Ok(Self::from_key_bytes(key.ref_to_bytes())) + } +} - // import the key material as bytes. - // Per RFC 2104 Section 2, if the application key exceeds the block - // length of the underlying hashes algorithm, we apply a hash invocation - // over the key first. +impl FromAnyKeyMaterial for HmacKey { + /// Builds the key from material tagged [`KeyType::MACKey`] (or [`KeyType::Zeroized`], for an + /// all-zero key) whose security strength is at least the HMAC's. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`] or + /// [`KeyMaterialError::SecurityStrength`]. + fn from_key_material(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, false) + } - self.load_key_material(key.ref_to_bytes()); + /// As [`from_key_material`](Self::from_key_material) without the strength check. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] with [`KeyMaterialError::InvalidKeyType`]. + fn from_key_material_allow_weak(key: &dyn KeyMaterialTrait) -> Result { + Self::from_key_material_checked(key, true) + } - self.pad_key_into_hasher(IPAD_BYTE); + /// 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 capped at what that length can + /// carry, and builds the key from it. + /// + /// # Errors + /// [`MACError::KeyMaterialError`] if the RNG fails, or with + /// [`KeyMaterialError::SecurityStrength`] if its strength is below the HMAC's. + fn generate(rng: &mut dyn RNG) -> Result { + let output_len = HASH::default().output_len(); + let mut key = KeyMaterial::::new(); + do_hazardous_operations(&mut key, |key| { + rng.next_bytes_out(&mut key.ref_to_bytes_mut()?[..output_len]) + .map_err(|_| KeyMaterialError::GenericError("RNG failed."))?; + key.set_key_len(output_len)?; + key.set_key_type(KeyType::MACKey)?; + // 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) + } +} - // check that the key had enough security level - if !allow_weak_keys && key.security_strength() < HASH::default().max_security_strength() { - Err(KeyMaterialError::SecurityStrength( - "HMAC::init(): provided key has a lower security strength than the instantiated HMAC", - ))? - } else { - Ok(()) - } +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() } - /// the out buffer can be oversized, but not less than the MIN_FIPS_DIGEST_LENGTH - /// Returns the number of bytes written. - fn do_final_internal_out(mut self, out: &mut [u8]) -> Result { + /// 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) + } +} - out.fill(0); +impl MacGenerationKey for HmacKey { + type Generator<'k> + = HMAC<'k, HASH> + where + Self: 'k; - // Per RFC 2104 Section 2, save our inner digest to calculate our - // outer digest. Note that we can't (necessarily) reuse out as a - // scratch pad here: if we're truncating the output but not - // truncating the underlying hashes, we'd lose bytes and compute an - // invalid outer hashes. - // TODO: rework this to be no_std friendly (ie no vec!) - let mut ihash = vec![0u8; self.hasher.output_len()]; - // `HMAC` implements `Drop` (required by `Secret`), so we cannot move `self.hasher` out - // directly. Swap in a fresh default and consume the taken-out hasher instead. - core::mem::take(&mut self.hasher).do_final_out(&mut ihash); - - // ohash - self.hasher = HASH::default(); - self.pad_key_into_hasher(OPAD_BYTE); - self.hasher.do_update(&ihash); - Ok(core::mem::take(&mut self.hasher).do_final_out(out)) + fn create_generator<'k>(&'k self) -> Self::Generator<'k> { + HMAC { hasher: self.inner.clone(), key: self } } } -// TODO: potential feature: add an interface that pre-computes the intermediate values (K XOR ipad) and (K XOR opad) -// TODO for a given key as described in RFC2104 section 4. -// TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key -// TODO: against different data. +impl MacVerificationKey for HmacKey { + type Verifier<'k> + = HMAC<'k, HASH> + where + Self: 'k; -impl MAC for HMAC { - fn new(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = Self { hasher: HASH::default(), key: Secret::new(), key_len: Secret::new() }; - hmac.init(key, false)?; - Ok(hmac) + 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 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<'k, HASH: HMACParams> { + /// RFC 2104 Section 2 steps (3)-(4): `H(K XOR ipad, text)` in progress. + hasher: HASH, + /// The key, for `H` after absorbing `K XOR opad` at step (7). + key: &'k HmacKey, +} + +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<'_, HASH> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "HMAC-{} instance", HASH::ALG_NAME,) + } +} - fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = Self { hasher: HASH::default(), key: Secret::new(), key_len: Secret::new() }; - hmac.init(key, true)?; - Ok(hmac) +// See definitions in RFC 2104 Section 2. +const IPAD_BYTE: u8 = 0x36; +const OPAD_BYTE: u8 = 0x5C; + +/// Per FIPS 140-2 IG A.8 Use of a truncated HMAC (matching NIST SP 800-107-r1 +/// Section 5.3.3. Truncation of HMAC), says that the minimum truncation of a +/// HMAC for tagging should be 32 bits; this exceeds the lower bound set by +/// IETF RFC 2104 Section 5 Truncated output, which sets the lower bound to be +/// half of the hash's length and no fewer than 80 bits. +/// +/// However, as we feel there should be a minimum limit (and have an author +/// work around this via explicit truncation manually afterwards), but not +/// be too strict about it, +/// = 32 bits / 8 = 4 bytes; +pub const MIN_FIPS_DIGEST_LEN: usize = 4; + +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 { + self.key.finish_out(self.hasher, out) } +} +impl MAC for HMAC<'_, HASH> { fn output_len(&self) -> usize { self.hasher.output_len() } @@ -324,7 +435,6 @@ impl MAC for HMAC Result { out.fill(0); - self.do_update(data); self.do_final_out(&mut out) } @@ -346,7 +456,6 @@ impl MAC for HMAC Result { out.fill(0); - self.do_final_internal_out(&mut out) } @@ -358,82 +467,47 @@ impl MAC for HMAC SecurityStrength { - HASH::default().max_security_strength() - } } /* SerializedState */ -/// HMAC is a keyed algorithm, so it implements [`SuspendableKeyed`] (rather than -/// [`Suspendable`]) for suspending and resuming in-progress operations. -/// The key is deliberately NOT written into the serialized -/// bytes and must be re-supplied at deserialization. +/// Suspend / resume for `HMAC`. /// -/// The serialized state is exactly the inner hasher's state (which has already absorbed `K ⊕ ipad` -/// and any message chunks provided so far) — so this is a straight passthrough to the underlying hash's -/// [`Suspendable`] impl. The re-supplied key is needed to reconstruct the material for the outer -/// (`K ⊕ opad`) step at finalization. -/// -/// There is no way to detect a mismatched key on -/// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed -/// operation will silently produce an incorrect MAC. -impl< - const HASH_STATE_LEN: usize, - const KEY_BUF_LEN: usize, - HASH: Hash + Default + Suspendable, -> 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> { - // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` - // rather than a single concrete key type. The key is only used (by reference) to reload the key - // bytes at from_serialized_state, so dynamic dispatch here is negligible. - type Key = dyn KeyMaterialTrait; - - fn suspend(mut self) -> [u8; HASH_STATE_LEN] { - // The key is intentionally excluded; the resumable state is just the inner hasher, which - // already carries the library version header from the hash's own SerializableState impl. - // `HMAC` implements `Drop` (required by `Secret`), so move the hasher out via `mem::take` - // rather than a direct partial move. - core::mem::take(&mut self.hasher).suspend() + type Key = HmacKey; + + fn suspend(self) -> [u8; HASH_STATE_LEN] { + self.hasher.suspend() } fn from_suspended( state: [u8; HASH_STATE_LEN], - key: &Self::Key, + key: &'k Self::Key, ) -> Result { - // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). - let hasher = HASH::from_suspended(state)?; - - // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do - // NOT re-absorb `K ⊕ ipad` — the deserialized hasher already contains it. The key is only - // needed for the outer `K ⊕ opad` step at finalization. - let mut hmac = HMAC { hasher, key: Secret::new(), key_len: Secret::new() }; - hmac.load_key_material(key.ref_to_bytes()); - - Ok(hmac) + Ok(HMAC { hasher: HASH::from_suspended(state)?, key }) } } /* KeyGen functions */ -impl HMAC { - /// Generates a key of the appropriate length for this HMAC from the provided RNG, tagged - /// [`KeyType::MACKey`] and ready to hand to [`MAC::new`]. - /// - /// The key length is the underlying hash's output length ([`HashAlgParams::OUTPUT_LEN`], carried - /// as [`HMACParams::MACKey`]); see that associated type for why. +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. /// - // Dev note: done this way to avoid this crate needing a dependency on the `bouncycastle-rng` crate, - // which itself has a dependency on `bouncycastle-sha2` which depends on this hmac crate, - // which creates a circular cargo dependency. + /// # Errors + /// [`RNGError::SecurityStrengthInsufficientForAlgorithm`] if `rng` is instantiated below + /// [`HMACParams::HMAC_MAX_SECURITY_STRENGTH`], or whatever the RNG returns. pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { - // Refuse to generate a key from an RNG that cannot back the strength this HMAC claims; - // otherwise the key's tagged security strength would overstate its true entropy. if rng.security_strength() < HASH::HMAC_MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm); } - let mut key = HASH::MACKey::default(); rng.fill_keymaterial_out(&mut key)?; key.set_key_type(KeyType::MACKey)?; diff --git a/crypto/hmac/tests/hmac_key_tests.rs b/crypto/hmac/tests/hmac_key_tests.rs new file mode 100644 index 000000000..e60bd9414 --- /dev/null +++ b/crypto/hmac/tests/hmac_key_tests.rs @@ -0,0 +1,197 @@ +//! `HmacKey`: the key object holding RFC 2104 Section 4's two pad states, and the workers it hands +//! 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, RNG, SuspendableKeyed}; +use bouncycastle_core_test_framework::FixedSeedRNG; +use bouncycastle_hmac::{HMAC, HMACParams, HmacKey}; +use bouncycastle_sha2::{SHA256, SHA512, SUSPENDED_SHA256_STATE_LEN}; +use bouncycastle_sha3::SHA3_256; + +fn mac_key(fill: u8) -> KeyMaterial { + KeyMaterial::::from_bytes_as_type(&[fill; N], KeyType::MACKey).expect("a MAC key") +} + +const MSG: &[u8] = b"The quick brown fox jumps over the lazy dog"; + +#[test] +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, 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 = 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(); + generator.do_update(&MSG[..10]); + generator.do_update(&MSG[10..]); + assert_eq!(generator.do_final(), expected); + } + for len in [1usize, 20, 64, 65, 136, 137, 200] { + let bytes: Vec = (0..len).map(|i| i as u8).collect(); + check::(&bytes); + check::(&bytes); + check::(&bytes); + } +} + +#[test] +fn several_workers_from_one_key_are_independent_and_the_key_is_unchanged() { + let key = HmacKey::::from_key_material(&mac_key::<32>(0x0b)).unwrap(); + let before = key.mac(MSG); + + let mut a = key.create_generator(); + let mut b = key.create_generator(); + a.do_update(b"message one"); + b.do_update(b"message two"); + let mut tag_b = [0u8; 32]; + let nb = b.do_final_out(&mut tag_b).unwrap(); // finished in the other order + let mut tag_a = [0u8; 32]; + 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(), key.mac(b"message one")); + + let mut v = key.create_verifier(); + v.do_update(b"message one"); + assert!(v.do_verify_final(&tag_a)); + assert!(key.verify(b"message two", &tag_b)); + assert!(!key.verify(b"message two", &tag_a)); + assert_eq!(key.mac(MSG), before); +} + +#[test] +fn one_shots_match_the_streaming_path_and_zeroize_the_output_first() { + let key = HmacKey::::from_key_material(&mac_key::<64>(0x42)).unwrap(); + let full = key.mac(MSG); + assert_eq!(full.len(), 64); + + let mut out = [0xEEu8; 80]; + let n = key.mac_out(MSG, &mut out).unwrap(); + assert_eq!(n, 64); + assert_eq!(&out[..64], &full[..]); + assert!(out[64..].iter().all(|&b| b == 0), "the whole output buffer is zeroized first"); + + // Truncation below the FIPS floor is refused, as on the worker. + assert!(matches!(key.mac_out(MSG, &mut [0u8; 3]), Err(MACError::InvalidLength(_)))); + // A 16-byte truncation is accepted and is a prefix of the full MAC. + let mut short = [0u8; 16]; + assert_eq!(key.mac_out(MSG, &mut short).unwrap(), 16); + assert_eq!(short, full[..16]); +} + +#[test] +fn construction_applies_the_key_type_and_strength_policy() { + // Wrong key type. + let seed = KeyMaterial256::from_bytes_as_type(&[7; 32], KeyType::Seed).unwrap(); + assert!(matches!( + HmacKey::::from_key_material(&seed), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + assert!(matches!( + HmacKey::::from_key_material_allow_weak(&seed), + Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(_))) + )); + + // Too weak for the construction: refused by the checked form, admitted by the weak form. + let mut weak = mac_key::<32>(0x0b); + weak.set_security_strength(SecurityStrength::_112bit).unwrap(); + assert!(matches!( + HmacKey::::from_key_material(&weak), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); + assert!(HmacKey::::from_key_material_allow_weak(&weak).is_ok()); + + // 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(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] +fn generate_draws_a_key_of_the_hash_output_length_at_the_rng_strength() { + let mut rng = FixedSeedRNG::<32>::new([0x5Au8; 32]); + let key = + HmacKey::::generate(&mut rng).expect("a 256-bit RNG suffices for HMAC-SHA256"); + let tag = key.mac(MSG); + assert!(key.verify(MSG, &tag)); + // A draw from another RNG stream is a different key. + let other = HmacKey::::generate(&mut FixedSeedRNG::<32>::new([0xA5u8; 32])).unwrap(); + assert!(!other.verify(MSG, &tag)); + + // The drawn material is the hash's output length, tagged MACKey: the same bytes through + // `from_key_material` give the same key. + let mut rng2 = FixedSeedRNG::<32>::new([0x5Au8; 32]); + let mut km = KeyMaterial::<32>::from_rng(&mut rng2).unwrap(); + 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::<32>::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::<28>::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::<32>::new([0x5Au8; 32]); + weak_rng.set_security_strength(SecurityStrength::_128bit); + assert!(HmacKey::::generate(&mut weak_rng).is_ok()); + assert!(matches!( + HmacKey::::generate(&mut weak_rng), + Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) + )); +} + +#[test] +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, &key).unwrap(); + resumed.do_update(&MSG[7..]); + assert_eq!(resumed.do_final(), key.mac(MSG)); +} + +#[test] +fn a_cloned_key_is_the_same_key() { + let key = HmacKey::::from_key_material(&mac_key::<32>(0x33)).unwrap(); + assert_eq!(key.clone().mac(MSG), 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/mldsa-common/Cargo.toml b/crypto/mldsa-common/Cargo.toml new file mode 100644 index 000000000..75eb97023 --- /dev/null +++ b/crypto/mldsa-common/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bouncycastle-mldsa-common" +version.workspace = true +edition.workspace = true + +[dependencies] +bouncycastle-core.workspace = true +bouncycastle-sha3.workspace = true +bouncycastle-utils.workspace = true diff --git a/crypto/mldsa-common/src/aux_functions.rs b/crypto/mldsa-common/src/aux_functions.rs new file mode 100644 index 000000000..636c20fce --- /dev/null +++ b/crypto/mldsa-common/src/aux_functions.rs @@ -0,0 +1,765 @@ +//! The auxiliary functions of FIPS 204, Section 7, on single coefficients and single polynomials: +//! the samplers (7.3), the bit packing and unpacking of one coordinate (7.1) and the rounding and +//! hint functions (7.4). Anything over a whole vector or matrix is the business of the crate that +//! holds vectors. +//! +//! Where a function exists in an in-place or caller-buffer form and a value-returning form, the +//! former is the implementation and the latter wraps it. + +use crate::params::{ + GAMMA1_2_POW_17, GAMMA1_2_POW_19, GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, + MLDSAParamsBase, +}; +use crate::polynomial::Polynomial; +use crate::{G, H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN, d, q}; +use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +use bouncycastle_utils::secret::ZeroizablePrimitive; + +/// Algorithm 14 CoeffFromThreeBytes(𝑏0, 𝑏1, 𝑏2) +/// Output: An integer modulo 𝑞, or `None` for ⊥. +pub fn coeff_from_three_bytes(b: &[u8; 3]) -> Option { + // This is the exact alg from FIPS 204: + // let mut b2_prime = b2; + // if b2_prime > 127 { + // // set the top bit of b2_prime to 0 + // b2_prime = b2_prime - 128; + // } + + // but this is equivalent and feels more constant-time: + let b2_prime = b[2] & 0x7F; + + let z: i32 = ((b2_prime as i32) << 16) | ((b[1] as i32) << 8) | (b[0] as i32); + + // mutants note: yeah, this could be z <= q and nothing changes because z == q is the same as z == 0 + if z < q { Some(z) } else { None } +} + +/// Algorithm 15 CoeffFromHalfByte(𝑏) +/// Let 𝜂 ∈ {2, 4}. Generates an element of {−𝜂, −𝜂 + 1, … , 𝜂} ∪ {⊥}. +/// Input: Integer 𝑏 ∈ {0, 1, … , 15}. +/// Output: An integer between −𝜂 and 𝜂, or `None` for ⊥. +#[inline(always)] +pub fn coeff_from_half_byte(b: u8) -> Option { + if P::eta == 2 && b < 15 { + // '%' is not constant-time, so `b mod 5` is written as a three-way range split. The + // branch is on which third of 0..=14 `b` falls in; within each third the coefficient + // 2 - (b mod 5) takes every value in -2..=2 once, so which branch ran is independent of + // the coefficient it produced. The rejection of b = 15 (and of b > 8 for eta = 4) is the + // branch every rejection sampler has, as in FIPS 204 Algorithm 15 itself. + let b = match b { + b if b < 5 => b, + b if b < 10 => b - 5, + _ => b - 10, + }; + Some(2 - b as i32) + } else { + if P::eta == 4 && b < 9 { Some(4 - b as i32) } else { None } + } +} + +/// A specific instantiation of Algorithm 16 SimpleBitPack(𝑤, 𝑏) with the constants set for packing the t1 vector +/// Encodes a polynomial 𝑤 into a byte string. +/// Input: 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in [0, 𝑏]. +/// Output: A byte string of length 32 ⋅ bitlen 𝑏. +pub fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { + let mut output = [0u8; POLY_T1PACKED_LEN]; + for i in 0..N / 4 { + output[5 * i] = w[4 * i] as u8; + output[5 * i + 1] = ((w[4 * i] >> 8) | (w[4 * i + 1] << 2)) as u8; + output[5 * i + 2] = ((w[4 * i + 1] >> 6) | (w[4 * i + 2] << 4)) as u8; + output[5 * i + 3] = ((w[4 * i + 2] >> 4) | (w[4 * i + 3] << 6)) as u8; + output[5 * i + 4] = (w[4 * i + 3] >> 2) as u8; + } + output +} + +/// A variant of Algorithm 17 BitPack specific to a=eta, b=eta +/// Encodes a polynomial 𝑤 into a byte string. +/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−eta, eta]. +/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). +// `match P::eta` folds away per monomorphization, so 𝜂 = 2 and 𝜂 = 4 each compile to just their +// own arm, leaving no dispatch at runtime. +#[inline(always)] +pub fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { + // `>=` rather than `==`: a caller may reuse one buffer sized for the largest 𝜂 across all + // three parameter sets and copy out only the first `POLY_ETA_PACKED_LEN` bytes, so for 𝜂 = 2 + // the buffer is deliberately longer than what gets written. Exactly that many bytes are + // written out either way, so this still catches an undersized buffer. + debug_assert!(r.len() >= P::POLY_ETA_PACKED_LEN); + + // temp swap space + let mut t: [u8; 8] = [0; 8]; + + match P::eta { + // MLDSA44 and MLDSA87 + 2 => { + let eta: i32 = 2; + for i in 0..N / 8 { + t[0] = (eta - w[8 * i]) as u8; + t[1] = (eta - w[8 * i + 1]) as u8; + t[2] = (eta - w[8 * i + 2]) as u8; + t[3] = (eta - w[8 * i + 3]) as u8; + t[4] = (eta - w[8 * i + 4]) as u8; + t[5] = (eta - w[8 * i + 5]) as u8; + t[6] = (eta - w[8 * i + 6]) as u8; + t[7] = (eta - w[8 * i + 7]) as u8; + + r[3 * i] = t[0] | (t[1] << 3) | (t[2] << 6); + r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7); + r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); + } + } + // MLDSA65 + 4 => { + let eta: i32 = 4; + for i in 0..N / 2 { + t[0] = (eta - w[2 * i]) as u8; + t[1] = (eta - w[2 * i + 1]) as u8; + r[i] = t[0] | t[1] << 4; + } + } + _ => panic!("Invalid eta value"), + } +} + +/// A variant of Algorithm 17 BitPack specific to packing the t0 polynomial with a=2^{d-1}-1, b=2^{d-1} +/// Encodes a polynomial 𝑤 into a byte string. +/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−𝑎, 𝑏]. +/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). +pub fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { + let mut r = [0u8; POLY_T0PACKED_LEN]; + + let mut t = [0; 8]; + for i in 0..N / 8 { + t[0] = (1 << (d - 1)) - t0[8 * i]; + t[1] = (1 << (d - 1)) - t0[8 * i + 1]; + t[2] = (1 << (d - 1)) - t0[8 * i + 2]; + t[3] = (1 << (d - 1)) - t0[8 * i + 3]; + t[4] = (1 << (d - 1)) - t0[8 * i + 4]; + t[5] = (1 << (d - 1)) - t0[8 * i + 5]; + t[6] = (1 << (d - 1)) - t0[8 * i + 6]; + t[7] = (1 << (d - 1)) - t0[8 * i + 7]; + + r[13 * i] = t[0] as u8; + r[13 * i + 1] = (t[0] >> 8) as u8; + r[13 * i + 1] |= (t[1] << 5) as u8; + r[13 * i + 2] = (t[1] >> 3) as u8; + r[13 * i + 3] = (t[1] >> 11) as u8; + r[13 * i + 3] |= (t[2] << 2) as u8; + r[13 * i + 4] = (t[2] >> 6) as u8; + r[13 * i + 4] |= (t[3] << 7) as u8; + r[13 * i + 5] = (t[3] >> 1) as u8; + r[13 * i + 6] = (t[3] >> 9) as u8; + r[13 * i + 6] |= (t[4] << 4) as u8; + r[13 * i + 7] = (t[4] >> 4) as u8; + r[13 * i + 8] = (t[4] >> 12) as u8; + r[13 * i + 8] |= (t[5] << 1) as u8; + r[13 * i + 9] = (t[5] >> 7) as u8; + r[13 * i + 9] |= (t[6] << 6) as u8; + r[13 * i + 10] = (t[6] >> 2) as u8; + r[13 * i + 11] = (t[6] >> 10) as u8; + r[13 * i + 11] |= (t[7] << 3) as u8; + r[13 * i + 12] = (t[7] >> 5) as u8; + } + + r +} + +/// A variant of Algorithm 17 specific to packing z in the signature value in \[−𝛾1 + 1, 𝛾1]. +/// +/// The destination is a slice rather than a `P::PolyZPacked` so that a caller can write straight +/// into its window of a signature buffer, which is chunked at runtime because the chunk size +/// `P::POLY_Z_PACKED_LEN` cannot be a const generic argument. See [`bitpack_gamma1`] for the form +/// that returns the buffer. +pub fn bitpack_gamma1_out(z: &Polynomial, out: &mut [u8]) { + debug_assert_eq!(out.len(), P::POLY_Z_PACKED_LEN); + out.fill(0); + + let mut t: [u32; 4] = [0; 4]; + // One layout per distinct 𝛾1 rather than one per parameter set; `P::gamma1` is a constant + // after monomorphization, so only the matching arm survives. + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { + for i in 0..N / 4 { + t[0] = (P::gamma1 - z[4 * i]) as u32; + t[1] = (P::gamma1 - z[4 * i + 1]) as u32; + t[2] = (P::gamma1 - z[4 * i + 2]) as u32; + t[3] = (P::gamma1 - z[4 * i + 3]) as u32; + + out[9 * i] = t[0] as u8; + out[9 * i + 1] = (t[0] >> 8) as u8; + out[9 * i + 2] = ((t[0] >> 16) | (t[1] << 2)) as u8; + out[9 * i + 3] = (t[1] >> 6) as u8; + out[9 * i + 4] = ((t[1] >> 14) | (t[2] << 4)) as u8; + out[9 * i + 5] = (t[2] >> 4) as u8; + out[9 * i + 6] = ((t[2] >> 12) | (t[3] << 6)) as u8; + out[9 * i + 7] = (t[3] >> 2) as u8; + out[9 * i + 8] = (t[3] >> 10) as u8; + } + } + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { + for i in 0..N / 2 { + t[0] = (P::gamma1 - z[2 * i]) as u32; + t[1] = (P::gamma1 - z[2 * i + 1]) as u32; + + out[5 * i] = t[0] as u8; + out[5 * i + 1] = (t[0] >> 8) as u8; + out[5 * i + 2] = ((t[0] >> 16) | (t[1] << 4)) as u8; + out[5 * i + 3] = (t[1] >> 4) as u8; + out[5 * i + 4] = (t[1] >> 12) as u8; + } + } + _ => { + panic!("Invalid gamma1 value") + } + } +} + +/// [`bitpack_gamma1_out`] into a fresh `P::PolyZPacked`. +#[must_use] +pub fn bitpack_gamma1(z: &Polynomial) -> P::PolyZPacked { + let mut out = ::ZEROED; + bitpack_gamma1_out::

(z, out.as_mut()); + out +} + +/// A specific instantiation of Algorithm 18 SimpleBitUnpack(v, 𝑏) with the constants set for unpacking the t1 vector +/// Input: 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen 𝑏. +/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [0, 2𝑐 − 1], where 𝑐 = bitlen 𝑏. +/// When 𝑏 + 1 is a power of 2, the coefficients are in [0, 𝑏]. +pub fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { + let mut w = Polynomial::new(); + + for i in 0..N / 4 { + w[4 * i] = ((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) & 0x3FF; + w[4 * i + 1] = (((v[5 * i + 1] as i32) >> 2) | ((v[5 * i + 2] as i32) << 6)) & 0x3FF; + w[4 * i + 2] = (((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) & 0x3FF; + w[4 * i + 3] = (((v[5 * i + 3] as i32) >> 6) | ((v[5 * i + 4] as i32) << 2)) & 0x3FF; + } + + w +} + +/// A variant of Algorithm 19 BitUnpack specific to a=eta, b=eta, writing into `w`. +/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). +/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). +/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. +/// +/// Note: caller is responsible for ensuring correct input array size. See [`bit_unpack_eta`] for +/// the form that returns the polynomial. +// `match P::eta` folds away per monomorphization, so 𝜂 = 2 and 𝜂 = 4 each compile to just their +// own arm, leaving no dispatch at runtime. +#[inline(always)] +pub fn bit_unpack_eta_out(v: &[u8], w: &mut Polynomial) { + debug_assert_eq!(v.len(), P::POLY_ETA_PACKED_LEN); + + match P::eta { + // MLDSA44 and MLDSA87 + 2 => { + let eta: i32 = 2; + for i in 0..N / 8 { + w[8 * i] = (v[3 * i] & 7) as i32; + w[8 * i + 1] = ((v[3 * i] >> 3) & 7) as i32; + w[8 * i + 2] = ((v[3 * i] >> 6) | (v[3 * i + 1] << 2) & 7) as i32; + w[8 * i + 3] = ((v[3 * i + 1] >> 1) & 7) as i32; + w[8 * i + 4] = ((v[3 * i + 1] >> 4) & 7) as i32; + w[8 * i + 5] = ((v[3 * i + 1] >> 7) | (v[3 * i + 2] << 1) & 7) as i32; + w[8 * i + 6] = ((v[3 * i + 2] >> 2) & 7) as i32; + w[8 * i + 7] = ((v[3 * i + 2] >> 5) & 7) as i32; + + w[8 * i] = eta - w[8 * i]; + w[8 * i + 1] = eta - w[8 * i + 1]; + w[8 * i + 2] = eta - w[8 * i + 2]; + w[8 * i + 3] = eta - w[8 * i + 3]; + w[8 * i + 4] = eta - w[8 * i + 4]; + w[8 * i + 5] = eta - w[8 * i + 5]; + w[8 * i + 6] = eta - w[8 * i + 6]; + w[8 * i + 7] = eta - w[8 * i + 7]; + } + } + // MLDSA65 + 4 => { + let eta: i32 = 4; + for i in 0..N / 2 { + w[2 * i] = (v[i] & 0x0F) as i32; + w[2 * i + 1] = (v[i] >> 4) as i32; + + w[2 * i] = eta - w[2 * i]; + w[2 * i + 1] = eta - w[2 * i + 1]; + } + } + _ => panic!("Invalid eta value"), + } +} + +/// [`bit_unpack_eta_out`] into a fresh polynomial. +#[inline(always)] +#[must_use] +pub fn bit_unpack_eta(v: &[u8]) -> Polynomial { + let mut w = Polynomial::new(); + bit_unpack_eta_out::

(v, &mut w); + w +} + +/// A variant of Algorithm 19 BitUnpack specific to unpacking the t0 polynomial with a=2^{d-1}-1, b=2^{d-1} +/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). +/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). +/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. +pub fn bit_unpack_t0(a: &[u8; POLY_T0PACKED_LEN]) -> Polynomial { + let mut t0 = Polynomial::new(); + + for i in 0..N / 8 { + t0[8 * i] = ((a[13 * i] as i32) | ((a[13 * i + 1] as i32) << 8)) & 0x1FFF; + t0[8 * i + 1] = ((((a[13 * i + 1] as i32) >> 5) | (a[13 * i + 2] as i32) << 3) + | ((a[13 * i + 3] as i32) << 11)) + & 0x1FFF; + t0[8 * i + 2] = (((a[13 * i + 3] as i32) >> 2) | ((a[13 * i + 4] as i32) << 6)) & 0x1FFF; + t0[8 * i + 3] = ((((a[13 * i + 4] as i32) >> 7) | (a[13 * i + 5] as i32) << 1) + | ((a[13 * i + 6] as i32) << 9)) + & 0x1FFF; + t0[8 * i + 4] = ((((a[13 * i + 6] as i32) >> 4) | (a[13 * i + 7] as i32) << 4) + | ((a[13 * i + 8] as i32) << 12)) + & 0x1FFF; + t0[8 * i + 5] = (((a[13 * i + 8] as i32) >> 1) | ((a[13 * i + 9] as i32) << 7)) & 0x1FFF; + t0[8 * i + 6] = ((((a[13 * i + 9] as i32) >> 6) | (a[13 * i + 10] as i32) << 2) + | ((a[13 * i + 11] as i32) << 10)) + & 0x1FFF; + t0[8 * i + 7] = (((a[13 * i + 11] as i32) >> 3) | ((a[13 * i + 12] as i32) << 5)) & 0x1FFF; + + t0[8 * i] = (1 << (d - 1)) - t0[8 * i]; + t0[8 * i + 1] = (1 << (d - 1)) - t0[8 * i + 1]; + t0[8 * i + 2] = (1 << (d - 1)) - t0[8 * i + 2]; + t0[8 * i + 3] = (1 << (d - 1)) - t0[8 * i + 3]; + t0[8 * i + 4] = (1 << (d - 1)) - t0[8 * i + 4]; + t0[8 * i + 5] = (1 << (d - 1)) - t0[8 * i + 5]; + t0[8 * i + 6] = (1 << (d - 1)) - t0[8 * i + 6]; + t0[8 * i + 7] = (1 << (d - 1)) - t0[8 * i + 7]; + } + + t0 +} + +/// A variant of Algorithm 19 BitUnpack specific to a=𝛾1 − 1, b=𝛾1 +/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). +/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). +/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. +/// +/// Note: caller is responsible for ensuring correct input array size +// `match P::gamma1` folds away per monomorphization, so each 𝛾1 compiles to just its own arm, +// leaving no dispatch at runtime. +#[inline(always)] +pub fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { + let mut w = Polynomial::new(); + + match P::gamma1 { + // MLDSA-44 + GAMMA1_2_POW_17 => { + for i in 0..N / 4 { + w[4 * i] = (((v[9 * i] as i32) | ((v[9 * i + 1] as i32) << 8)) + | ((v[9 * i + 2] as i32) << 16)) + & 0x3FFFF; + w[4 * i + 1] = ((((v[9 * i + 2] as i32) >> 2) | ((v[9 * i + 3] as i32) << 6)) + | ((v[9 * i + 4] as i32) << 14)) + & 0x3FFFF; + w[4 * i + 2] = ((((v[9 * i + 4] as i32) >> 4) | ((v[9 * i + 5] as i32) << 4)) + | ((v[9 * i + 6] as i32) << 12)) + & 0x3FFFF; + w[4 * i + 3] = ((((v[9 * i + 6] as i32) >> 6) | ((v[9 * i + 7] as i32) << 2)) + | ((v[9 * i + 8] as i32) << 10)) + & 0x3FFFF; + + w[4 * i] = P::gamma1 - w[4 * i]; + w[4 * i + 1] = P::gamma1 - w[4 * i + 1]; + w[4 * i + 2] = P::gamma1 - w[4 * i + 2]; + w[4 * i + 3] = P::gamma1 - w[4 * i + 3]; + } + } + // MLDSA-65 and -87 have the same GAMMA1 value + GAMMA1_2_POW_19 => { + for i in 0..N / 2 { + w[2 * i] = (((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) + | ((v[5 * i + 2] as i32) << 16)) + & 0xFFFFF; + w[2 * i + 1] = ((((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) + | ((v[5 * i + 4] as i32) << 12)) + & 0xFFFFF; + + w[2 * i] = P::gamma1 - w[2 * i]; + w[2 * i + 1] = P::gamma1 - w[2 * i + 1]; + } + } + _ => { + panic!("Invalid gamma1 value") + } + }; + + w +} + +/// Algorithm 29 SampleInBall(𝜌) +/// Samples a polynomial 𝑐 ∈ 𝑅 with coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. +/// Input: A seed 𝜌 ∈ 𝔹𝜆/4 +/// Output: A polynomial 𝑐 in 𝑅. +pub fn sample_in_ball(rho: &P::SigCTilde) -> Polynomial { + // 1: 𝑐 ← 0 + let mut c = Polynomial::new(); + + // 2: ctx ← H.Init() + // 3: ctx ← H.Absorb(ctx, 𝜌) + // 4: (ctx, 𝑠) ← H.Squeeze(ctx, 8) + let mut h = H::new(); + h.do_update(rho.as_ref()); + let mut s = [0u8; 8]; + let mut h = h.into_squeezer(); + h.do_output_out(&mut s); + + // 5: ℎ ← BytesToBits(𝑠) + // ▷ ℎ is a bit string of length 64 + let mut signs: u64 = 0; + for (i, item) in s.iter().enumerate().take(8) { + signs |= (*item as u64) << (8 * i); + } + + // 6: for 𝑖 from 256 − 𝜏 to 255 do + let mut j = [0u8]; + for i in (N - P::tau as usize)..N { + // 7: (ctx, 𝑗) ← H.Squeeze(ctx, 1) + // Note: Even though it may appear that pre-squeezing a buffer outside the loop would be faster, + // testing it both ways doesn't make a noticeable difference, so this has been left as is + // for better correspondence with the FIPS sample algorithm. + h.do_output_out(&mut j); + + // 8: while 𝑗 > 𝑖 do + while j[0] as usize > i { + // ▷ rejection sampling in {0, … , 𝑖} + // 9: (ctx, 𝑗) ← H.Squeeze(ctx, 1) + h.do_output_out(&mut j); + } + + // 11: 𝑐𝑖 ← 𝑐𝑗 + c[i] = c[j[0] as usize]; + + // 12: 𝑐𝑗 ← (−1)^ℎ[𝑖+𝜏−256] + c[j[0] as usize] = (1u64.wrapping_sub(2 * (signs & 1))) as i32; + signs >>= 1; + } + + // check post-condition + // coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. + #[cfg(debug_assertions)] + { + let mut hamming_weight: i32 = 0; + for i in 0..N { + debug_assert!((-1..=1).contains(&c[i])); + if c[i] != 0 { + hamming_weight += 1; + } + } + debug_assert!(hamming_weight > 0); + debug_assert!(hamming_weight <= 64); + } + + c +} + +/// Algorithm 30 RejNTTPoly(𝜌) +/// This is supposed to take a rho: `[u8; 34]`, which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) +/// but to avoid needing to copy bytes and allocate more memory, +/// that is split into a `[u8; 32]` and a `[u8; 2]` +pub fn rej_ntt_poly(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { + let mut w_hat = Polynomial::new(); + let mut j: usize = 0; + let mut g = G::new(); + g.do_update(rho); + g.do_update(nonce); + + // SHAKE is fairly inefficient if only 3 bytes are squeezed at a time, so instead this implementation does a block. + // Size is not a limitation, so long as it's a multiple of 3. + // 288 seemed to be the sweet spot found during experimentation and benchmarking. + // It's probably around the average rejection rate, and 288 is a multiple of both 3 (required for this alg) + // and 8 (efficient for SHAKE). + let mut s = [0u8; 288]; + let mut g = g.into_squeezer(); + g.do_output_out(&mut s); + let mut idx: usize = 0; + + while j < N { + if idx == s.len() { + g.do_output_out(&mut s); + idx = 0; + } + w_hat[j] = match coeff_from_three_bytes(&s[idx..idx + 3].try_into().unwrap()) { + Some(c) => c, + None => { + // those three bytes were out of range for a coefficient, so go again with the next three bytes + // from the SHAKE stream. + idx += 3; + continue; + } + }; + idx += 3; + j += 1; + } + + w_hat +} + +/// Algorithm 31 RejBoundedPoly(𝜌) +/// Samples an element 𝑎 ∈ 𝑅 with coefficients in \[−𝜂, 𝜂\] computed via rejection sampling from 𝜌. +/// Input: A seed 𝜌 ∈ 𝔹66 . +/// Output: A polynomial 𝑎 ∈ 𝑅. +/// +/// This is supposed to take a rho: `[u8; 66]`, which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) +/// but to avoid needing to copy bytes and allocate more memory, +/// that is split into a `[u8; 64]` and a `[u8; 2]` +pub fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { + let mut a = Polynomial::new(); + let mut j: usize = 0; + let mut h = H::new(); + h.do_update(rho); + h.do_update(nonce); + + // size doesn't really matter + // 312 seemed to be the sweet spot from playing with benchmarks + // maybe something to do with the average rejection rate? + // Also, 312 is a multiple of 8 (efficient for SHAKE) + let mut z_arr = [0u8; 312]; + let mut h = h.into_squeezer(); + h.do_output_out(&mut z_arr); + let mut idx: usize = 0; + + while j < N { + let z0 = coeff_from_half_byte::

(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) + let z1 = coeff_from_half_byte::

(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) + + if let Some(z0) = z0 { + a[j] = z0; + j += 1; + } /* else: do nothing */ + if j < 256 + && let Some(z1) = z1 + { + a[j] = z1; + j += 1; + } /* else: do nothing */ + + idx += 1; + if idx == z_arr.len() { + h.do_output_out(&mut z_arr); + idx = 0; + } + } + + a +} + +/// One coordinate of Algorithm 34 ExpandMask(𝜌, 𝜇): the polynomial 𝐲\[𝑟] for `nonce` = 𝜇 + 𝑟, +/// with coefficients between −𝛾1 + 1 and 𝛾1. +/// Input: A seed 𝜌 ∈ 𝔹64 and a nonnegative integer 𝜇 + 𝑟. +/// Output: One polynomial of 𝐲 ∈ 𝑅ℓ. +pub fn expand_mask_poly(rho: &[u8; 64], nonce: u16) -> Polynomial { + // 1: 𝑐 ← 1 + bitlen (𝛾1 − 1) + // ▷ 𝛾1 is always a power of 2 + // 3: 𝜌′ ← 𝜌||IntegerToBytes(𝜇 + 𝑟, 2) + // 4: 𝑣 ← H(𝜌′, 32𝑐) + // The 32𝑐 bytes squeezed on line 4 are exactly `P::POLY_Z_PACKED_LEN`, so the buffer for them + // is `P::PolyZPacked`; see the docs on `MLDSAParamsBase::POLY_Z_PACKED_LEN`. + let mut h = H::new(); + h.do_update(rho); + h.do_update(&nonce.to_le_bytes()); + let mut v = ::ZEROED; + let mut h = h.into_squeezer(); + h.do_output_out(v.as_mut()); + + // 5: 𝐲[𝑟] ← BitUnpack(𝑣, 𝛾1 − 1, 𝛾1) + bit_unpack_gamma1::

(v.as_ref()) +} + +/// Algorithm 35 Power2Round(𝑟) +/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. +/// Input: 𝑟 ∈ ℤ𝑞. +/// Output: Integers (𝑟1, 𝑟0). +pub fn power_2_round(r: i32) -> (i32, i32) { + const u: i32 = (1 << (d - 1)) - 1; + const v: i32 = -1 << d; + + let t = r + u; + let r0 = r - (t & v); + + (t >> d, r0) +} + +/// Algorithm 36 Decompose(𝑟) +/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1(2𝛾2) + 𝑟0 mod 𝑞. +/// Input: 𝑟 ∈ ℤ𝑞. +/// Output: Integers (𝑟1, 𝑟0). +// `match P::gamma2` folds away per monomorphization, so each 𝛾2 compiles to just its own arm, +// leaving no dispatch at runtime. +#[inline(always)] +pub fn decompose(r: i32) -> (i32, i32) { + // 1: 𝑟+ ← 𝑟 mod 𝑞 + // 2: 𝑟0 ← 𝑟+ mod±(2𝛾2) + // 3: if 𝑟+ − 𝑟0 = 𝑞 − 1 then + // 4: 𝑟1 ← 0 + // 5: 𝑟0 ← 𝑟0 − 1 + // 6: else 𝑟1 ← (𝑟+ − 𝑟0)/(2𝛾2) + // 7: end if + // 8: return (𝑟1, 𝑟0) + + // The branch-free decompose() of the reference implementation (pq-crystals/dilithium, + // ref/rounding.c), followed line for line. r1 is round(r / 2γ2) by a fixed-point multiply, and + // the value one past the top (44 for γ2 = (q-1)/88, 16 for (q-1)/32, reached only when r+ is + // within γ2 of q-1) is sent to 0: steps 3-4. r0 = r - r1 * 2γ2 is then moved into (-γ2, γ2] by + // subtracting q when it exceeds (q-1)/2, which is step 2's mod± and, in the wrapped case where + // r0 is still r+, the -1 of step 5 (r+ - q = (r+ - (q-1)) - 1). + let mut r1 = (r + 127) >> 7; + + match P::gamma2 { + // ML-DSA-44 + GAMMA2_Q_MINUS_1_OVER_88 => { + r1 = (r1 * 11275 + (1 << 23)) >> 24; + r1 ^= ((43 - r1) >> 31) & r1; + } + // ML-DSA-65 and -87 have the same gamma2 + GAMMA2_Q_MINUS_1_OVER_32 => { + r1 = (r1 * 1025 + (1 << 21)) >> 22; + r1 &= 15; + } + _ => { + // this branch should never compile + unimplemented!() + } + } + + let mut r0 = r - r1 * 2 * P::gamma2; + + // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, + // this seems to work out mathematically equivalent if doing q/2, or (q+3)/2, but here it is left as (q-1)/2 + // since that's algorithmically correct, and just ignore the mutants results. + r0 -= (((q - 1) / 2 - r0) >> 31) & q; + + (r1, r0) +} + +/// Algorithm 37 HighBits(𝑟) +/// Returns 𝑟1 from the output of Decompose (𝑟). +/// Input: 𝑟 ∈ ℤ𝑞. +/// Output: Integer 𝑟1. +pub fn high_bits(r: i32) -> i32 { + // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) + // 2: return 𝑟1 + let (r1, _) = decompose::

(r); + r1 +} + +/// Algorithm 38 LowBits(𝑟) +/// Returns 𝑟0 from the output of Decompose (𝑟). +/// Input: 𝑟 ∈ ℤ𝑞. +/// Output: Integer 𝑟0. +pub fn low_bits(r: i32) -> i32 { + // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) + // 2: return 𝑟0 + let (_, r0) = decompose::

(r); + r0 +} + +/// Algorithm 39 MakeHint(𝑧, 𝑟) +/// Computes hint bit indicating whether adding 𝑧 to 𝑟 alters the high bits of 𝑟. +/// Input: 𝑧, 𝑟 ∈ ℤ𝑞. +/// Output: Boolean. +/// +/// Takes, in place of Algorithm 39's z and r, the two parts the reference implementation's +/// make_hint(a0, a1) works from (pq-crystals/dilithium, ref/rounding.c): `low` is +/// LowBits(w - cs2) + ct0 as a residue in [0, q), and `high` is HighBits(w). The hint is 1 exactly +/// when adding ct0 carries the low part out of (-γ2, γ2], except that landing on -γ2 itself only +/// changes the high bits when they are nonzero. `low <= γ2 || low > q - γ2` is mod± low in +/// [-γ2 + 1, γ2], and `low == q - γ2` is mod± low == -γ2. +pub fn make_hint(low: i32, high: i32) -> i32 { + // Naïve implementation: + // // 1: 𝑟1 ← HighBits(𝑟) + // let r1 = high_bits::

(r); + // + // // 2: 𝑣1 ← HighBits(𝑟 + 𝑧) + // let v1 = high_bits::

(r + z); + // + // // 3: return [[𝑟1 ≠ 𝑣1]] + // if r1 != v1 { 1 } else { 0 } + + // mutants note: we do not have KATs that exercise all branches of this if + if low <= P::gamma2 || low > q - P::gamma2 || (low == q - P::gamma2 && high == 0) { + 0 + } else { + 1 + } +} + +/// Algorithm 40 UseHint(ℎ, 𝑟) +/// Returns the high bits of 𝑟 adjusted according to hint ℎ. +/// Input: Boolean ℎ, 𝑟 ∈ ℤ𝑞. +/// Output: 𝑟1 ∈ ℤ with 0 ≤ 𝑟1 ≤ (𝑞−1) / 2*gamma2). +pub fn use_hint(r: i32, hint: i32) -> i32 { + // 1: 𝑚 ← (𝑞 − 1)/(2𝛾2) + // m is 44 for γ2 = (q-1)/88 and 16 for (q-1)/32; the "mod m" below is the wrap at the ends, + // or `& 15` when m is 16. + // 2: (𝑟1, 𝑟0) ← Decompose(𝑟) + let (r1, r0) = decompose::

(r); + + // 5: return 𝑟1 + if hint == 0 { + return r1; + } + + debug_assert!(hint == 1); + + // 3: if ℎ = 1 and 𝑟0 > 0 return (𝑟1 + 1) mod 𝑚 + // 4: if ℎ = 1 and 𝑟0 ≤ 0 return (𝑟1 − 1) mod 𝑚 + match P::gamma2 { + // ML-DSA-44 + GAMMA2_Q_MINUS_1_OVER_88 => { + // mutants note: this passes unit tests if it's r0 >= 0 + // it is left like this because it matches the spec + if r0 > 0 { + if r1 == 43 { 0 } else { r1 + 1 } + } else if r1 == 0 { + 43 + } else { + r1 - 1 + } + } + // ML-DSA-65 and -87 have the same gamma2 + GAMMA2_Q_MINUS_1_OVER_32 => { + // mutants note: this passes unit tests if it's r0 >= 0 + // it is left like this because it matches the spec + if r0 > 0 { (r1 + 1) & 15 } else { (r1 - 1) & 15 } + } + _ => { + panic!("Invalid GAMMA2 value") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + // FIPS 204 describes the output as easy to check: + // Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. + fn test_power_2_round() { + test(1); + test(q - 3); + test(q); + test(q + 3); + + fn test(r: i32) { + let (r1, r0) = power_2_round(r); + let mut res = ((r1 << d) + r0) % q; + if res < 0 { + res += q; + } + assert_eq!(r % q, res); + } + } +} diff --git a/crypto/mldsa-common/src/lib.rs b/crypto/mldsa-common/src/lib.rs new file mode 100644 index 000000000..c8f1ca182 --- /dev/null +++ b/crypto/mldsa-common/src/lib.rs @@ -0,0 +1,97 @@ +//! The arithmetic, encodings and parameters that `bouncycastle-mldsa` and +//! `bouncycastle-mldsa-lowmemory` share. +//! +//! This is not an API for callers, and every item in it is `#[doc(hidden)]`. The two ML-DSA crates +//! differ in how they hold a private key and in how they schedule FIPS 204's algorithms, but they +//! agree on the ring arithmetic, the samplers, the bit packing, the rounding and the parameter +//! tables; this crate holds that one copy. Use one of those two crates. +//! +//! # 🚨 Security Considerations 🚨 +//! +//! Nothing here checks the lengths it is handed beyond debug assertions, and nothing here is a +//! complete signature operation. The security considerations of ML-DSA are documented on the two +//! crates that build on this one. + +#![no_std] +#![forbid(unsafe_code)] +#![forbid(missing_docs)] +// Names follow FIPS 204, where for example 'K' and 'k', or 'A' and 'a', are different things. +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] + +#[doc(hidden)] +pub mod aux_functions; +#[doc(hidden)] +pub mod mu_builder; +#[doc(hidden)] +pub mod params; +#[doc(hidden)] +pub mod polynomial; + +use bouncycastle_sha3::{SHAKE128, SHAKE256}; + +/* FIPS 204, Table 1: the values that are the same for all parameter sets. */ + +/// The degree of the ring polynomial, 256. +#[doc(hidden)] +pub const N: usize = 256; +/// The modulus 𝑞 = 2^23 − 2^13 + 1. +#[doc(hidden)] +pub const q: i32 = 8380417; +/// 𝑞^(−1) mod 2^32, for Montgomery reduction. +#[doc(hidden)] +pub const q_inv: i32 = 58728449; +/// 𝑑, the number of dropped bits from 𝐭. +#[doc(hidden)] +pub const d: i32 = 13; + +/// Length of the \[u8] holding an ML-DSA seed value. +#[doc(hidden)] +pub const MLDSA_SEED_LEN: usize = 32; +/// Length of the \[u8] holding an ML-DSA signing random value. +#[doc(hidden)] +pub const MLDSA_RND_LEN: usize = 32; +/// Length of the \[u8] holding an ML-DSA tr value (which is the SHAKE256 hash of the public key). +#[doc(hidden)] +pub const MLDSA_TR_LEN: usize = 64; +/// Length of the \[u8] holding an ML-DSA mu value. +#[doc(hidden)] +pub const MLDSA_MU_LEN: usize = 64; + +/// The packed length of one coordinate of 𝐭0: 32 ⋅ 𝑑 bytes (FIPS 204, Algorithm 24, line 7). +#[doc(hidden)] +pub const POLY_T0PACKED_LEN: usize = 416; +/// The packed length of one coordinate of 𝐭1: 32 ⋅ (bitlen (𝑞 − 1) − 𝑑) bytes (FIPS 204, Algorithm 22). +#[doc(hidden)] +pub const POLY_T1PACKED_LEN: usize = 320; + +// Typedefs just to make the algorithms look more like the FIPS 204 sample code. +/// H, the SHAKE256 XOF of FIPS 204, Section 3.7. +#[doc(hidden)] +pub type H = SHAKE256; +/// G, the SHAKE128 XOF of FIPS 204, Section 3.7. +#[doc(hidden)] +pub type G = SHAKE128; + +/// Algorithm name of ML-DSA-44, as used by the CLI and `Algorithm::ALG_NAME`. +#[doc(hidden)] +pub const ML_DSA_44_NAME: &str = "ML-DSA-44"; +/// Algorithm name of ML-DSA-65. +#[doc(hidden)] +pub const ML_DSA_65_NAME: &str = "ML-DSA-65"; +/// Algorithm name of ML-DSA-87. +#[doc(hidden)] +pub const ML_DSA_87_NAME: &str = "ML-DSA-87"; + +/// Algorithm name of HashML-DSA-44 with SHA-256, as used by the CLI and `Algorithm::ALG_NAME`. +#[doc(hidden)] +pub const HASH_ML_DSA_44_with_SHA256_NAME: &str = "HashML-DSA-44_with_SHA256"; +/// Algorithm name of HashML-DSA-44 with SHA-512. +#[doc(hidden)] +pub const HASH_ML_DSA_44_with_SHA512_NAME: &str = "HashML-DSA-44_with_SHA512"; +/// Algorithm name of HashML-DSA-65 with SHA-512. +#[doc(hidden)] +pub const HASH_ML_DSA_65_with_SHA512_NAME: &str = "HashML-DSA-65_with_SHA512"; +/// Algorithm name of HashML-DSA-87 with SHA-512. +#[doc(hidden)] +pub const HASH_ML_DSA_87_with_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; diff --git a/crypto/mldsa-common/src/mu_builder.rs b/crypto/mldsa-common/src/mu_builder.rs new file mode 100644 index 000000000..57f0cf061 --- /dev/null +++ b/crypto/mldsa-common/src/mu_builder.rs @@ -0,0 +1,142 @@ +//! The message representative 𝜇 of FIPS 204, computed incrementally. + +use crate::{H, MLDSA_MU_LEN, MLDSA_TR_LEN}; +use bouncycastle_core::errors::{SignatureError, SuspendableError}; +use bouncycastle_core::traits::{Hash, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_sha3::SUSPENDED_SHA3_STATE_LEN; + +/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204. +/// Provides a stateful version of `MLDSATrait::compute_mu_from_pk` and `MLDSATrait::compute_mu_from_tr` +/// that supports streaming large to-be-signed messages. +/// +/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA +/// does not benefit from allowing external construction of the message representative mu. +/// The same behaviour can be obtained by computing the pre-hash `ph` with the appropriate hash function +/// and providing that to HashMLDSA via `PHSigner::sign_ph`. +#[derive(Clone)] +pub struct MuBuilder { + h: H, +} + +impl MuBuilder { + /// Algorithm 7 + /// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀′, 64) + pub fn compute_mu( + tr: &[u8; MLDSA_TR_LEN], + msg: &[u8], + ctx: Option<&[u8]>, + ) -> Result<[u8; MLDSA_MU_LEN], SignatureError> { + let mut mu_builder = MuBuilder::do_init(tr, ctx)?; + mu_builder.do_update(msg); + let mu = mu_builder.do_final(); + + Ok(mu) + } + + /// The HashML-DSA message representative: Algorithm 7, line 6, over the 𝑀′ of Algorithm 4, + /// line 23, which carries the pre-hash `ph` and the DER-encoded OID of the hash that produced + /// it. `ctx` longer than 255 bytes is the error of Algorithm 4, line 1 (and Algorithm 5, + /// line 1). + pub fn compute_mu_ph( + tr: &[u8; MLDSA_TR_LEN], + ctx: Option<&[u8]>, + oid_der: &[u8], + ph: &[u8], + ) -> Result<[u8; MLDSA_MU_LEN], SignatureError> { + let ctx = ctx.unwrap_or(&[]); + + // Algorithm 4 + // 1: if |𝑐𝑡𝑥| > 255 then + if ctx.len() > 255 { + return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); + } + + // Algorithm 7 + // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) + let mut h = H::new(); + h.do_update(tr); + + // Algorithm 4 + // 23: 𝑀' ← BytesToBits(IntegerToBytes(1, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ OID ∥ PH𝑀) + // all done together + h.do_update(&[1u8]); + h.do_update(&[ctx.len() as u8]); + h.do_update(ctx); + h.do_update(oid_der); + h.do_update(ph); + + let mut mu = [0u8; MLDSA_MU_LEN]; + let bytes_written = h.into_squeezer().do_output_out(&mut mu); + debug_assert_eq!(bytes_written, MLDSA_MU_LEN); + + Ok(mu) + } + + /// This function requires the public key hash `tr`, which can be computed from the public key + /// using `MLDSAPublicKeyTrait::compute_tr`. + pub fn do_init(tr: &[u8; MLDSA_TR_LEN], ctx: Option<&[u8]>) -> Result { + let ctx = match ctx { + Some(ctx) => ctx, + None => &[], + }; + + // Algorithm 2 + // 1: if |𝑐𝑡𝑥| > 255 then + if ctx.len() > 255 { + return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); + } + + // Algorithm 7 + // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) + let mut mb = Self { h: H::new() }; + mb.h.do_update(tr); + + // Algorithm 2 + // 10: 𝑀′ ← BytesToBits(IntegerToBytes(0, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥) ∥ 𝑀 + // all done together + mb.h.do_update(&[0u8]); + mb.h.do_update(&[ctx.len() as u8]); + mb.h.do_update(ctx); + + // now ready to absorb M + Ok(mb) + } + + /// Stream a chunk of the message. + pub fn do_update(&mut self, msg_chunk: &[u8]) { + self.h.do_update(msg_chunk); + } + + /// Finalize and return the mu value. + pub fn do_final(self) -> [u8; MLDSA_MU_LEN] { + // Completion of + // Algorithm 7 + // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀 ′, 64) + let mut mu = [0u8; MLDSA_MU_LEN]; + self.h.into_squeezer().do_output_out(&mut mu); + + mu + } +} + +/// The length, in bytes, of a serialized state of a [`MuBuilder`] object. +pub const SUSPENDED_MU_BUILDER_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; + +/// If you are processing a large input message into ML-DSA and want to pause the operation +/// -- maybe while waiting for slow network IO), you'll need to use [`Suspendable`]. +/// Serialization of the state of an in-progress ML-DSA instance is really just serialization +/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm +/// has a pausable state. +// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, +// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. +impl Suspendable for MuBuilder { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + self.h.suspend() + } + + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], + ) -> Result { + Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) + } +} diff --git a/crypto/mldsa-common/src/params.rs b/crypto/mldsa-common/src/params.rs new file mode 100644 index 000000000..c33c065d1 --- /dev/null +++ b/crypto/mldsa-common/src/params.rs @@ -0,0 +1,399 @@ +//! The three ML-DSA parameter sets of FIPS 204, Section 4, as a sealed trait with one type per set. +//! +//! [`MLDSAParamsBase`] holds what both ML-DSA crates read from a parameter set: the values of +//! Table 1 and Table 2, the algorithm metadata, and the byte-buffer types whose length they fix. +//! Each crate extends it with a trait of its own for the types only it needs (the vector and matrix +//! types in `bouncycastle-mldsa`, the packed-vector buffers in `bouncycastle-mldsa-lowmemory`), and +//! implements that extension for the three marker types defined here. +//! +//! # Derived parameters +//! +//! FIPS 204, Table 1 assigns eight independent values per set (𝜏, 𝜆, 𝛾1, 𝛾2, (𝑘, ℓ), 𝜂, 𝜔) plus the +//! three sizes of Table 2. Everything else the implementations need is a function of those, so it +//! is written once as a defaulted associated const rather than three times as a hand-computed +//! number. `params::tests` checks every derivation against the values tabulated in FIPS 204. + +use crate::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, q}; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_utils::secret::ZeroizablePrimitive; + +/// `bitlen 𝑥`, the length of the binary expansion of 𝑥 (FIPS 204, Section 2.3). +/// +/// `bitlen 0` is 0; every use below has a positive argument. +pub const fn bitlen(x: u32) -> usize { + if x == 0 { 0 } else { x.ilog2() as usize + 1 } +} + +/// A fixed-size byte buffer whose length depends on the parameter set. +/// +/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` +/// for arrays stops at 32 elements and every buffer here is longer than that. +pub trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} +impl ByteBuffer for [u8; N] {} + +mod sealed { + /// Prevents a new ML-DSA parameter set from being defined outside this crate: the three + /// marker types below are the only implementations, here and in the crates that extend + /// [`super::MLDSAParamsBase`]. + pub trait Sealed {} +} + +/// One ML-DSA parameter set: the values of FIPS 204, Table 1 and Table 2, and the byte buffers +/// whose size they determine. +/// +/// Sealed, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] are the only +/// implementations. +pub trait MLDSAParamsBase: sealed::Sealed { + /* FIPS 204, Table 1: the values assigned by each parameter set. */ + + /// 𝜏, the number of ±1's in the polynomial 𝑐. + const tau: i32; + /// 𝜆, the collision strength of 𝑐̃, in bits. + const lambda: i32; + /// 𝛾1, the coefficient range of 𝐲. Always a power of two. + const gamma1: i32; + /// 𝛾2, the low-order rounding range. + const gamma2: i32; + /// 𝑘, the number of rows of 𝐀. + const k: usize; + /// ℓ, the number of columns of 𝐀. + const l: usize; + /// 𝜂, the private key range. + const eta: usize; + /// 𝜔, the maximum number of 1's in the hint 𝐡. + const omega: i32; + + /* FIPS 204, Table 2: sizes in bytes of keys and signatures. */ + + /// The length of an encoded public key. + const PK_LEN: usize; + /// The length of the FIPS 204 encoding of a private key, the output of Algorithm 24 (skEncode). + /// + /// Named `FULL_SK_LEN` because a crate may store a private key in another form (the 32-byte + /// seed 𝜉, in `bouncycastle-mldsa-lowmemory`) and reserve `SK_LEN` for what it stores. + const FULL_SK_LEN: usize; + /// The length of a signature. + const SIG_LEN: usize; + + /* Algorithm meta-data */ + + /// The algorithm name, as reported by `Algorithm::ALG_NAME`. + const ALG_NAME: &'static str; + /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. + const MAX_SECURITY_STRENGTH: SecurityStrength; + /// The OID in component form, as reported by `AlgorithmOID::OID`. + const OID: &'static [u32]; + /// The DER encoding of [`MLDSAParamsBase::OID`], as reported by `AlgorithmOID::OID_DER`. + const OID_DER: &'static [u8]; + + /* Derived. Never written out per parameter set -- see the module docs. */ + + /// 𝛽, which FIPS 204, Table 1 defines as "𝛽 = 𝜏 ⋅ 𝜂". + const beta: i32 = Self::tau * Self::eta as i32; + + /// The length of the commitment hash 𝑐̃, which FIPS 204, Algorithm 26 (sigEncode) gives as + /// 𝑐̃ ∈ 𝔹^(𝜆/4). + const C_TILDE_LEN: usize = Self::lambda as usize / 4; + + /// The packed length of one coordinate of 𝐳: FIPS 204, Algorithm 26 (sigEncode) writes each of + /// the ℓ coordinates as 𝔹^(32⋅(1+bitlen (𝛾1−1))). + /// + /// This is also the number of bytes ExpandMask squeezes per coordinate: FIPS 204, + /// Algorithm 34, line 1 sets 𝑐 ← 1 + bitlen (𝛾1 − 1) and line 4 squeezes 32𝑐 bytes. + const POLY_Z_PACKED_LEN: usize = 32 * (1 + bitlen(Self::gamma1 as u32 - 1)); + + /// The packed length of one coordinate of 𝐬1 or 𝐬2: FIPS 204, Algorithm 24 (skEncode), line 3 + /// packs each with BitPack(𝐬1\[𝑖], 𝜂, 𝜂), and Algorithm 17 (BitPack) outputs + /// 𝔹^(32⋅bitlen (𝑎+𝑏)), so 32⋅bitlen (2𝜂). + const POLY_ETA_PACKED_LEN: usize = 32 * bitlen(2 * Self::eta as u32); + + /// The packed length of one coordinate of 𝐰1: FIPS 204, Algorithm 28 (w1Encode) outputs + /// 𝔹^(32𝑘⋅bitlen ((𝑞−1)/(2𝛾2)−1)) for all 𝑘 coordinates together. + const POLY_W1_PACKED_LEN: usize = 32 * bitlen(((q - 1) / (2 * Self::gamma2)) as u32 - 1); + + /// 𝛾1 − 𝛽, the rejection bound on ‖𝐳‖∞ (FIPS 204, Algorithm 7, line 23). + const gamma1_minus_beta: i32 = Self::gamma1 - Self::beta; + + /// 𝛾2 − 𝛽, the rejection bound on ‖𝐫0‖∞ (FIPS 204, Algorithm 7, line 23). + const gamma2_minus_beta: i32 = Self::gamma2 - Self::beta; + + /* Types whose size depends on the parameter set. */ + + /// The commitment hash 𝑐̃, of [`MLDSAParamsBase::C_TILDE_LEN`] bytes. + type SigCTilde: ByteBuffer; + /// One packed coordinate of 𝐳, of [`MLDSAParamsBase::POLY_Z_PACKED_LEN`] bytes. + /// + /// ExpandMask squeezes into a buffer of this same length; see + /// [`MLDSAParamsBase::POLY_Z_PACKED_LEN`]. + type PolyZPacked: ByteBuffer; + /// One packed coordinate of 𝐰1, of [`MLDSAParamsBase::POLY_W1_PACKED_LEN`] bytes. + type PolyW1Packed: ByteBuffer; +} + +/// The ML-DSA-44 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA44Params; +/// The ML-DSA-65 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA65Params; +/// The ML-DSA-87 parameter set (FIPS 204, Table 1). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MLDSA87Params; + +impl sealed::Sealed for MLDSA44Params {} +impl sealed::Sealed for MLDSA65Params {} +impl sealed::Sealed for MLDSA87Params {} + +impl MLDSAParamsBase for MLDSA44Params { + const tau: i32 = 39; + const lambda: i32 = 128; + const gamma1: i32 = 1 << 17; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 88; + const k: usize = 4; + const l: usize = 4; + const eta: usize = 2; + const omega: i32 = 80; + + const PK_LEN: usize = 1312; + const FULL_SK_LEN: usize = 2560; + const SIG_LEN: usize = 2420; + + const ALG_NAME: &'static str = ML_DSA_44_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; + + type SigCTilde = [u8; 32]; // 𝜆/4 = 128/4 + type PolyZPacked = [u8; 576]; // 32 * (1 + bitlen(2^17 - 1)) = 32 * 18 + type PolyW1Packed = [u8; 192]; // 32 * bitlen(44 - 1) = 32 * 6 +} + +impl MLDSAParamsBase for MLDSA65Params { + const tau: i32 = 49; + const lambda: i32 = 192; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 6; + const l: usize = 5; + const eta: usize = 4; + const omega: i32 = 55; + + const PK_LEN: usize = 1952; + const FULL_SK_LEN: usize = 4032; + const SIG_LEN: usize = 3309; + + const ALG_NAME: &'static str = ML_DSA_65_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; + + type SigCTilde = [u8; 48]; // 𝜆/4 = 192/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 +} + +impl MLDSAParamsBase for MLDSA87Params { + const tau: i32 = 60; + const lambda: i32 = 256; + const gamma1: i32 = 1 << 19; + // mutants note: because of the bitshifting, the "- 1" ends up not mattering. + const gamma2: i32 = (q - 1) / 32; + const k: usize = 8; + const l: usize = 7; + const eta: usize = 2; + const omega: i32 = 75; + + const PK_LEN: usize = 2592; + const FULL_SK_LEN: usize = 4896; + const SIG_LEN: usize = 4627; + + const ALG_NAME: &'static str = ML_DSA_87_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; + /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; + + type SigCTilde = [u8; 64]; // 𝜆/4 = 256/4 + type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 + type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 +} + +/// The two distinct values 𝛾1 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// The bit-packing routines have one layout per distinct 𝛾1, so they dispatch on these rather than +/// on the parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub const GAMMA1_2_POW_17: i32 = MLDSA44Params::gamma1; +/// See [`GAMMA1_2_POW_17`]. +pub const GAMMA1_2_POW_19: i32 = MLDSA65Params::gamma1; + +/// The two distinct values 𝛾2 takes across the three parameter sets (FIPS 204, Table 1). +/// +/// As with 𝛾1, the routines that depend on 𝛾2 have one form per distinct value rather than one per +/// parameter set. ML-DSA-65 and ML-DSA-87 share the second value. +pub const GAMMA2_Q_MINUS_1_OVER_88: i32 = MLDSA44Params::gamma2; +/// See [`GAMMA2_Q_MINUS_1_OVER_88`]. +pub const GAMMA2_Q_MINUS_1_OVER_32: i32 = MLDSA65Params::gamma2; + +/// The weaker of two security strengths. +/// +/// [`SecurityStrength`]'s discriminants are assigned in increasing order of strength, so comparing +/// them as integers orders them. A `const fn` because the strength of a HashML-DSA pairing is a +/// defaulted associated const. +pub const fn weaker_of(a: SecurityStrength, b: SecurityStrength) -> SecurityStrength { + if (a as u8) <= (b as u8) { a } else { b } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::d; + + /// One row of FIPS 204 Table 1: `(tau, lambda, gamma1, gamma2, k, l, eta, omega)`. + type Table1Row = (i32, i32, i32, i32, usize, usize, usize, i32); + + /// FIPS 204, Table 1, transcribed column by column: the eight values each parameter set + /// assigns. + const TABLE_1: [Table1Row; 3] = [ + (39, 128, 131072, (q - 1) / 88, 4, 4, 2, 80), + (49, 192, 524288, (q - 1) / 32, 6, 5, 4, 55), + (60, 256, 524288, (q - 1) / 32, 8, 7, 2, 75), + ]; + + /// FIPS 204, Table 2, transcribed row by row: `(private key, public key, signature)` in bytes. + const TABLE_2: [(usize, usize, usize); 3] = + [(2560, 1312, 2420), (4032, 1952, 3309), (4896, 2592, 4627)]; + + /// FIPS 204, Table 1 also tabulates 𝛽, which it labels "𝛽 = 𝜏 ⋅ 𝜂". + const TABLE_1_BETA: [i32; 3] = [78, 196, 120]; + + fn check_table_1(i: usize) { + let (tau, lambda, gamma1, gamma2, k, l, eta, omega) = TABLE_1[i]; + assert_eq!(P::tau, tau, "{}: 𝜏", P::ALG_NAME); + assert_eq!(P::lambda, lambda, "{}: 𝜆", P::ALG_NAME); + assert_eq!(P::gamma1, gamma1, "{}: 𝛾1", P::ALG_NAME); + assert_eq!(P::gamma2, gamma2, "{}: 𝛾2", P::ALG_NAME); + assert_eq!(P::k, k, "{}: 𝑘", P::ALG_NAME); + assert_eq!(P::l, l, "{}: ℓ", P::ALG_NAME); + assert_eq!(P::eta, eta, "{}: 𝜂", P::ALG_NAME); + assert_eq!(P::omega, omega, "{}: 𝜔", P::ALG_NAME); + assert_eq!(P::beta, TABLE_1_BETA[i], "{}: 𝛽 = 𝜏 ⋅ 𝜂", P::ALG_NAME); + } + + fn check_table_2(i: usize) { + let (full_sk_len, pk_len, sig_len) = TABLE_2[i]; + assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: private key size", P::ALG_NAME); + assert_eq!(P::PK_LEN, pk_len, "{}: public key size", P::ALG_NAME); + assert_eq!(P::SIG_LEN, sig_len, "{}: signature size", P::ALG_NAME); + } + + /// Each of the three sizes of Table 2 also has a formula in FIPS 204, and the two must agree. + /// Table 2 is what is written down above; this is what re-derives it. + fn check_table_2_formulas() { + // Algorithm 22 (pkEncode): 𝑝𝑘 ∈ 𝔹^(32+32𝑘(bitlen (𝑞−1)−𝑑)). + let pk_len = 32 + 32 * P::k * (bitlen((q - 1) as u32) - d as usize); + assert_eq!(P::PK_LEN, pk_len, "{}: Algorithm 22 output size", P::ALG_NAME); + + // Algorithm 24 (skEncode): 𝑠𝑘 ∈ 𝔹^(32+32+64+32⋅((𝑘+ℓ)⋅bitlen (2𝜂)+𝑑𝑘)). + let full_sk_len = + 32 + 32 + 64 + 32 * ((P::k + P::l) * bitlen(2 * P::eta as u32) + d as usize * P::k); + assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: Algorithm 24 output size", P::ALG_NAME); + + // Algorithm 26 (sigEncode): 𝜎 ∈ 𝔹^(𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘). + let sig_len = P::lambda as usize / 4 + + P::l * 32 * (1 + bitlen(P::gamma1 as u32 - 1)) + + P::omega as usize + + P::k; + assert_eq!(P::SIG_LEN, sig_len, "{}: Algorithm 26 output size", P::ALG_NAME); + } + + /// The associated types must be exactly as long as the consts that describe them; they are + /// written out by hand per parameter set, so this guards against a typo in one of them. + fn check_associated_type_sizes() { + for (got, want, what) in [ + (size_of::(), P::C_TILDE_LEN, "SigCTilde"), + (size_of::(), P::POLY_Z_PACKED_LEN, "PolyZPacked"), + (size_of::(), P::POLY_W1_PACKED_LEN, "PolyW1Packed"), + ] { + assert_eq!(got, want, "{}: {} vs its length const", P::ALG_NAME, what); + } + } + + #[test] + fn test_parameter_sets_match_fips204_table_1() { + check_table_1::(0); + check_table_1::(1); + check_table_1::(2); + } + + #[test] + fn test_sizes_match_fips204_table_2() { + check_table_2::(0); + check_table_2::(1); + check_table_2::(2); + } + + #[test] + fn test_table_2_sizes_agree_with_the_encoding_formulas() { + check_table_2_formulas::(); + check_table_2_formulas::(); + check_table_2_formulas::(); + } + + #[test] + fn test_associated_types_are_the_length_their_consts_claim() { + check_associated_type_sizes::(); + check_associated_type_sizes::(); + check_associated_type_sizes::(); + } + + #[test] + fn test_bitlen_matches_its_definition() { + // FIPS 204 Section 2.3 defines bitlen 𝑥 as the length of the binary expansion of 𝑥. + assert_eq!(bitlen(0), 0); + assert_eq!(bitlen(1), 1); + assert_eq!(bitlen(2), 2); + assert_eq!(bitlen(3), 2); + assert_eq!(bitlen(4), 3); + // The two arguments the derivations above actually use, plus bitlen(𝑞 − 1) = 23. + assert_eq!(bitlen((1 << 17) - 1), 17); + assert_eq!(bitlen((1 << 19) - 1), 19); + assert_eq!(bitlen((q - 1) as u32), 23); + } + + #[test] + fn test_gamma_dispatch_constants_cover_every_parameter_set() { + // The packing routines dispatch on these; a parameter set whose 𝛾 is neither value would + // fall through to a panic at runtime rather than fail to compile, so pin them here. + for gamma1 in [MLDSA44Params::gamma1, MLDSA65Params::gamma1, MLDSA87Params::gamma1] { + assert!(gamma1 == GAMMA1_2_POW_17 || gamma1 == GAMMA1_2_POW_19); + } + for gamma2 in [MLDSA44Params::gamma2, MLDSA65Params::gamma2, MLDSA87Params::gamma2] { + assert!(gamma2 == GAMMA2_Q_MINUS_1_OVER_88 || gamma2 == GAMMA2_Q_MINUS_1_OVER_32); + } + assert_ne!(GAMMA1_2_POW_17, GAMMA1_2_POW_19); + assert_ne!(GAMMA2_Q_MINUS_1_OVER_88, GAMMA2_Q_MINUS_1_OVER_32); + } + + #[test] + fn test_weaker_of_picks_the_lower_strength() { + assert_eq!( + weaker_of(SecurityStrength::_128bit, SecurityStrength::_256bit), + SecurityStrength::_128bit + ); + assert_eq!( + weaker_of(SecurityStrength::_256bit, SecurityStrength::_128bit), + SecurityStrength::_128bit + ); + assert_eq!( + weaker_of(SecurityStrength::_192bit, SecurityStrength::_192bit), + SecurityStrength::_192bit + ); + } +} diff --git a/crypto/mldsa-lowmemory/src/polynomial.rs b/crypto/mldsa-common/src/polynomial.rs similarity index 63% rename from crypto/mldsa-lowmemory/src/polynomial.rs rename to crypto/mldsa-common/src/polynomial.rs index 6d37f6c53..009549768 100644 --- a/crypto/mldsa-lowmemory/src/polynomial.rs +++ b/crypto/mldsa-common/src/polynomial.rs @@ -1,8 +1,9 @@ -//! Represents a polynomial over the ML-DSA ring. +//! A polynomial over the ML-DSA ring, with the NTT and the coefficient-wise operations of +//! FIPS 204 Section 7, and reduction modulo 𝑞. use crate::aux_functions::{high_bits, low_bits, make_hint, use_hint}; -use crate::mldsa::{N, d, q, q_inv}; -use crate::params::{GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, MLDSAParams}; +use crate::params::{GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, MLDSAParamsBase}; +use crate::{N, d, q, q_inv}; use bouncycastle_utils::secret::ZeroizablePrimitive; use core::ops::{Index, IndexMut}; @@ -12,10 +13,13 @@ use core::ops::{Index, IndexMut}; /// Polynomials themselves are not inherently secret since sometimes they are part of public keys /// and sometimes private keys. /// It is the responsibility of the caller to wrap sensitive instances in `Secret`. -/// Note: at the moment, nothing in this crate uses `Secret`, so I have left the `impl ZeroizablePrimitive` commented-out. +/// +/// `coeffs` is public because the two ML-DSA crates build their vector types and key encodings +/// on it; outside them a `Polynomial` is an opaque handle. #[derive(Clone, Copy)] -pub(crate) struct Polynomial { - pub(crate) coeffs: [i32; N], +pub struct Polynomial { + /// The 256 coefficients, 𝑤0 … 𝑤255. + pub coeffs: [i32; N], } /// Convenience function to avoid ".0" all over the place. @@ -33,10 +37,11 @@ impl IndexMut for Polynomial { } } -// Turn this back on if we want to start tagging things as `Secret`. -// impl ZeroizablePrimitive for Polynomial { -// const ZEROED: Self = Self::new(); -// } +impl Default for Polynomial { + fn default() -> Self { + Self::new() + } +} impl Polynomial { /// Create a new polynomial with all coefficients set to zero. @@ -44,15 +49,23 @@ impl Polynomial { Self { coeffs: [0i32; N] } } - pub(crate) fn conditional_add_q(&mut self) { + /// Adds 𝑞 to every negative coefficient. + pub fn conditional_add_q(&mut self) { for x in self.coeffs.iter_mut() { *x = conditional_add_q(*x); } } + /// Montgomery-reduces every coefficient. + pub fn reduce(&mut self) { + for i in 0..N { + self[i] = montgomery_reduce(self[i] as i64); + } + } + /// Reduces every coefficient to |𝑐| < 𝑞 without changing its residue. See [`reduce32`] for when - /// this is required for security reasons; note that [`Self::reduce`] is a *Montgomery* reduction and is not a substitute. - pub(crate) fn reduce32(&mut self) { + /// this is required; note that [`Self::reduce`] is a *Montgomery* reduction and is not a substitute. + pub fn reduce32(&mut self) { for x in self.coeffs.iter_mut() { *x = reduce32(*x); } @@ -61,60 +74,77 @@ impl Polynomial { /// Algorithm 44 AddNTT(𝑎, 𝑏)̂ /// Computes the sum a + 𝑏 of two elements 𝑎, 𝑏 ∈ 𝑇𝑞. /// Note: result could be up to 2q. - pub(crate) fn add_ntt(&mut self, w: &Self) { + pub fn add_ntt(&mut self, w: &Self) { for i in 0..N { self[i] += w[i]; } } - pub(crate) fn sub(&mut self, w: &Self) { + /// Subtracts `w` coefficient by coefficient. + pub fn sub(&mut self, w: &Self) { for i in 0..N { self[i] -= w[i]; } } - /// Algorithm 45 MultiplyNTT(𝑎, 𝑏)̂ - /// Computes the product 𝑎 ∘̂ 𝑏 of two elements 𝑎, 𝑏 ∈ 𝑇𝑞. - /// Input: 𝑎, 𝑏 ∈ 𝑇𝑞. - /// Output: 𝑐 ∈ 𝑇𝑞. - /// Multiply the coefficients in this polynomial by those in another polynomial and perform montgomery reduction. - /// Also called pointwise montgomery multiplication - pub(crate) fn multiply_ntt(&mut self, b: &Polynomial) { + /// Algorithm 45 MultiplyNTT(𝑎, 𝑏)̂, in place: `self` becomes 𝑠𝑒𝑙𝑓 ∘̂ 𝑏. + /// See [`multiply_ntt`] for the form that returns a new polynomial. + pub fn multiply_ntt(&mut self, b: &Polynomial) { for i in 0..N { self[i] = montgomery_reduce((self[i] as i64) * (b[i] as i64)); } } - pub(crate) fn high_bits(&mut self) { + /// Algorithm 37 HighBits(𝑟) applied to every coefficient, in place. + pub fn high_bits_inplace(&mut self) { for i in 0..N { self[i] = high_bits::

(self[i]); } } - pub(crate) fn low_bits(&mut self) { + /// Algorithm 38 LowBits(𝑟) applied to every coefficient, in place. + pub fn low_bits_inplace(&mut self) { for i in 0..N { self[i] = low_bits::

(self[i]); } } + /// Algorithm 37 HighBits(𝑟) applied to every coefficient, leaving `self` as it was. + #[must_use] + pub fn high_bits(&self) -> Self { + let mut w = *self; + w.high_bits_inplace::

(); + w + } + + /// Algorithm 38 LowBits(𝑟) applied to every coefficient, leaving `self` as it was. + #[must_use] + pub fn low_bits(&self) -> Self { + let mut w = *self; + w.low_bits_inplace::

(); + w + } + /// Tests whether any coefficient has absolute value at least `bound`. /// /// `bound` is a runtime argument rather than a const generic because every call site passes a /// value derived from the parameter set (𝛾1 − 𝛽, 𝛾2 − 𝛽, or 𝛾2), and an associated const of a /// type parameter cannot be used as a const generic argument. It is still a constant after /// monomorphization, so this costs nothing. - pub(crate) fn check_norm(&self, bound: i32) -> bool { - // Fine that this is not constant-time (returns true early) because it is used in a rejection loop. - // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing - // the signature validation. - // So the i32 that was just checked in a non-constant-time manner is about to get thrown away. + pub fn check_norm(&self, bound: i32) -> bool { + // It is acceptable that this function is not constant-time (returns true early) + // The reason being because it is used in a rejection loop. + // That is, the early quit here leads to rejection, dropping the secret values and + // continuing to the top of the rejection loop with generating new secret values, + // or failing the signature validation. + // So the i32 that we just checked in a non-constant-time manner is about to get thrown away. // Note: this formulation of the check_norm function usually requires this bounds check // if bound > (q - 1) / 8 { // return true; // } - // but since every caller passes a parameter-set constant, a debug_assert is done instead - // to ensure the value is what is expected. + // but since every caller passes a parameter-set constant, a debug_assert is performed + // instead to make sure the value is what we expect. debug_assert!(bound <= (q - 1) / 8); let mut t: i32; @@ -133,25 +163,36 @@ impl Polynomial { /// /// 𝑑 is 13 for all three parameter sets (FIPS 204, Table 1), so it is read from the global /// constant rather than being passed in. - pub(crate) fn shift_left_d(&mut self) { + pub fn shift_left_d(&mut self) { for x in self.coeffs.iter_mut() { *x <<= d; } } - /// Creates the hint vector, and also returns its hamming weight (ie the number of 1's). - pub(crate) fn make_hint_row(&self, r: &Self) -> (Self, i32) { + /// The hint polynomial of `self` = LowBits(w - cs2) + ct0 against `r` = HighBits(w), coefficient + /// by coefficient (see [`make_hint`]), and its Hamming weight (the number of 1's). + pub fn make_hint(&self, r: &Self) -> (Self, i32) { let mut out = Polynomial::new(); let mut count = 0i32; for i in 0..N { let x = make_hint::

(self[i], r[i]); out[i] = x; + + // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch count += x; } (out, count) } + /// Algorithm 40 UseHint(ℎ, 𝑟) applied coefficient by coefficient, in place: `self` is + /// 𝐰'_approx on entry and 𝐰1' on return. + pub fn use_hint(&mut self, h: &Polynomial) { + for i in 0..N { + self[i] = use_hint::

(self[i], h[i]); + } + } + /// SimpleBitPack(𝐰1[𝑖], (𝑞 − 1)/(2𝛾2) − 1), the per-coordinate body of /// FIPS 204, Algorithm 28 (w1Encode), line 3. /// @@ -159,16 +200,10 @@ impl Polynomial { /// 4 bits for 𝛾2 = (𝑞 − 1)/32, so there is one packing layout per distinct 𝛾2 rather than one /// per parameter set. `P::gamma2` is a constant after monomorphization, so only the matching /// arm survives. - pub(crate) fn w1_encode(&self) -> P::PolyW1Packed { - // It might seem counter-intuitive for a low-memory implementation to create a tmp buffer - // rather than work in the provided buffer, but experimentation shows that - // rust is roughly an order of magnitude faster working in a scope-local array than - // in a referenced piece of memory. - // This is possibly because, once the rust compiler understands that the intermediate values are scope-local, - // it performs optimizations throughout all of the computation into CPU registers and skips, in this case, - // several hundred physical memory writes. - // So while it looks odd to use a scope variable in a low-memory implementation, it's way faster - // while seemingly maintaining the same physical memory footprint. + pub fn w1_encode(&self) -> P::PolyW1Packed { + // A scope-local output buffer rather than a caller-supplied one: measured roughly an order + // of magnitude faster, presumably because the compiler keeps the intermediate values in + // registers once it can see the buffer does not escape, and the peak memory is the same. let mut out = ::ZEROED; let r = out.as_mut(); @@ -206,10 +241,13 @@ impl Polynomial { /// Usage of the rust type system to enforce this is arguably unnecessary, since that's what the NIST /// test vectors are for. /// - /// Design choice: the NTT is not done in-place, but copy data to a new array. - /// This uses slightly more memory and requires a copy, but makes the code easier to read - /// and less likely to contain a bug. But this optimization could be considered in the future. - pub(crate) fn ntt(&mut self) { + /// The transform is done in place. + /// + /// Lazy reduction: the butterfly omits an explicit reduction modulo `q` + /// This is safe only because ‖input‖∞ ≤ q-1 (i.e. intermediates stay below ~5q + /// (well within i32) and the input of montgomery_reduce input stays below q·2^{31}) and + /// the final result is reduced downstream. + pub fn ntt(&mut self) { let mut m: usize = 0; let mut len: usize = 128; @@ -221,21 +259,22 @@ impl Polynomial { for j in start..start + len { let t = montgomery_reduce(z as i64 * self[j + len] as i64); - self[j + len] = self[j] - t; // '% q' not strictly needed cause it gets reduced at some point later. Removing it gave +5% in benchmarking - self[j] = self[j] + t; // '% q' not strictly needed + // '% q' not strictly needed cause it gets reduced at some point later. + // Removing it gave +5% in benchmarking + self[j + len] = self[j] - t; + self[j] += t; // '% q' not strictly needed } - start = start + 2 * len; + start += 2 * len; } len >>= 1; } } - /// Algorithm 42 NTT−1(𝑤)̂ + /// Algorithm 42 NTT−1(𝑤_hat) /// Computes the inverse of the NTT. - /// Input: ̂̂ ̂ 𝑤 = (𝑤\[0], … , 𝑤\[255]) ∈ 𝑇𝑞. - /// Output: Polynomial 𝑤(𝑋) = ∑255 - /// 𝑗=0 𝑤𝑗𝑋𝑗 ∈ 𝑅𝑞 - pub(crate) fn inv_ntt(&mut self) { + /// Input: 𝑤_hat = (𝑤_hat[0], … , 𝑤_hat[255]) ∈ 𝑇𝑞. + /// Output: Polynomial 𝑤(𝑋) = Σ_{j=0}^{255} 𝑤𝑗𝑋𝑗 ∈ 𝑅𝑞 + pub fn inv_ntt(&mut self) { // A core input condition on the InverseNTT is that every input // coefficient must satisfy |𝑤| < 𝑞 for the sums to stay within an i32. // The reason this is required is because Algorithm 42's butterflies (steps 13-14) @@ -254,7 +293,7 @@ impl Polynomial { let mut start: usize = 0; while start < N { m -= 1; - let z = (-1) * ZETAS[m]; + let z = -ZETAS[m]; // j = start; // while j < start + len { @@ -271,33 +310,45 @@ impl Polynomial { // 𝑤𝑗+𝑙𝑒𝑛 ← (𝑧 ⋅ 𝑤𝑗+𝑙𝑒𝑛) mod 𝑞 self[j + len] = montgomery_reduce(z as i64 * self[j + len] as i64); } - start = start + 2 * len; // could be optimized to save the multiply-by-two since j finishes as `start + len`. That said 2* is just << 1, which is basically free. + start += 2 * len; + // could be optimized to save the multiply-by-two since j finishes as `start + len`. + // That said 2* is just << 1, which is basically free. } len <<= 1; } - // f = 256^-1 mod q - // const f: i64 = 8347681; - // bc-java uses this value rather than the one in FIPS 204 + // Final 1/256 normalization, in Montgomery form to match the montgomery_reduce + // bookkeeping used by every butterfly above (each contributes a 2^-32 factor). + // Note: f != 256^-1 mod q = 8347681. That value is only correct + // applied as a plain multiply (FIPS 204 Alg 42: w_j <- f * w_j mod q). + // Here we apply f via montgomery_reduce(f * w_j), so the constant is the + // Montgomery-domain form: f = mont^2 / 256 mod q = 41978, mont = 2^32 mod q. + // Do NOT substitute 8347681, as it is invalid through montgomery_reduce. const f: i64 = 41978; for j in 0..N { // equiv. to the global constant N self[j] = montgomery_reduce(f * self[j] as i64); } } +} - pub(crate) fn use_hint(&mut self, h: &Polynomial) { - for i in 0..N { - self[i] = use_hint::

(self[i], h[i]); - } - } +/// Algorithm 45 MultiplyNTT(𝑎, 𝑏)̂ +/// Computes the product 𝑎 ∘̂ 𝑏 of two elements 𝑎, 𝑏 ∈ 𝑇𝑞. +/// Input: 𝑎, 𝑏 ∈ 𝑇𝑞. +/// Output: 𝑐 ∈ 𝑇𝑞. +/// Multiply the coefficients in this polynomial by those in another polynomial and perform montgomery reduction. +/// Also called pointwise Montgomery multiplication. See [`Polynomial::multiply_ntt`] for the in-place form. +pub fn multiply_ntt(a: &Polynomial, b: &Polynomial) -> Polynomial { + let mut out = *a; + out.multiply_ntt(b); + out } /// FIPS 204 Algorithm 49 /// As described in FIPS 204 Appendix A, montgomery reduction allows for efficient computation /// of expressions of the form c = a * b (mod q). /// The output is not necessarily less than q in absolute value, but it is less than 2q in absolute value -pub(crate) fn montgomery_reduce(a: i64) -> i32 { +pub fn montgomery_reduce(a: i64) -> i32 { debug_assert!(a > -((q as i64) << 31) && a < ((q as i64) << 31)); // 2: 𝑡 ← ((𝑎 mod 2^32) ⋅ QINV) mod 2^32 @@ -307,7 +358,8 @@ pub(crate) fn montgomery_reduce(a: i64) -> i32 { ((a - ((t as i64) * (q as i64))) >> 32) as i32 } -pub(crate) fn conditional_add_q(a: i32) -> i32 { +/// Adds 𝑞 to a negative coefficient, so that one in (−𝑞, 𝑞) lands in [0, 𝑞). +pub fn conditional_add_q(a: i32) -> i32 { a + ((a >> 31) & q) } @@ -318,30 +370,15 @@ pub(crate) fn conditional_add_q(a: i32) -> i32 { /// In this implementation, sums of Montgomery products are left unreduced, but `inv_ntt` (Algorithm 42) /// needs |input| < 𝑞 to stay within an `i32`, so this is applied to those sums first. Omitting it before /// the verifier's NTT⁻¹ is exploitable (, Wycheproof mldsa_87_verify tcId 240/241). -pub(crate) fn reduce32(a: i32) -> i32 { +pub fn reduce32(a: i32) -> i32 { // The reference implementation's stated input bound; above it `a + 2^22` overflows. debug_assert!(a <= i32::MAX - (1 << 22)); let t = (a + (1 << 22)) >> 23; a - t * q } -#[test] -/// These are the results it's giving; I'm not sure if these are "correct" or not. -fn test_conditional_add_q() { - assert_eq!(conditional_add_q(-q - 1), -1); - assert_eq!(conditional_add_q(-q), 0); - assert_eq!(conditional_add_q(-q - 2), -2); - assert_eq!(conditional_add_q(-q + 1), 1); - assert_eq!(conditional_add_q(-1), q - 1); - assert_eq!(conditional_add_q(0), 0); - assert_eq!(conditional_add_q(1), 1); - assert_eq!(conditional_add_q(q - 1), q - 1); - assert_eq!(conditional_add_q(q), q); - assert_eq!(conditional_add_q(q + 1), q + 1); -} - /// Constants for NTT -const ZETAS: [i32; 256] = [ +pub const ZETAS: [i32; N] = [ 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, @@ -373,6 +410,21 @@ const ZETAS: [i32; 256] = [ #[cfg(test)] mod tests { use super::*; + + #[test] + fn test_conditional_add_q() { + assert_eq!(conditional_add_q(-q - 1), -1); + assert_eq!(conditional_add_q(-q), 0); + assert_eq!(conditional_add_q(-q - 2), -2); + assert_eq!(conditional_add_q(-q + 1), 1); + assert_eq!(conditional_add_q(-1), q - 1); + assert_eq!(conditional_add_q(0), 0); + assert_eq!(conditional_add_q(1), 1); + assert_eq!(conditional_add_q(q - 1), q - 1); + assert_eq!(conditional_add_q(q), q); + assert_eq!(conditional_add_q(q + 1), q + 1); + } + #[test] fn test_reduce32() { // congruent to the input mod q, and within the reference implementation's stated output range @@ -405,4 +457,16 @@ mod tests { assert!(reduce32(8 * q - 8).abs() < q); assert!(reduce32(-(8 * q - 8)).abs() < q); } + + #[test] + fn test_montgomery_reduce_is_a_times_2_pow_minus_32() { + // Algorithm 49: the output is congruent to 𝑎 ⋅ 2^(−32) mod 𝑞 and lies in (−𝑞, 𝑞). + const MONT: i64 = (1i64 << 32) % q as i64; + for &a in &[0i64, 1, -1, q as i64 - 1, -(q as i64), 123456789012, -987654321098] { + let r = montgomery_reduce(a); + assert!(r.abs() < q, "montgomery_reduce({a}) = {r} out of range"); + // r * 2^32 ≡ a (mod q) + assert_eq!((r as i64 * MONT - a).rem_euclid(q as i64), 0, "montgomery_reduce({a})"); + } + } } diff --git a/crypto/mldsa-lowmemory/Cargo.toml b/crypto/mldsa-lowmemory/Cargo.toml index 395a97cf9..1a507c99f 100644 --- a/crypto/mldsa-lowmemory/Cargo.toml +++ b/crypto/mldsa-lowmemory/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true +bouncycastle-mldsa-common.workspace = true bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true @@ -12,6 +13,7 @@ bouncycastle-utils.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true +bouncycastle-mldsa.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true diff --git a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs index 4e20fa833..df551e7e5 100644 --- a/crypto/mldsa-lowmemory/benches/mldsa_benches.rs +++ b/crypto/mldsa-lowmemory/benches/mldsa_benches.rs @@ -124,19 +124,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA44_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA44::sign(&mldsa44_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-44_lowmemory", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA44::verify(&mldsa44_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA44::verify(&mldsa44_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); @@ -148,19 +146,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA65_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA65::sign(&mldsa65_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-65_lowmemory", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA65::verify(&mldsa65_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA65::verify(&mldsa65_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); @@ -172,19 +168,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA87_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA87::sign(&mldsa87_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-87_lowmemory", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA87::verify(&mldsa87_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA87::verify(&mldsa87_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); diff --git a/crypto/mldsa-lowmemory/src/aux_functions.rs b/crypto/mldsa-lowmemory/src/aux_functions.rs index 7f5c702a5..3570b9691 100644 --- a/crypto/mldsa-lowmemory/src/aux_functions.rs +++ b/crypto/mldsa-lowmemory/src/aux_functions.rs @@ -1,338 +1,12 @@ -//! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. +//! The parts of FIPS 204, Algorithm 27 (sigDecode) that read one component of a signature at a +//! time, so that the verifier never holds the whole of 𝐳 or 𝐡. The coefficient- and +//! polynomial-level functions of Section 7 are in `bouncycastle_mldsa_common::aux_functions`. -use crate::mldsa::{G, H, POLY_T0PACKED_LEN}; -use crate::mldsa::{N, POLY_T1PACKED_LEN, d, q}; -use crate::params::{ - GAMMA1_2_POW_17, GAMMA1_2_POW_19, GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, - MLDSAParams, -}; -use crate::polynomial::Polynomial; -use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; +use crate::params::MLDSAParams; +use bouncycastle_mldsa_common::aux_functions::bit_unpack_gamma1; +use bouncycastle_mldsa_common::polynomial::Polynomial; use bouncycastle_utils::secret::ZeroizablePrimitive; -/// Algorithm 14 CoeffFromThreeBytes(𝑏0, 𝑏1, 𝑏2) -/// Output: An integer modulo 𝑞 or ⊥. -pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { - // This is the exact alg from FIPS 204: - // let mut b2_prime = b2; - // if b2_prime > 127 { - // // Set the top bit of b2_prime to 0 - // b2_prime = b2_prime - 128; - // } - - // but this is equivalent and feels more constant-time: - let b2_prime = b[2] & 0x7F; - - let z: i32 = ((b2_prime as i32) << 16) | ((b[1] as i32) << 8) | (b[0] as i32); - - // mutants note: yeah, this could be z <= q and nothing changes because z == q is the same as z == 0 - if z < q { Ok(z) } else { Err(()) } -} - -/// Algorithm 15 CoeffFromHalfByte(𝑏) -/// Let 𝜂 ∈ {2, 4}. Generates an element of {−𝜂, −𝜂 + 1, … , 𝜂} ∪ {⊥}. -/// Input: Integer 𝑏 ∈ {0, 1, … , 15}. -/// Output: An integer between −𝜂 and 𝜂, or ⊥. -#[inline(always)] -pub(crate) fn coeff_from_half_byte(b: u8) -> Result { - if P::eta == 2 && b < 15 { - // Original code is bad because '%' is not constant-time. - // Ok(2 - (b % 5) as i32) - // TODO: Verify whether this function is constant time and whether it can be further optimized - let b = match b { - b if b < 5 => b, - b if b < 10 => b - 5, - _ => b - 10, - }; - Ok(2 - b as i32) - } else { - if P::eta == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } - } -} - -/// A specific instantiation of Algorithm 16 SimpleBitPack(𝑤, 𝑏) with the constants set for packing the t1 vector -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in [0, 𝑏]. -/// Output: A byte string of length 32 ⋅ bitlen 𝑏. -pub(crate) fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { - let mut output = [0u8; POLY_T1PACKED_LEN]; - for i in 0..N / 4 { - output[5 * i] = w[4 * i] as u8; - output[5 * i + 1] = ((w[4 * i] >> 8) | (w[4 * i + 1] << 2)) as u8; - output[5 * i + 2] = ((w[4 * i + 1] >> 6) | (w[4 * i + 2] << 4)) as u8; - output[5 * i + 3] = ((w[4 * i + 2] >> 4) | (w[4 * i + 3] << 6)) as u8; - output[5 * i + 4] = (w[4 * i + 3] >> 2) as u8; - } - output -} - -/// A variant of Algorithm 17 BitPack specific to a=eta, b=eta -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−eta, eta]. -/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). -// `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 -// and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. -#[inline(always)] -pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { - debug_assert_eq!(r.len(), P::POLY_ETA_PACKED_LEN); - - // temp swap space - let mut t: [u8; 8] = [0; 8]; - - match P::eta { - // MLDSA-44 and MLDSA-87 - 2 => { - let eta: i32 = 2; - for i in 0..N / 8 { - t[0] = (eta - w[8 * i]) as u8; - t[1] = (eta - w[8 * i + 1]) as u8; - t[2] = (eta - w[8 * i + 2]) as u8; - t[3] = (eta - w[8 * i + 3]) as u8; - t[4] = (eta - w[8 * i + 4]) as u8; - t[5] = (eta - w[8 * i + 5]) as u8; - t[6] = (eta - w[8 * i + 6]) as u8; - t[7] = (eta - w[8 * i + 7]) as u8; - - r[3 * i] = t[0] | (t[1] << 3) | (t[2] << 6); - r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7); - r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); - } - } - // MLDSA-65 - 4 => { - let eta: i32 = 4; - for i in 0..N / 2 { - t[0] = (eta - w[2 * i]) as u8; - t[1] = (eta - w[2 * i + 1]) as u8; - r[i] = t[0] | t[1] << 4; - } - } - _ => panic!("Invalid eta value"), - } -} - -/// A variant of Algorithm 17 BitPack specific to packing the t0 polynomial with a=2^{d-1}-1, b=2^{d-1} -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−eta, eta]. -/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). -pub(crate) fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { - let mut r = [0u8; POLY_T0PACKED_LEN]; - - let mut t = [0; 8]; - for i in 0..N / 8 { - t[0] = (1 << (d - 1)) - t0[8 * i]; - t[1] = (1 << (d - 1)) - t0[8 * i + 1]; - t[2] = (1 << (d - 1)) - t0[8 * i + 2]; - t[3] = (1 << (d - 1)) - t0[8 * i + 3]; - t[4] = (1 << (d - 1)) - t0[8 * i + 4]; - t[5] = (1 << (d - 1)) - t0[8 * i + 5]; - t[6] = (1 << (d - 1)) - t0[8 * i + 6]; - t[7] = (1 << (d - 1)) - t0[8 * i + 7]; - - r[13 * i] = t[0] as u8; - r[13 * i + 1] = (t[0] >> 8) as u8; - r[13 * i + 1] |= (t[1] << 5) as u8; - r[13 * i + 2] = (t[1] >> 3) as u8; - r[13 * i + 3] = (t[1] >> 11) as u8; - r[13 * i + 3] |= (t[2] << 2) as u8; - r[13 * i + 4] = (t[2] >> 6) as u8; - r[13 * i + 4] |= (t[3] << 7) as u8; - r[13 * i + 5] = (t[3] >> 1) as u8; - r[13 * i + 6] = (t[3] >> 9) as u8; - r[13 * i + 6] |= (t[4] << 4) as u8; - r[13 * i + 7] = (t[4] >> 4) as u8; - r[13 * i + 8] = (t[4] >> 12) as u8; - r[13 * i + 8] |= (t[5] << 1) as u8; - r[13 * i + 9] = (t[5] >> 7) as u8; - r[13 * i + 9] |= (t[6] << 6) as u8; - r[13 * i + 10] = (t[6] >> 2) as u8; - r[13 * i + 11] = (t[6] >> 10) as u8; - r[13 * i + 11] |= (t[7] << 3) as u8; - r[13 * i + 12] = (t[7] >> 5) as u8; - } - - r -} - -/// A variant of Algorithm 17 specific to packing z in the signature value in \[−𝛾1 + 1, 𝛾1]. -/// The destination is a slice rather than a `P::PolyZPacked`: the only caller writes straight into -/// its window of the signature buffer, which is chunked at runtime because the chunk size -/// `P::POLY_Z_PACKED_LEN` cannot be a const generic argument. -pub(crate) fn bitpack_gamma1(z: &Polynomial, out: &mut [u8]) { - debug_assert_eq!(out.len(), P::POLY_Z_PACKED_LEN); - out.fill(0); - - let mut t: [u32; 4] = [0; 4]; - match P::gamma1 { - // MLDSA-44 - GAMMA1_2_POW_17 => { - for i in 0..N / 4 { - t[0] = (P::gamma1 - z[4 * i]) as u32; - t[1] = (P::gamma1 - z[4 * i + 1]) as u32; - t[2] = (P::gamma1 - z[4 * i + 2]) as u32; - t[3] = (P::gamma1 - z[4 * i + 3]) as u32; - - out[9 * i] = t[0] as u8; - out[9 * i + 1] = (t[0] >> 8) as u8; - out[9 * i + 2] = ((t[0] >> 16) | (t[1] << 2)) as u8; - out[9 * i + 3] = (t[1] >> 6) as u8; - out[9 * i + 4] = ((t[1] >> 14) | (t[2] << 4)) as u8; - out[9 * i + 5] = (t[2] >> 4) as u8; - out[9 * i + 6] = ((t[2] >> 12) | (t[3] << 6)) as u8; - out[9 * i + 7] = (t[3] >> 2) as u8; - out[9 * i + 8] = (t[3] >> 10) as u8; - } - } - // MLDSA-65 and -87 have the same GAMMA1 value - GAMMA1_2_POW_19 => { - for i in 0..N / 2 { - t[0] = (P::gamma1 - z[2 * i]) as u32; - t[1] = (P::gamma1 - z[2 * i + 1]) as u32; - - out[5 * i] = t[0] as u8; - out[5 * i + 1] = (t[0] >> 8) as u8; - out[5 * i + 2] = ((t[0] >> 16) | (t[1] << 4)) as u8; - out[5 * i + 3] = (t[1] >> 4) as u8; - out[5 * i + 4] = (t[1] >> 12) as u8; - } - } - _ => { - panic!("Invalid gamma1 value") - } - } -} - -/// A specific instantiation of Algorithm 18 SimpleBitUnpack(v, 𝑏) with the constants set for unpacking the t1 vector -/// Input: 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen 𝑏. -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [0, 2𝑐 − 1], where 𝑐 = bitlen 𝑏. -/// When 𝑏 + 1 is a power of 2, the coefficients are in [0, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size -pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { - let mut w = Polynomial::new(); - - for i in 0..N / 4 { - w[4 * i] = ((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) & 0x3FF; - w[4 * i + 1] = (((v[5 * i + 1] as i32) >> 2) | ((v[5 * i + 2] as i32) << 6)) & 0x3FF; - w[4 * i + 2] = (((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) & 0x3FF; - w[4 * i + 3] = (((v[5 * i + 3] as i32) >> 6) | ((v[5 * i + 4] as i32) << 2)) & 0x3FF; - } - - w -} - -/// A variant of Algorithm 19 BitUnpack specific to a=eta, b=eta -/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). -/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size - -// `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 -// and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. -#[inline(always)] -pub(crate) fn bit_unpack_eta_out(v: &[u8], w: &mut Polynomial) { - debug_assert_eq!(v.len(), P::POLY_ETA_PACKED_LEN); - - match P::eta { - // MLDSA44 and MLDSA87 - 2 => { - let eta: i32 = 2; - for i in 0..N / 8 { - w[8 * i] = (v[3 * i] & 7) as i32; - w[8 * i + 1] = ((v[3 * i] >> 3) & 7) as i32; - w[8 * i + 2] = ((v[3 * i] >> 6) | (v[3 * i + 1] << 2) & 7) as i32; - w[8 * i + 3] = ((v[3 * i + 1] >> 1) & 7) as i32; - w[8 * i + 4] = ((v[3 * i + 1] >> 4) & 7) as i32; - w[8 * i + 5] = ((v[3 * i + 1] >> 7) | (v[3 * i + 2] << 1) & 7) as i32; - w[8 * i + 6] = ((v[3 * i + 2] >> 2) & 7) as i32; - w[8 * i + 7] = ((v[3 * i + 2] >> 5) & 7) as i32; - - w[8 * i] = eta - w[8 * i]; - w[8 * i + 1] = eta - w[8 * i + 1]; - w[8 * i + 2] = eta - w[8 * i + 2]; - w[8 * i + 3] = eta - w[8 * i + 3]; - w[8 * i + 4] = eta - w[8 * i + 4]; - w[8 * i + 5] = eta - w[8 * i + 5]; - w[8 * i + 6] = eta - w[8 * i + 6]; - w[8 * i + 7] = eta - w[8 * i + 7]; - } - } - // MLDSA65 - 4 => { - let eta: i32 = 4; - for i in 0..N / 2 { - w[2 * i] = (v[i] & 0x0F) as i32; - w[2 * i + 1] = (v[i] >> 4) as i32; - - w[2 * i] = eta - w[2 * i]; - w[2 * i + 1] = eta - w[2 * i + 1]; - } - } - _ => panic!("Invalid eta value"), - } -} - -/// A variant of Algorithm 19 BitUnpack specific to a=𝛾1 − 1, b=𝛾1 -/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). -/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size - -// `match ETA` folds away per monomorphization (ETA is a const generic), so ETA = 2 -// and ETA = 4 each compile to just their own arm, leaving no dispatch at runtime. -#[inline(always)] -pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { - let mut w = Polynomial::new(); - - match P::gamma1 { - // MLDSA-44 - GAMMA1_2_POW_17 => { - // const gamma1: i32 = 1<<17; - for i in 0..N / 4 { - w[4 * i] = (((v[9 * i] as i32) | ((v[9 * i + 1] as i32) << 8)) - | ((v[9 * i + 2] as i32) << 16)) - & 0x3FFFF; - w[4 * i + 1] = ((((v[9 * i + 2] as i32) >> 2) | ((v[9 * i + 3] as i32) << 6)) - | ((v[9 * i + 4] as i32) << 14)) - & 0x3FFFF; - w[4 * i + 2] = ((((v[9 * i + 4] as i32) >> 4) | ((v[9 * i + 5] as i32) << 4)) - | ((v[9 * i + 6] as i32) << 12)) - & 0x3FFFF; - w[4 * i + 3] = ((((v[9 * i + 6] as i32) >> 6) | ((v[9 * i + 7] as i32) << 2)) - | ((v[9 * i + 8] as i32) << 10)) - & 0x3FFFF; - - w[4 * i] = P::gamma1 - w[4 * i]; - w[4 * i + 1] = P::gamma1 - w[4 * i + 1]; - w[4 * i + 2] = P::gamma1 - w[4 * i + 2]; - w[4 * i + 3] = P::gamma1 - w[4 * i + 3]; - } - } - // MLDSA-65 and -87 have the same GAMMA1 value - GAMMA1_2_POW_19 => { - // const gamma1: i32 = 1<<19; - for i in 0..N / 2 { - w[2 * i] = (((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) - | ((v[5 * i + 2] as i32) << 16)) - & 0xFFFFF; - w[2 * i + 1] = ((((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) - | ((v[5 * i + 4] as i32) << 12)) - & 0xFFFFF; - - w[2 * i] = P::gamma1 - w[2 * i]; - w[2 * i + 1] = P::gamma1 - w[2 * i + 1]; - } - } - _ => { - panic!("Invalid gamma1 value") - } - }; - - w -} - /// Part of unpacking the sig value pub(crate) fn unpack_c_tilde(sig: &[u8]) -> P::SigCTilde { let mut c_tilde = ::ZEROED; @@ -420,343 +94,3 @@ pub(crate) fn unpack_h_row( Some(h) } - -/// Algorithm 29 SampleInBall(𝜌) -/// Samples a polynomial 𝑐 ∈ 𝑅 with coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. -/// Input: A seed 𝜌 ∈ 𝔹𝜆/4 -/// Output: A polynomial 𝑐 in 𝑅. -pub(crate) fn sample_in_ball(rho: &P::SigCTilde) -> Polynomial { - // 1: 𝑐 ← 0 - let mut c = Polynomial::new(); - - // 2: ctx ← H.Init() - // 3: ctx ← H.Absorb(ctx, 𝜌) - // 4: (ctx, 𝑠) ← H.Squeeze(ctx, 8) - let mut h = H::new(); - h.do_update(rho.as_ref()); - let mut s = [0u8; 8]; - let mut h = h.into_squeezer(); - h.do_output_out(&mut s); - - // 5: ℎ ← BytesToBits(𝑠) - // ▷ ℎ is a bit string of length 64 - let mut signs: u64 = 0; - for (i, item) in s.iter().enumerate().take(8) { - signs |= (*item as u64) << (8 * i); - } - - // 6: for 𝑖 from 256 − 𝜏 to 255 do - - // let mut pos = 8; - // let mut b; - let mut j = [0u8]; - for i in (N - P::tau as usize)..N { - // 7: (ctx, 𝑗) ← H.Squeeze(ctx, 1) - // Note: At first, it might seem to be faster to pre-squeeze a buffer outside the loop. - // However, after experimentation and testing, the difference is not noticeable. - h.do_output_out(&mut j); - - // 8: while 𝑗 > 𝑖 do - while j[0] as usize > i { - // ▷ rejection sampling in {0, … , 𝑖} - // 9: (ctx, 𝑗) ← H.Squeeze(ctx, 1) - h.do_output_out(&mut j); - } - - // 11: 𝑐𝑖 ← 𝑐𝑗 - c[i] = c[j[0] as usize]; - - // 12: 𝑐𝑗 ← (−1)^ℎ[𝑖+𝜏−256] - c[j[0] as usize] = (1u64.wrapping_sub(2 * (signs & 1))) as i32; - signs >>= 1; - } - - // check post-condition - // coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. - #[cfg(debug_assertions)] - { - let mut hamming_weight: i32 = 0; - for i in 0..N { - debug_assert!((-1..=1).contains(&c[i])); - if c[i] != 0 { - hamming_weight += 1; - } - } - debug_assert!(hamming_weight > 0); - debug_assert!(hamming_weight <= 64); - } - - c -} - -/// Algorithm 30 RejNTTPoly(𝜌) -/// This is supposed to take a rho: [u8; 34], which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) -/// but to avoid needing to copy bytes and allocate more memory, -/// here that is split into a [u8;32] and a [u8;2] -pub(crate) fn rej_ntt_poly(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { - let mut w_hat = Polynomial::new(); - let mut j: usize = 0; - let mut g = G::new(); - g.do_update(rho); - g.do_update(nonce); - - // SHAKE is fairly inefficient if only 3 bytes are squeezed at a time, so the implementation does a block instead. - // size is not a limitation, so long as it's a multiple of 3. - // 288 seemed to be the sweet spot from playing with benchmarks - // It's probably around the average rejection rate, and 288 is a multiple of both 3 (required for this alg) - // and 8 (efficient for SHAKE). - let mut s = [0u8; 288]; - let mut g = g.into_squeezer(); - g.do_output_out(&mut s); - let mut idx: usize = 0; - - while j < N { - if idx == s.len() { - g.do_output_out(&mut s); - idx = 0; - } - w_hat[j] = match coeff_from_three_bytes(&s[idx..idx + 3].try_into().unwrap()) { - Ok(c) => c, - Err(_) => { - // those three bytes were out of range for a coefficient, so go again with the next three bytes - // from the SHAKE stream. - idx += 3; - continue; - } - }; - idx += 3; - j += 1; - } - - w_hat -} - -/// Algorithm 31 RejBoundedPoly(𝜌) -/// Samples an element 𝑎 ∈ 𝑅 with coefficients in \[−𝜂, 𝜂\] computed via rejection sampling from 𝜌. -/// Input: A seed 𝜌 ∈ 𝔹66 . -/// Output: A polynomial 𝑎 ∈ 𝑅. -/// -/// This is supposed to take a rho: [u8; 66], which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) -/// but to avoid needing to copy bytes and allocate more memory, -/// here that is split into a [u8;64] and a [u8;2] -pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { - let mut a = Polynomial::new(); - let mut j: usize = 0; - let mut h = H::new(); - h.do_update(rho); - h.do_update(nonce); - - // SHAKE is fairly inefficient if only 3 bytes are squeezed at a time, so the implementation does a block instead. - // size is not a limitation as long as it is a multiple of 3. - // 312 seems to be the sweet spot after some experimentation - // which is possibly also related with the average rejection rate. - // Also, 312 is a multiple of 8 (efficient for SHAKE) - let mut z_arr = [0u8; 312]; - let mut h = h.into_squeezer(); - h.do_output_out(&mut z_arr); - let mut idx: usize = 0; - - while j < N { - let z0 = coeff_from_half_byte::

(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) - let z1 = coeff_from_half_byte::

(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) - - if z0.is_ok() { - a[j] = z0.unwrap(); - j += 1; - } /* else: do nothing */ - if z1.is_ok() && j < 256 { - a[j] = z1.unwrap(); - j += 1; - } /* else: do nothing */ - - idx += 1; - if idx == z_arr.len() { - h.do_output_out(&mut z_arr); - idx = 0; - } - } - - a -} - -/// Algorithm 34 ExpandMask(𝜌, 𝜇) -/// Samples a vector 𝐲 ∈ 𝑅ℓ such that each polynomial 𝐲[𝑟] has coefficients between −𝛾1 + 1 and 𝛾1. -/// Input: A seed 𝜌 ∈ 𝔹64 and a nonnegative integer 𝜇. -/// Output: Vector 𝐲 ∈ 𝑅ℓ . -pub(crate) fn expand_mask_poly(rho: &[u8; 64], nonce: u16) -> Polynomial { - // 1: 𝑐 ← 1 + bitlen (𝛾1 − 1) - // ▷ 𝛾1 is always a power of 2 - // 3: 𝜌′ ← 𝜌||IntegerToBytes(𝜇 + 𝑟, 2) - // 4: 𝑣 ← H(𝜌′, 32𝑐) - // The 32𝑐 bytes squeezed on line 4 are exactly `P::POLY_Z_PACKED_LEN`, so the buffer for them - // is `P::PolyZPacked`; see the docs on `MLDSAParams::POLY_Z_PACKED_LEN`. - let mut h = H::new(); - h.do_update(rho); - h.do_update(&nonce.to_le_bytes()); - let mut v = ::ZEROED; - let mut h = h.into_squeezer(); - h.do_output_out(v.as_mut()); - bit_unpack_gamma1::

(v.as_ref()) -} - -/// Algorithm 35 Power2Round(𝑟) -/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integers (𝑟1, 𝑟0). -pub(crate) fn power_2_round(r: i32) -> (i32, i32) { - const u: i32 = (1 << (d - 1)) - 1; - const v: i32 = -1 << d; - - let t = r + u; - let r0 = r - (t & v); - - (t >> d, r0) -} - -#[test] -// FIPS 204 describes the output as easy to check: -// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. -fn test_power_2_round() { - test(1); - test(q - 3); - test(q); - test(q + 3); - - fn test(r: i32) { - let (r1, r0) = power_2_round(r); - let mut res = ((r1 << d) + r0) % q; - if res < 0 { - res += q; - } - assert_eq!(r % q, res); - } -} - -/// Algorithm 36 Decompose(𝑟) -/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1(2𝛾2) + 𝑟0 mod 𝑞. -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integers (𝑟1, 𝑟0). -// the hope here is that the compiler will aggressively inline this function, -// and optimize away the branching. -#[inline(always)] -pub(crate) fn decompose(r: i32) -> (i32, i32) { - // 1: 𝑟+ ← 𝑟 mod 𝑞 - // 2: 𝑟0 ← 𝑟+ mod±(2𝛾2) - // 3: if 𝑟+ − 𝑟0 = 𝑞 − 1 then - // 4: 𝑟1 ← 0 - // 5: 𝑟0 ← 𝑟0 − 1 - // 6: else 𝑟1 ← (𝑟+ − 𝑟0)/(2𝛾2) - // 7: end if - // 8: return (𝑟1, 𝑟0) - - // By the powers of someone much more clever than me, this is equivalent. - - let mut r1: i32; - let mut r0 = (r + 127) >> 7; - - match P::gamma2 { - // MLDSO-44 - GAMMA2_Q_MINUS_1_OVER_88 => { - // (q - 1) / 88 - r0 = (r0 * 11275 + (1 << 23)) >> 24; - r0 ^= ((43 - r0) >> 31) & r0; - } - // ML-DSA-65 and -87 have the same GAMMA2 - GAMMA2_Q_MINUS_1_OVER_32 => { - // (q - 1) / 32; - r0 = (r0 * 1025 + (1 << 21)) >> 22; - r0 &= 15; - } - _ => { - // this branch should never compile - unimplemented!() - } - } - - r1 = r - r0 * 2 * P::gamma2; - - // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, - // this seems to work out mathematically equivalent to doing q/2, or (q+3)/2, but here it is left as (q-1)/2 - // since that's algorithmically correct, and just ignore the mutants results. - r1 -= (((q - 1) / 2 - r1) >> 31) & q; - - (r0, r1) -} - -/// Algorithm 37 HighBits(𝑟) -/// Returns 𝑟1 from the output of Decompose (𝑟). -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integer 𝑟1. -pub(crate) fn high_bits(r: i32) -> i32 { - // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) - // 2: return 𝑟1 - let (r1, _) = decompose::

(r); - r1 -} - -/// Algorithm 38 LowBits(𝑟) -/// Returns 𝑟0 from the output of Decompose (𝑟). -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integer 𝑟0. -pub(crate) fn low_bits(r: i32) -> i32 { - // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) - // 2: return 𝑟0 - let (_, r0) = decompose::

(r); - r0 -} - -/// Algorithm 39 MakeHint(𝑧, 𝑟) -/// Computes hint bit indicating whether adding 𝑧 to 𝑟 alters the high bits of 𝑟. -/// Input: 𝑧, 𝑟 ∈ ℤ𝑞. -/// Output: Boolean. -pub(crate) fn make_hint(z: i32, r: i32) -> i32 { - // Naïve implementation: - // // 1: 𝑟1 ← HighBits(𝑟) - // let r1 = high_bits::

(r); - // - // // 2: 𝑣1 ← HighBits(𝑟 + 𝑧) - // let v1 = high_bits::

(r + z); - // - // // 3: return [[𝑟1 ≠ 𝑣1]] - // if r1 != v1 { 1 } else { 0 } - - // By the powers of someone much more clever than me, this is equivalent. - // mutants note: we do not have KATs that exercise all branches of this if - if z <= P::gamma2 || z > q - P::gamma2 || (z == q - P::gamma2 && r == 0) { 0 } else { 1 } -} - -/// Algorithm 40 UseHint(ℎ, 𝑟) -/// Returns the high bits of 𝑟 adjusted according to hint ℎ. -/// Input: Boolean ℎ, 𝑟 ∈ ℤ𝑞. -/// Output: 𝑟1 ∈ ℤ with 0 ≤ 𝑟1 ≤ (𝑞−1) / 2*gamma2). -pub(super) fn use_hint(a: i32, hint: i32) -> i32 { - let (a0, a1) = decompose::

(a); - - if hint == 0 { - return a0; - } - - debug_assert!(hint == 1); - - match P::gamma2 { - // MLDSA-44 - GAMMA2_Q_MINUS_1_OVER_88 => { - // mutants note: this passes unit tests if it's a1 >= 0 - // it is left like this because it matches the spec - if a1 > 0 { - if a0 == 43 { 0 } else { a0 + 1 } - } else { - if a0 == 0 { 43 } else { a0 - 1 } - } - } - // ML-DSA65 and -87 have the same GAMMA2 - GAMMA2_Q_MINUS_1_OVER_32 => { - // mutants note: this passes unit tests if it's a0 >= 0 - // it is left like this because it matches the spec - if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } - } - _ => { - panic!("Invalid GAMMA2 value") - } - } -} diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index bf871512f..defcafe6a 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -29,7 +29,7 @@ //! ```rust //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_core::traits::{ -//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! HashOneShot, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, //! }; //! use bouncycastle_sha2::SHA512; //! use bouncycastle_mldsa_lowmemory::{MLDSATrait, HashMLDSA65_with_SHA512, HashMLDSA44_with_SHA512}; @@ -38,7 +38,7 @@ //! //! // Here, and in contrast to External Mu mode of ML-DSA, the message can be pre-hashed before //! // generating the signing key. -//! let ph: [u8; 64] = SHA512::default().hash(msg).as_slice().try_into().unwrap(); +//! let ph: [u8; 64] = SHA512::hash(msg).as_slice().try_into().unwrap(); //! //! //! let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); @@ -65,15 +65,14 @@ //! Thereofre, if the user needs the more sophisticated keygen functions, they should just use them from [`MLDSA`]. //! But a simple [`HashMLDSA::keygen`] is provided. -use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; +use crate::mldsa::{MLDSA_RND_LEN, MLDSATrait, MuBuilder}; use crate::mldsa::{MLDSA44_FULL_SK_LEN, MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; use crate::mldsa::{MLDSA65_FULL_SK_LEN, MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; use crate::mldsa::{MLDSA87_FULL_SK_LEN, MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; use crate::params::{ - HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA256Params, - HashMLDSA65_with_SHA512Params, HashMLDSA87_with_SHA256Params, HashMLDSA87_with_SHA512Params, - HashMLDSAParams, + HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA512Params, + HashMLDSA87_with_SHA512Params, HashMLDSAParams, }; use crate::{ MLDSA, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, @@ -83,29 +82,19 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, PHSignatureVerifier, PHSigner, RNG, SignatureVerifier, Signer, - XOF, XOFSqueezer, + Algorithm, AlgorithmOID, Hash, HashOneShot, PHSignatureVerifier, PHSigner, RNG, + SignatureVerifier, Signer, }; use bouncycastle_rng::HashDRBG_SHA512; use core::marker::PhantomData; -// Imports needed only for docs -#[allow(unused_imports)] -use crate::mldsa::MuBuilder; /*** Constants ***/ -/// -pub const HASH_ML_DSA_44_with_SHA256_NAME: &str = "HashML-DSA-44_with_SHA256"; -/// -pub const HASH_ML_DSA_65_WITH_SHA256_NAME: &str = "HashML-DSA-65_with_SHA256"; -/// -pub const HASH_ML_DSA_87_with_SHA256_NAME: &str = "HashML-DSA-87_with_SHA256"; -/// -pub const HASH_ML_DSA_44_with_SHA512_NAME: &str = "HashML-DSA-44_with_SHA512"; -/// -pub const HASH_ML_DSA_65_WITH_SHA512_NAME: &str = "HashML-DSA-65_with_SHA512"; -/// -pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{ + HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, + HASH_ML_DSA_65_with_SHA512_NAME, HASH_ML_DSA_87_with_SHA512_NAME, +}; /*** Pub Types ***/ @@ -126,6 +115,11 @@ impl< } /// The HashML-DSA-44_with_SHA256 signature algorithm. +/// +/// The only SHA-256 pairing. FIPS 204 Section 5.4 requires the pre-hash to provide "at least +/// lambda bits of classical security strength against both collision and second preimage +/// attacks", which footnote 6 puts at a digest of "at least 2 lambda bits"; SHA-256 meets that for +/// ML-DSA-44 (lambda = 128) and not for ML-DSA-65 or -87, which pair only with SHA-512 here. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA256 = HashMLDSA< HashMLDSA44_with_SHA256Params, @@ -138,32 +132,6 @@ pub type HashMLDSA44_with_SHA256 = HashMLDSA< MLDSA44_SIG_LEN, >; -/// The HashML-DSA-65_with_SHA256 signature algorithm. -#[allow(non_camel_case_types)] -pub type HashMLDSA65_with_SHA256 = HashMLDSA< - HashMLDSA65_with_SHA256Params, - MLDSA65PublicKey, - MLDSA65PrivateKey, - { HashMLDSA65_with_SHA256Params::PH_LEN }, - MLDSA65_PK_LEN, - MLDSA65_SK_LEN, - MLDSA65_FULL_SK_LEN, - MLDSA65_SIG_LEN, ->; - -/// The HashML-DSA-87_with_SHA256 signature algorithm. -#[allow(non_camel_case_types)] -pub type HashMLDSA87_with_SHA256 = HashMLDSA< - HashMLDSA87_with_SHA256Params, - MLDSA87PublicKey, - MLDSA87PrivateKey, - { HashMLDSA87_with_SHA256Params::PH_LEN }, - MLDSA87_PK_LEN, - MLDSA87_SK_LEN, - MLDSA87_FULL_SK_LEN, - MLDSA87_SIG_LEN, ->; - /// The HashML-DSA-44_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA512 = HashMLDSA< @@ -291,12 +259,10 @@ impl< /// (modified to take an externally-computed ph instead of M', thus combining Algorithm 4 with Algorithm 7). /// /// Security note: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode, it must be clear that - /// the responsibility is on the user to ensure that their nonce `rnd` is unique per signature. - /// If not, some privacy properties may be lost. For example, it becomes easy to tell if a signer - /// has signed the same message twice or two different messages, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// `rnd` comes from the caller. FIPS 204 Section 3.4 permits the deterministic variant, `rnd` + /// all zeros, and the hedged variant draws it fresh. Deterministic signing does not leak the + /// key, but a repeated `rnd` loses some privacy: it becomes easy to tell whether a signer has + /// signed the same message twice, or whether two signatures on one message share a signer. /// /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help /// prevent accidental nonce reuse, this function moves `rnd`. @@ -311,18 +277,9 @@ impl< Ok(out) } - /// Algorithm 7 ML-DSA.Sign_internal(𝑠𝑘, 𝑀′, 𝑟𝑛𝑑) - /// (modified to take an externally-computed ph instead of M', thus combining Algorithm 4 with Algorithm 7). + /// As [`Self::sign_ph_deterministic`], writing into `output`; see its security note. /// - /// Performs an ML-DSA signature using the provided external message representative `mu`. - /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both - /// FIPS 204 itself, as well as subsequent FAQ documents. - /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG. - /// - /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help - /// prevent accidental nonce reuse, this function moves `rnd`. - /// - /// Returns the number of bytes written to the output buffer. It can be called with an oversized buffer. + /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. pub fn sign_ph_deterministic_out( sk: &SK, ctx: Option<&[u8]>, @@ -330,33 +287,11 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { - let ctx = if ctx.is_some() { ctx.unwrap() } else { &[] }; - - // Algorithm 4 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - output.fill(0); - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mut h = H::new(); - h.do_update(&sk.tr()); - - // Algorithm 4 - // 23: 𝑀' ← BytesToBits(IntegerToBytes(1, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ OID ∥ PH𝑀) - // all done together - h.do_update(&[1u8]); - h.do_update(&[ctx.len() as u8]); - h.do_update(ctx); - h.do_update(::OID_DER); - h.do_update(ph); - let mut mu = [0u8; MLDSA_MU_LEN]; - let mut h = h.into_squeezer(); - let bytes_written = h.do_output_out(&mut mu); - debug_assert_eq!(bytes_written, MLDSA_MU_LEN); + // Algorithm 4, lines 1 and 23, and Algorithm 7, line 6 + let mu = + MuBuilder::compute_mu_ph(&sk.tr(), ctx, ::OID_DER, ph)?; // 24: 𝜎 ← ML-DSA.Sign_internal(𝑠𝑘, 𝑀', 𝑟𝑛𝑑) let bytes_written = MLDSA::::sign_mu_deterministic_out(sk, &mu, rnd, output)?; @@ -372,19 +307,15 @@ impl< } fn parse_ctx(ctx: Option<&[u8]>) -> Result<([u8; 255], usize), SignatureError> { - if ctx.is_some() { - // Algorithm 2 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.unwrap().len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - let mut ctx_buf = [0u8; 255]; - ctx_buf[..ctx.unwrap().len()].copy_from_slice(ctx.unwrap()); - Ok((ctx_buf, ctx.unwrap().len())) - } else { - Ok(([0u8; 255], 0)) + let ctx = ctx.unwrap_or(&[]); + // Algorithm 2 + // 1: if |𝑐𝑡𝑥| > 255 then + if ctx.len() > 255 { + return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); } + let mut ctx_buf = [0u8; 255]; + ctx_buf[..ctx.len()].copy_from_slice(ctx); + Ok((ctx_buf, ctx.len())) } /// Alternative initialization of the streaming signer where the user has their private key @@ -438,7 +369,7 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) } @@ -467,43 +398,33 @@ impl< } fn do_sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result { + output.fill(0); let ph: [u8; PH_LEN] = self.hash.do_final().try_into().unwrap(); + let ctx = Some(&self.ctx[..self.ctx_len]); - if self.sk.is_none() && self.seed.is_none() { + if let Some(sk) = self.sk { + return match self.signer_rnd { + None => Self::sign_ph_out(&sk, &ph, ctx, output), + Some(rnd) => Self::sign_ph_deterministic_out(&sk, ctx, &ph, rnd, output), + }; + } + let Some(seed) = self.seed else { return Err(SignatureError::GenericError( - "Somehow you managed to construct a streaming signer without a private key, impressive!", + "do_sign_final_out called on a streaming context with no private key or seed; \ + this is a verify-initialized context. Call do_verify_final instead", )); - } - - output.fill(0); - - if self.sk.is_some() { - if self.signer_rnd.is_none() { - Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output) - } else { - Self::sign_ph_deterministic_out( - &self.sk.unwrap(), - Some(&self.ctx[..self.ctx_len]), - &ph, - self.signer_rnd.unwrap(), - output, - ) - } - } else if self.seed.is_some() { - let rnd = if self.signer_rnd.is_some() { - self.signer_rnd.unwrap() - } else { + }; + let rnd = match self.signer_rnd { + Some(rnd) => rnd, + None => { let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; rnd - }; - // At this point it's necessary to fully reconstruct SK in order to compute tr for mu. - // Therefore there is no savings to using the more sophisticated MLDSA::sign_from_seed - let (_pk, sk) = Self::keygen_from_seed(&self.seed.unwrap())?; - Self::sign_ph_deterministic_out(&sk, Some(&self.ctx[..self.ctx_len]), &ph, rnd, output) - } else { - unreachable!() - } + } + }; + // The seed is expanded here, not at init: the pre-hash does not need tr until now. + let (_pk, sk) = Self::keygen_from_seed(&seed)?; + Self::sign_ph_deterministic_out(&sk, ctx, &ph, rnd, output) } } @@ -522,7 +443,7 @@ impl< { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::verify_ph(pk, &ph_m, ctx, sig) } @@ -546,12 +467,12 @@ impl< } fn do_verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { - assert!( - self.pk.is_some(), - "Somehow you managed to construct a streaming verifier without a public key, impressive!" - ); + // A context from do_sign_init has no public key; the same error MLDSA gives. + let pk = self + .pk + .ok_or(SignatureError::GenericError("No public key set on streaming verifier."))?; let ph: [u8; PH_LEN] = self.hash.do_final().try_into().unwrap(); - Self::verify_ph(&self.pk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), &sig[..SIG_LEN]) + Self::verify_ph(&pk, &ph, Some(&self.ctx[..self.ctx_len]), &sig[..SIG_LEN]) } } @@ -621,30 +542,13 @@ impl< } let sig_sized = &sig.try_into().unwrap(); - let ctx = if ctx.is_some() { ctx.unwrap() } else { &[] }; - - // Algorithm 5 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mut h = H::new(); - h.do_update(&pk.compute_tr()); - - // Algorithm 4 - // 23: 𝑀 ← BytesToBits(IntegerToBytes(1, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ OID ∥ PH𝑀) - // all done together - h.do_update(&[1u8]); - h.do_update(&[ctx.len() as u8]); - h.do_update(ctx); - h.do_update(::OID_DER); - h.do_update(ph); - let mut mu = [0u8; MLDSA_MU_LEN]; - let mut h = h.into_squeezer(); - _ = h.do_output_out(&mut mu); + // Algorithm 5, line 1, Algorithm 4, line 23, and Algorithm 7, line 6 + let mu = MuBuilder::compute_mu_ph( + &pk.compute_tr(), + ctx, + ::OID_DER, + ph, + )?; MLDSA::::verify_mu( pk, &mu, sig_sized, diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 999c6cd8b..6d0e1cf17 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -55,13 +55,9 @@ //! These hygiene points can always be further improved with increasingly aggressive design choices. //! The authors feel that the trade-offs have hit the point of diminishing returns, maintaining an acceptable balance //! of memory footprint, performance, and code readability. That being said, we welcome pull requests if, for example, -//! we've missed a polynomial that doesn't need to be created has been missed and could be eliminated. +//! a polynomial that does not need to exist has been missed and could be eliminated. //! -//! All this combined, the implementations achieves an approximate *1/10 the memory footprint* at a cost of approximately *3x -//! the runtime for signing and no appreciable difference for keygen and verification*, -//! compared with our un-optimized implementation in the \[bouncycastle_mldsa] crate, which we are extremely happy with! -//! -//! # Memory Footprint +//! # Memory Usage //! //! Below, find performance charts relative to the standard ML-DSA implementation in the \[bouncycastle_mldsa] crate. //! @@ -73,16 +69,16 @@ //! //! | Key Object | PK size on disk | PK size in memory | SK Size on disk | SK size in memory | //! |------------|-----------------|-------------------|-----------------|-------------------| -//! | ML-DSA-44 | 1312 (1312) | 1312 (4128) | 32 (2560) | 176 (12464) | -//! | ML-DSA-65 | 1952 (1952) | 1952 (6176) | 32 (4032) | 176 (17584) | -//! | ML-DSA-87 | 2592 (2592) | 2592 (8224) | 32 (4896) | 176 (23728) | +//! | ML-DSA-44 | 1312 (1312) | 1312 (4128) | 32 (2560) | 240 (12464) | +//! | ML-DSA-65 | 1952 (1952) | 1952 (6176) | 32 (4032) | 240 (17584) | +//! | ML-DSA-87 | 2592 (2592) | 2592 (8224) | 32 (4896) | 240 (23728) | //! //! All values are in bytes. The "in memory" sizes are measured by rust's `std::mem::size_of`. //! Values in parentheses are the usual sizes in our un-optimized implementation in the \[bouncycastle_mldsa] crate. //! //! //! ## Algorithm Peak Memory Usage -//! The table below shows peak memory usage of the ML-DSA algorithms and the rough performanc (throughput) impact. +//! The table below shows peak memory usage of the ML-DSA algorithms and the rough performance (throughput) impact. //! //! Measuring peak application memory usage can be a bit tricky, and the numbers obtained depend heavily on how the //! measurement harness is designed. Here, we aim to provide a conservative measurement, meaning that we are aiming for an @@ -99,7 +95,7 @@ //! | Algorithm | Peak swap memory usage (kB) | Throughput (ops/s) | //! |---------------------------|-----------------------------|---------------------| //! | MLDSA44_lowmemory/KeyGen | 12.6 (113.8) | 11,800 (11,300) | -//! | MLDSA65_lowmemory/KeyGen | 15.0 (124.1) | 5,500 (7.000) | +//! | MLDSA65_lowmemory/KeyGen | 15.0 (124.1) | 5,500 (7,000) | //! | MLDSA87_lowmemory/KeyGen | 15.2 (197.8) | 3,300 (4,200) | //! | MLDSA44_lowmemory/Sign | 24.8 (117.7) | 850 (4,000) | //! | MLDSA65_lowmemory/Sign | 28.2 (159.6) | 580 (2,900) | @@ -109,20 +105,25 @@ //! | MLDSA87_lowmemory/Verify | 20.6 (211.6) | 3,500 (5,000) | //! //! Values in parentheses are the comparison values from the un-optimized implementation in the \[bouncycastle_mldsa] crate. -//! Size numbers were collected with valgrind using a simple main program that calls only the measured function. -//! Performance throughput numbers were collected on my laptop using the library's provided benchmarks, so -//! performance they should be taken with an extreme grain of salt. -//! -//! Actual values may vary based on build configuration and target architecture. +//! The memory figures come from `mem_usage_benches/src/bench_mldsa_mem_usage.rs` under valgrind as +//! described above, and the throughput figures from this crate's criterion benches, both run on a +//! developer workstation when the crate was written and not re-measured since; the throughput +//! column is indicative only. Regenerate both with those harnesses after a change to the signing or +//! verifying path. (The private key grew by 64 bytes on the branch that cached `tr`; the peak +//! figures above predate that.) //! -//! # Usage +//! # Usage Examples //! //! This crate has been designed to serve a wide range of use cases, from people dabbling in //! cryptography for the first time, to cryptographic protocol designers who need access to the advanced //! functionality of the ML-DSA algorithm, to embedded systems developers who want access to memory //! and performance optimized functions. //! -//! This page gives examples of simple usage for generating keys and signatures, and verifying signatures.//! +//! This page gives examples of simple usage for generating keys and signatures, and verifying signatures. +//! +//! The `bc-rust` CLI's ML-DSA subcommands are built on `bouncycastle-mldsa`. This crate produces +//! the same keys and signatures, byte for byte (see `tests/cross_check_tests.rs`), so it has no +//! subcommand of its own. //! //! More examples on advanced usage can be found on the [`mldsa`] and [`hash_mldsa`] pages. //! @@ -190,13 +191,16 @@ //! ML-DSA and HashML-DSA take several parameters: `seed`, `mu`, `ph`, `ctx`, and `rnd`. //! They fall into two groups with very different failure modes. //! -//! -//! `seed` and `rnd`, however, are secret/entropy inputs and must be handled with care: +//! `seed` and `rnd` are secret or entropy inputs and must be handled with care: //! //! - `seed` *is* the private key, i.e. the entire key is derived from it. It must be generated -//! with a strong cryptographically secure PRNG, it must be kept secret, and it must never reused. +//! with a strong cryptographically secure PRNG, it must be kept secret, and it must never be reused. //! A low-entropy, predictable, or disclosed seed yields a full key compromise, -//! not merely an unverifiable signature. +//! not merely an unverifiable signature. The private key *is* the seed in this crate, so +//! `MLDSA44PrivateKey::from_bytes` and its siblings take any 32 bytes as a seed of full +//! strength: nothing can be checked at that point, and a key loaded from storage is trusted +//! to have been generated as above. [`MLDSA::keygen_from_seed`] on typed `KeyMaterial` is the +//! path that enforces the key type and strength. //! //! - `rnd` is the signing randomizer. ML-DSA is designed to be nonce-misuse-resistant, i.e. the //! signing mask is derived from a secret key value together with `rnd` and `mu`, so @@ -210,6 +214,14 @@ //! verifier won't accept (a correctness/interoperability failure). //! One caveat: `ctx` can still be security-relevant at the protocol level (domain separation, replay and //! cross-protocol binding), so choosing it incorrectly can weaken those properties. +//! +//! ## What is zeroized +//! +//! FIPS 204 Section 3.6.3 asks that "any potentially sensitive intermediate data is destroyed as +//! soon as it is no longer needed". Here the private key's `K` and `rho'`, the packed `s1` and +//! `s2` it expands on demand, and the seed are `Secret` or `KeyMaterial`, wiped on drop. The +//! per-row vectors signing works through are plain stack values that are not wiped when their +//! scope ends. #![no_std] #![forbid(unsafe_code)] @@ -235,10 +247,9 @@ mod low_memory_helpers; pub mod mldsa; mod mldsa_keys; mod params; -mod polynomial; /*** Exported types ***/ -pub use hash_mldsa::{HashMLDSA44_with_SHA256, HashMLDSA65_with_SHA256, HashMLDSA87_with_SHA256}; +pub use hash_mldsa::HashMLDSA44_with_SHA256; pub use hash_mldsa::{HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512}; pub use mldsa::MuBuilder; pub use mldsa::{MLDSA, MLDSA44, MLDSA65, MLDSA87, MLDSATrait}; @@ -254,12 +265,10 @@ pub use mldsa::ML_DSA_65_NAME; pub use mldsa::ML_DSA_87_NAME; pub use hash_mldsa::HASH_ML_DSA_44_with_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_with_SHA256_NAME; pub use hash_mldsa::HASH_ML_DSA_44_with_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_WITH_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_65_with_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_87_with_SHA512_NAME; pub use mldsa::{MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSA_TR_LEN}; pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; diff --git a/crypto/mldsa-lowmemory/src/low_memory_helpers.rs b/crypto/mldsa-lowmemory/src/low_memory_helpers.rs index fac94a4a2..b6235f1b1 100644 --- a/crypto/mldsa-lowmemory/src/low_memory_helpers.rs +++ b/crypto/mldsa-lowmemory/src/low_memory_helpers.rs @@ -2,15 +2,18 @@ //! and other intermediate values by never holding the whole thing in memory at once, but re-constructing //! what it needs in pieces, which generally means handling the matrices and vectors row-wise or entry-wise. -use crate::aux_functions::{bit_unpack_eta_out, expand_mask_poly, rej_ntt_poly, unpack_z_row}; +use crate::aux_functions::unpack_z_row; use crate::params::MLDSAParams; -use crate::polynomial::Polynomial; use bouncycastle_core::errors::SignatureError; +use bouncycastle_mldsa_common::aux_functions::{ + bit_unpack_eta_out, expand_mask_poly, rej_ntt_poly, +}; +use bouncycastle_mldsa_common::polynomial::Polynomial; use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; #[inline(always)] pub(crate) fn expandA_elem(rho: &[u8; 32], i: usize, j: usize) -> Polynomial { - rej_ntt_poly(&rho, &[j as u8, i as u8]) + rej_ntt_poly(rho, &[j as u8, i as u8]) } /// Compute a row of the core signing operation @@ -46,7 +49,7 @@ pub(crate) fn compute_wp_approx_row( rho: &[u8; 32], sig: &[u8; SIG_LEN], t1: &Polynomial, - c: &Polynomial, + c_hat: &Polynomial, idx: usize, ) -> Result { // Algorithm 8: line 9: 𝐰′_approx ← NTT−1(𝐀_hat ∘ NTT(𝐳) − NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑)) @@ -56,6 +59,11 @@ pub(crate) fn compute_wp_approx_row( // NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑) // ) // ▷ 𝐰'_approx = 𝐀𝐳 − 𝑐𝐭1 ⋅ 2^𝑑 + // + // NTT(𝑐) is the same for every row, so the caller computes it once and passes `c_hat`. NTT(𝐳) + // is also the same for every row, but holding all l of its coordinates would cost l KiB + // (7 KiB for ML-DSA-87), so each row unpacks and transforms 𝐳 again: the trade this crate + // makes is memory over time. let mut z_i = unpack_z_row::(0, sig)?; z_i.ntt(); @@ -74,12 +82,11 @@ pub(crate) fn compute_wp_approx_row( Az_acc.add_ntt(&tmp); } - let ct1 = compute_ct1(t1.clone(), c.clone()); - fn compute_ct1(mut t1_i: Polynomial, mut c: Polynomial) -> Polynomial { + let ct1 = compute_ct1(*t1, c_hat); + fn compute_ct1(mut t1_i: Polynomial, c_hat: &Polynomial) -> Polynomial { t1_i.shift_left_d(); t1_i.ntt(); - c.ntt(); - t1_i.multiply_ntt(&c); + t1_i.multiply_ntt(c_hat); t1_i } @@ -103,7 +110,7 @@ pub(crate) fn compute_z_component( col: usize, ) -> Result, SignatureError> { let y = expand_mask_poly::

(rho_p_p, kappa + col as u16); - let mut s1_hat = s1.clone(); + let mut s1_hat = *s1; s1_hat.ntt(); s1_hat.multiply_ntt(c_hat); let mut cs1 = s1_hat; // rename @@ -119,20 +126,20 @@ pub(crate) fn compute_w0cs2_component( w: &Polynomial, c_hat: &Polynomial, ) -> Option { - let mut s2_hat = s2.clone(); + let mut s2_hat = *s2; s2_hat.ntt(); s2_hat.multiply_ntt(c_hat); let mut cs2 = s2_hat; // rename cs2.inv_ntt(); - // Note: this could be further optimized by using the optimization described in - // https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf section 5.1: - // "instead of computing (r1, r0) = Decomposeq (w − cs2, α) - // and checking whether ‖r0‖∞ < γ2 − β and r1 = w1, it is equivalent to just check that - // ‖w0 − cs2‖∞ < γ2 − β, where w0 is the low part of w. If this check passes, w0 − cs2 - // is the low part of w − cs2." - let mut w0cs2 = w.clone(); - w0cs2.low_bits::

(); + // The shortcut of the Dilithium round-3 specification, Section 5.1 + // (https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf): "instead + // of computing (r1, r0) = Decompose_q(w − cs2, α) and checking whether ‖r0‖∞ < γ2 − β and + // r1 = w1, it is equivalent to just check that ‖w0 − cs2‖∞ < γ2 − β, where w0 is the low part + // of w. If this check passes, w0 − cs2 is the low part of w − cs2." That is what the three + // lines below do: FIPS 204 Algorithm 7 lines 21 and 23 without a second Decompose. + let mut w0cs2 = *w; + w0cs2.low_bits_inplace::

(); w0cs2.sub(&cs2); if w0cs2.check_norm(P::gamma2_minus_beta) { None } else { Some(w0cs2) } } @@ -141,7 +148,7 @@ pub(crate) fn compute_ct0_component( t0_row: &Polynomial, c_hat: &Polynomial, ) -> Option { - let mut t0_hat = t0_row.clone(); + let mut t0_hat = *t0_row; t0_hat.ntt(); t0_hat.multiply_ntt(c_hat); let mut ct0 = t0_hat; // rename diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 066ef7aef..5881abc45 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -384,26 +384,26 @@ //! } //! ``` -use crate::aux_functions::{bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row}; +use crate::aux_functions::{unpack_c_tilde, unpack_h_row}; use crate::low_memory_helpers::{ compute_ct0_component, compute_w_row, compute_w0cs2_component, compute_wp_approx_row, compute_z_component, s_unpack, }; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait}; use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait}; -use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams, MLDSAParamsBase}; use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; +use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, RNG, SignatureVerifier, Signer, Suspendable, XOF, XOFSqueezer, + Algorithm, AlgorithmOID, Hash, RNG, SignatureVerifier, Signer, XOF, XOFSqueezer, }; +use bouncycastle_mldsa_common::aux_functions::{bitpack_gamma1_out, sample_in_ball}; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use core::marker::PhantomData; // imports needed just for docs @@ -417,30 +417,13 @@ use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; /*** Constants ***/ -/// -pub const ML_DSA_44_NAME: &str = "ML-DSA-44"; -/// -pub const ML_DSA_65_NAME: &str = "ML-DSA-65"; -/// -pub const ML_DSA_87_NAME: &str = "ML-DSA-87"; - -// From FIPS 204 Table 1 and Table 2 - -// Constants that are the same for all parameter sets -pub(crate) const N: usize = 256; -pub(crate) const q: i32 = 8380417; -pub(crate) const q_inv: i32 = 58728449; // q ^ (-1) mod 2 ^32 -pub(crate) const d: i32 = 13; -/// Length of the \[u8] holding a ML-DSA signing random value. -pub const MLDSA_RND_LEN: usize = 32; -/// Length of the \[u8] holding a ML-DSA tr value (which is the SHAKE256 hash of the public key). -pub const MLDSA_TR_LEN: usize = 64; -/// Length of the \[u8] holding a ML-DSA mu value. -pub const MLDSA_MU_LEN: usize = 64; -/// Length of the \[u8] holding an private key seed. -pub const MLDSA_SEED_LEN: usize = 32; -pub(crate) const POLY_T0PACKED_LEN: usize = 416; -pub(crate) const POLY_T1PACKED_LEN: usize = 320; +// The constants, names and XOF aliases FIPS 204 fixes for every parameter set are shared with +// `bouncycastle-mldsa` and live in `bouncycastle-mldsa-common`. +pub(crate) use bouncycastle_mldsa_common::{H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSA_SEED_LEN, MLDSA_TR_LEN}; /*** Re-exporting length constants that a caller will need instead of the entire Params objects which contains a bunch of internal algorithm detail ***/ @@ -471,10 +454,6 @@ pub const MLDSA87_FULL_SK_LEN: usize = MLDSA87Params::FULL_SK_LEN; /// Length of the \[u8] holding an ML-DSA-87 signature value. pub const MLDSA87_SIG_LEN: usize = MLDSA87Params::SIG_LEN; -// Typedefs just to make the algorithms look more like the FIPS 204 sample code. -pub(crate) type H = SHAKE256; -pub(crate) type G = SHAKE128; - /*** Pub Types ***/ /// The ML-DSA-44 algorithm. @@ -593,9 +572,8 @@ impl< /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it into a /// correctly-sized [`KeyMaterial256`] using [`KeyMaterialTrait::truncate`]. pub(crate) fn keygen_internal(seed: &KeyMaterial256) -> Result<(PK, SK), SignatureError> { - let sk = SK::from_keymaterial(seed)?; - let pk = sk.derive_pk(); - let pk = PK::new(pk.rho, pk.t1_packed); // type-loundering to satisfy the checker + let (sk, pk) = SK::from_keymaterial_with_pk(seed)?; + let pk = PK::new(pk.rho, pk.t1_packed); // type-laundering to satisfy the checker Ok((pk, sk)) } } @@ -626,14 +604,13 @@ impl< /// If the provided key and derived key don't match, an error is returned. fn keygen_from_seed_and_encoded( seed: &KeyMaterial<32>, - encoded_sk: &[u8; SK_LEN], + encoded_sk: &[u8; FULL_SK_LEN], ) -> Result<(PK, SK), SignatureError> { let (pk, sk) = Self::keygen_internal(seed)?; - let sk_from_bytes = SK::sk_decode(encoded_sk); - - // MLDSAPrivateKey impls PartialEq with a constant-time equality check. - if sk != sk_from_bytes { + // The stored form of a key here is the seed itself, so the only encoding worth checking + // against is the FIPS 204 Algorithm 24 one a conventionally exported key arrives in. + if !bouncycastle_utils::ct::ct_eq_bytes(&sk.encode_full_sk(), encoded_sk) { return Err(SignatureError::KeyGenError("Encoded key does not match generated key")); } @@ -648,9 +625,8 @@ impl< /// /// Returns either `()` or [`SignatureError::ConsistencyCheckFailed`]. fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), SignatureError> { - // This is maybe a computationally heavy way to compare them, but it works - let derived_pk = sk.derive_pk(); - if derived_pk.compute_tr() == pk.compute_tr() { + // tr = H(pk) was computed when the key was built, so no derivation is needed here. + if bouncycastle_utils::ct::ct_eq_bytes(&sk.tr(), &pk.compute_tr()) { Ok(()) } else { Err(SignatureError::ConsistencyCheckFailed()) @@ -822,8 +798,8 @@ impl< let mut hash = H::new(); hash.do_update(mu); for row in 0..P::k { - let mut w = compute_w_row::

(&sk.rho(), &rho_p_p, kappa, row); - w.high_bits::

(); + let mut w = compute_w_row::

(sk.rho(), &rho_p_p, kappa, row); + w.high_bits_inplace::

(); hash.do_update(w.w1_encode::

().as_ref()); } let mut sig_val_c_tilde = ::ZEROED; @@ -867,7 +843,7 @@ impl< }; let start = z_offset + col * P::POLY_Z_PACKED_LEN; - bitpack_gamma1::

(&z, &mut output[start..start + P::POLY_Z_PACKED_LEN]); + bitpack_gamma1_out::

(&z, &mut output[start..start + P::POLY_Z_PACKED_LEN]); } if rejected { @@ -879,7 +855,7 @@ impl< // 19-28 (hint path): recompute rows as needed and write the packed hint directly. let mut hint_count = 0usize; for row in 0..P::k { - let mut w = compute_w_row::

(&sk.rho(), &rho_p_p, kappa, row); + let mut w = compute_w_row::

(sk.rho(), &rho_p_p, kappa, row); let mut tmp = match compute_w0cs2_component::

( // [Optimization Note]: // This is one of the places that a row of s1 can be re-computed instead of unpacked from the compressed form. @@ -912,8 +888,8 @@ impl< tmp.add_ntt(&ct0); tmp.conditional_add_q(); - w.high_bits::

(); - let (hint_row, weight) = tmp.make_hint_row::

(&w); + w.high_bits_inplace::

(); + let (hint_row, weight) = tmp.make_hint::

(&w); let next_hint_count = hint_count + weight as usize; // mutants note: don't have a test vector that exercises this condition, @@ -950,8 +926,7 @@ impl< rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; - SK::from_keymaterial(&seed)?; - Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, &mut out)?; + Self::sign_mu_deterministic_from_seed_out(seed, mu, rnd, &mut out)?; Ok(out) } @@ -962,9 +937,7 @@ impl< output: &mut [u8; SIG_LEN], ) -> Result { output.fill(0); - - SK::from_keymaterial(&seed)?; - Self::sign_mu_deterministic_out(&SK::from_keymaterial(&seed)?, mu, rnd, output) + Self::sign_mu_deterministic_out(&SK::from_keymaterial(seed)?, mu, rnd, output) } /// To be used for deterministic signing in conjunction with the @@ -1011,7 +984,9 @@ impl< // skip because this function is being handed mu // 8: 𝑐 ∈ 𝑅𝑞 ← SampleInBall(c_tilde) - let c = sample_in_ball::

(&unpack_c_tilde::

(sig)); + // NTT(𝑐) from line 9 is taken once here rather than once per row below. + let mut c_hat = sample_in_ball::

(&unpack_c_tilde::

(sig)); + c_hat.ntt(); // 12: 𝑐_tilde_p ← H(𝜇||w1Encode(𝐰1'), 𝜆/4) // ▷ hash it; this should match 𝑐_tilde @@ -1019,16 +994,20 @@ impl< hash.do_update(mu); for row in 0..P::k { - let mut wp_approx = match { - // 9: 𝐰′_approx ← NTT−1(𝐀_hat ∘ NTT(𝐳) − NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑)) - compute_wp_approx_row::(pk.rho(), sig, &pk.unpack_t1_row(row), &c, row) - } { + // 9: 𝐰′_approx ← NTT−1(𝐀_hat ∘ NTT(𝐳) − NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑)) + let mut wp_approx = match compute_wp_approx_row::( + pk.rho(), + sig, + &pk.unpack_t1_row(row), + &c_hat, + row, + ) { Ok(wp_approx) => wp_approx, // means the norm check on z failed Err(_) => return Err(SignatureError::SignatureVerificationFailed), }; - let h_i = match unpack_h_row::(row, &sig) { + let h_i = match unpack_h_row::(row, sig) { Some(h_i) => h_i, // means there were more than OMEGA bits set in the hint None => return Err(SignatureError::SignatureVerificationFailed), @@ -1084,7 +1063,7 @@ pub trait MLDSATrait< fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { // Source the seed from the provided RNG if rng.security_strength() < P::MAX_SECURITY_STRENGTH { - return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm.into()); } let mut seed = KeyMaterial::<32>::new(); rng.fill_keymaterial_out(&mut seed)?; @@ -1092,15 +1071,15 @@ pub trait MLDSATrait< } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; - /// Imports a secret key from both a seed and an encoded_sk. + /// Imports a secret key from a seed and checks it against the same key in the FIPS 204 + /// Algorithm 24 skEncode() form, `FULL_SK_LEN` bytes, with a constant-time comparison. /// - /// This is a convenience function to expand the key from seed and compare it against - /// the provided `encoded_sk` using a constant-time equality check. - /// If everything checks out, the secret key is returned fully populated with pk and seed. - /// If the provided key and derived key don't match, an error is returned. + /// A key here is stored as its seed, so this is the check for a caller who holds both the + /// seed and a conventionally exported private key and wants to know they agree. On a + /// mismatch [`SignatureError::KeyGenError`] is returned. fn keygen_from_seed_and_encoded( seed: &KeyMaterial<32>, - encoded_sk: &[u8; SK_LEN], + encoded_sk: &[u8; FULL_SK_LEN], ) -> Result<(PK, SK), SignatureError>; /// Given a public key and a secret key, check that the public key matches the secret key. /// This is a sanity check that the public key was generated correctly from the secret key. @@ -1189,39 +1168,17 @@ pub trait MLDSATrait< /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead /// of taking it by reference. /// - /// Security note about deterministic mode: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that - /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature. - /// If not, some privacy properties may be lost; for example it becomes easy to tell if a signer - /// has signed the same message twice or two different messagase, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// Security note: + /// `rnd` comes from the caller. FIPS 204 Section 3.4 permits the deterministic variant, `rnd` + /// all zeros, and the hedged variant draws it fresh. Deterministic signing does not leak the + /// key, but a repeated `rnd` loses some privacy: it becomes easy to tell whether a signer has + /// signed the same message twice, or whether two signatures on one message share a signer. fn sign_mu_deterministic( sk: &SK, mu: &[u8; 64], rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError>; - /// Algorithm 7 ML-DSA.Sign_internal(𝑠𝑘, 𝑀′, 𝑟𝑛𝑑) - /// (modified to take an externally-computed mu instead of M') - /// - /// Performs an ML-DSA signature using the provided external message representative `mu`. - /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both - /// FIPS 204 itself, as well as subsequent FAQ documents. - /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG. - /// - /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing - /// nonce from a source other than the library's default internal RNG, or who wish to use the - /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`. - /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead - /// of taking it by reference. - /// - /// Security note about deterministic mode: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that - /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature. - /// If not, some privacy properties may be lost; for example it becomes easy to tell if a signer - /// has signed the same message twice or two different messagase, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// As [`MLDSATrait::sign_mu_deterministic`], writing into `output`; see its security note. /// /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. fn sign_mu_deterministic_out( @@ -1315,39 +1272,30 @@ impl< } fn do_sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result { + output.fill(0); let mu = self.mu_builder.do_final(); - if self.sk.is_none() && self.seed.is_none() { + if let Some(sk) = self.sk { + return match self.signer_rnd { + None => Self::sign_mu_out(&sk, &mu, output), + Some(rnd) => Self::sign_mu_deterministic_out(&sk, &mu, rnd, output), + }; + } + let Some(seed) = self.seed else { return Err(SignatureError::GenericError( - "Somehow you managed to construct a streaming signer without a private key, impressive!", + "do_sign_final_out called on a streaming context with no private key or seed; \ + this is a verify-initialized context. Call do_verify_final instead", )); - } - - output.fill(0); - - if self.sk.is_some() { - if self.signer_rnd.is_none() { - Self::sign_mu_out(&self.sk.unwrap(), &mu, output) - } else { - Self::sign_mu_deterministic_out( - &self.sk.unwrap(), - &mu, - self.signer_rnd.unwrap(), - output, - ) - } - } else if self.seed.is_some() { - let rnd = if self.signer_rnd.is_some() { - self.signer_rnd.unwrap() - } else { + }; + let rnd = match self.signer_rnd { + Some(rnd) => rnd, + None => { let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; rnd - }; - Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output) - } else { - unreachable!() - } + } + }; + Self::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, output) } } @@ -1390,113 +1338,20 @@ impl< fn do_verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { let mu = self.mu_builder.do_final(); - assert!( - self.pk.is_some(), - "Somehow you managed to construct a streaming verifier without a public key, impressive!" - ); - - if sig.len() != SIG_LEN { - return Err(SignatureError::LengthError("Signature value is not the correct length.")); - } - - Self::verify_mu(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) - } -} - -/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204. -/// Provides a stateful version of [`MLDSATrait::compute_mu_from_pk`] and [`MLDSATrait::compute_mu_from_tr`] -/// that supports streaming -/// large to-be-signed messages. -/// -/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA -/// does not benefit from allowing external construction of the message representative mu. -/// It is possible to get the same behaviour by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [`PHSigner::sign_ph`]. -#[derive(Clone)] -pub struct MuBuilder { - h: H, -} - -impl MuBuilder { - /// Algorithm 7 - /// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀′, 64) - pub fn compute_mu( - tr: &[u8; 64], - msg: &[u8], - ctx: Option<&[u8]>, - ) -> Result<[u8; 64], SignatureError> { - let mut mu_builder = MuBuilder::do_init(&tr, ctx)?; - mu_builder.do_update(msg); - let mu = mu_builder.do_final(); - - Ok(mu) - } - - /// This function requires the public key hash `tr`, which can be computed from the public key - /// using [`MLDSAPublicKeyTrait::compute_tr`]. - pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result { - let ctx = match ctx { - Some(ctx) => ctx, - None => &[], - }; + // A context from do_sign_init has no public key. + let pk = self + .pk + .ok_or(SignatureError::GenericError("No public key set on streaming verifier."))?; - // Algorithm 2 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mut mb = Self { h: H::new() }; - mb.h.do_update(tr); - - // Algorithm 2 - // 10: 𝑀′ ← BytesToBits(IntegerToBytes(0, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥) ∥ 𝑀 - // all done together - mb.h.do_update(&[0u8]); - mb.h.do_update(&[ctx.len() as u8]); - mb.h.do_update(ctx); - - // now ready to absorb M - Ok(mb) - } + let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { + SignatureError::LengthError("Signature value is not the correct length.") + })?; - /// Stream a chunk of the message. - pub fn do_update(&mut self, msg_chunk: &[u8]) { - self.h.do_update(msg_chunk); - } - - /// Finalize and return the mu value. - pub fn do_final(self) -> [u8; 64] { - // Completion of - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀 ′, 64) - let mut mu = [0u8; 64]; - self.h.into_squeezer().do_output_out(&mut mu); - - mu + Self::verify_mu(&pk, &mu, sig) } } -/// The length, in bytes, of a serialized state of a [`MuBuilder`] object. -pub const SUSPENDED_MU_BUILDER_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; - -/// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [`Suspendable`]. -/// Serialization of the state of an in-progress ML-DSA instance is really just serialization -/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm -/// has a pausable state. -// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, -// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl Suspendable for MuBuilder { - fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { - self.h.suspend() - } - - fn from_suspended( - serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { - Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) - } -} +// The message representative builder is shared with the other ML-DSA crate and lives in +// `bouncycastle-mldsa-common`. +#[doc(inline)] +pub use bouncycastle_mldsa_common::mu_builder::{MuBuilder, SUSPENDED_MU_BUILDER_STATE_LEN}; diff --git a/crypto/mldsa-lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs index 34bedbccb..771b28937 100644 --- a/crypto/mldsa-lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa-lowmemory/src/mldsa_keys.rs @@ -1,7 +1,3 @@ -use crate::aux_functions::{ - bit_pack_eta, bit_pack_t0, power_2_round, rej_bounded_poly, simple_bit_pack_t1, - simple_bit_unpack_t1, -}; use crate::low_memory_helpers::{expandA_elem, s_unpack}; use crate::mldsa::{H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; use crate::mldsa::{MLDSA44_FULL_SK_LEN, MLDSA44_PK_LEN, MLDSA44_SK_LEN}; @@ -13,6 +9,11 @@ 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::{Hash, SignaturePrivateKey, SignaturePublicKey, XOF, XOFSqueezer}; +use bouncycastle_mldsa_common::aux_functions::{ + bit_pack_eta, bit_pack_t0, power_2_round, rej_bounded_poly, simple_bit_pack_t1, + simple_bit_unpack_t1, +}; +use bouncycastle_mldsa_common::polynomial::Polynomial; use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::fmt; use core::fmt::{Debug, Display, Formatter}; @@ -20,7 +21,6 @@ use core::ops::DerefMut; // imports just for docs #[allow(unused_imports)] use crate::mldsa::MLDSATrait; -use crate::polynomial::Polynomial; /* Pub Types */ @@ -69,7 +69,7 @@ pub trait MLDSAPublicKeyTrait: /// This is exposed as a public API for a few reasons: /// 1. `tr` is required for some external-prehashing schemes such as the so-called "external mu" signing mode. /// 2. `tr` is the canonical fingerprint of an ML-DSA public key, so would be an appropriate value - /// to use, for example, to build a public key lookup or deny-listing table. + /// to use, for example, to build a public key lookup or deny-listing table. fn compute_tr(&self) -> [u8; 64]; } @@ -205,12 +205,9 @@ pub trait MLDSAPrivateKeyTrait< /// Get a ref to the seed, if there is one stored with this private key fn seed(&self) -> Option<&KeyMaterial<32>>; - /// Get a copy of the key hash `tr`. - /// This is computationally intensive as it requires fully re-computing the public key (and then discarding it). - /// It is highly recommended that, if the user already has a copy of the public key, they should get `tr` from that, - /// or else compute `tr` once and store it. + /// Get a copy of the key hash `tr`, computed once when the key was built. fn tr(&self) -> [u8; 64]; - /// Returns the full public key, and has the side-effect of setting the public key hash tr in this MLDSASeedSK object. + /// Derives the full public key from the seed: a key generation's worth of work. fn derive_pk(&self) -> MLDSAPublicKey; /// This produces the full private key in the encoding specified in FIPS 204 Algorithm 24 skEncode() /// so that it is compatible with other implementations. @@ -249,6 +246,9 @@ pub struct MLDSASeedPrivateKey< rho: [u8; 32], rho_prime: Secret<[u8; 64]>, K: Secret<[u8; 32]>, + // Algorithm 6 line 9, tr = H(pk, 64): public data. Computed once when the key is built, since + // every signature needs it for mu and deriving it means a full public-key derivation. + tr: [u8; 64], } // Written out rather than derived: the derives would demand `P: Clone` / `P: Eq`, and `P` is a // marker for the parameter set that is never stored, only used to name the field types. @@ -262,6 +262,7 @@ impl MLDSASeedPrivateKey { - /// Create a new MLDSASeedPrivateKey from a 32-byte KeyMaterial. - /// Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87), - /// otherwise it throws a SignatureError::KeyGenError("SecurityStrength". + /// Create a new MLDSASeedPrivateKey from a 32-byte KeyMaterial, deriving `tr` on the way. + /// The seed's `SecurityStrength` must be at least the parameter set's: 128-bit (ML-DSA-44), + /// 192-bit (ML-DSA-65) or 256-bit (ML-DSA-87); otherwise [`SignatureError::KeyGenError`]. pub fn new(seed: &KeyMaterial<32>) -> Result { + Self::new_with_pk(seed).map(|(sk, _pk)| sk) + } + + /// [`Self::new`], also returning the public key the construction derives on the way to + /// `tr`, so that key generation does not derive it twice. + pub(crate) fn new_with_pk( + seed: &KeyMaterial<32>, + ) -> Result<(Self, MLDSAPublicKey), SignatureError> { if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) || seed.key_len() != 32 { @@ -319,12 +328,24 @@ impl Result { if bytes.len() != 32 { return Err(SignatureError::DecodingError("Invalid seed length")); @@ -442,8 +467,7 @@ impl [u8; 64] { - let pk: MLDSAPublicKey = self.derive_pk(); - pk.compute_tr() + self.tr } fn derive_pk(&self) -> MLDSAPublicKey { @@ -461,7 +485,7 @@ impl::new(self.rho.clone(), t1_packed) + MLDSAPublicKey::::new(self.rho, t1_packed) } fn encode_full_sk(&self) -> [u8; FULL_SK_LEN] { let mut out = [0; FULL_SK_LEN]; @@ -512,6 +536,8 @@ impl Self { + // Infallible: from_bytes fails only on a length other than 32, and SK_LEN is 32 here. + debug_assert_eq!(SK_LEN, 32); Self::from_bytes(sk).unwrap() } } @@ -525,6 +551,12 @@ pub(crate) trait MLDSAPrivateKeyInternalTrait< fn rho(&self) -> &[u8; 32]; fn K(&self) -> &[u8; 32]; + /// [`MLDSAPrivateKeyTrait::from_keymaterial`], also returning the public key derived on the + /// way, for key generation. + fn from_keymaterial_with_pk( + seed: &KeyMaterial<32>, + ) -> Result<(Self, MLDSAPublicKey), SignatureError>; + /// A single entry of a privacy key vector. /// These tend to be used very transiently, so we won't bother wrapping it as a Secret. fn compute_s1_row(&self, idx: usize) -> Polynomial; @@ -568,6 +600,12 @@ impl, + ) -> Result<(Self, MLDSAPublicKey), SignatureError> { + Self::new_with_pk(seed) + } + fn K(&self) -> &[u8; 32] { &self.K } diff --git a/crypto/mldsa-lowmemory/src/params.rs b/crypto/mldsa-lowmemory/src/params.rs index 9952a01d0..c92e25c15 100644 --- a/crypto/mldsa-lowmemory/src/params.rs +++ b/crypto/mldsa-lowmemory/src/params.rs @@ -1,125 +1,37 @@ -//! The three ML-DSA parameter sets of FIPS 204, Section 4, as a sealed trait with one type per set. +//! This crate's view of the three ML-DSA parameter sets of FIPS 204, Section 4, and the four +//! HashML-DSA pairings built on them, each as a sealed trait with one type per set. //! -//! This mirrors `bouncycastle_mldsa::params`, minus the vector and matrix types: this crate never -//! materializes 𝐀̂ or a whole polynomial vector, so the only parameter-sized types it needs are -//! byte buffers. -//! -//! # Derived parameters -//! -//! FIPS 204, Table 1 assigns eight independent values per set (𝜏, 𝜆, 𝛾1, 𝛾2, (𝑘, ℓ), 𝜂, 𝜔) plus the -//! three sizes of Table 2. Everything else this implementation needs is a function of those, so it -//! is written once as a defaulted associated const rather than three times as a hand-computed -//! number. `params::tests` checks every derivation against the values tabulated in FIPS 204. +//! The values of Table 1 and Table 2, the algorithm metadata and the derived constants live on +//! [`MLDSAParamsBase`] in `bouncycastle-mldsa-common`, shared with `bouncycastle-mldsa`; +//! [`MLDSAParams`] extends it with the packed-vector buffers this implementation works from. This +//! crate never materializes 𝐀̂ or a whole polynomial vector, so the only parameter-sized types it +//! needs are byte buffers. use crate::hash_mldsa::{ HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, - HASH_ML_DSA_65_WITH_SHA256_NAME, HASH_ML_DSA_65_WITH_SHA512_NAME, - HASH_ML_DSA_87_WITH_SHA512_NAME, HASH_ML_DSA_87_with_SHA256_NAME, -}; -use crate::mldsa::{ - ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, MLDSA_SEED_LEN, POLY_T1PACKED_LEN, q, + HASH_ML_DSA_65_with_SHA512_NAME, HASH_ML_DSA_87_with_SHA512_NAME, }; +use crate::mldsa::{MLDSA_SEED_LEN, POLY_T1PACKED_LEN}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, HashAlgParams}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, HashOneShot}; +use bouncycastle_mldsa_common::params::{ByteBuffer, weaker_of}; use bouncycastle_sha2::{SHA256, SHA512}; -use bouncycastle_utils::secret::ZeroizablePrimitive; - -/// `bitlen 𝑥`, the length of the binary expansion of 𝑥 (FIPS 204, Section 2.3). -/// -/// `bitlen 0` is 0; every use below has a positive argument. -pub(crate) const fn bitlen(x: u32) -> usize { - if x == 0 { 0 } else { x.ilog2() as usize + 1 } -} -/// A fixed-size byte buffer whose length depends on the parameter set. -/// -/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` -/// for arrays stops at 32 elements and every buffer here is longer than that. -trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} -impl ByteBuffer for [u8; N] {} - -/// A crate-private (aka "sealed") trait that prevents a new ML-DSA parameter set from being defined -/// outside this crate. -trait MLDSAParamsInternalTrait {} +pub use bouncycastle_mldsa_common::params::{ + MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParamsBase, +}; -/// One ML-DSA parameter set: the values of FIPS 204, Table 1 and Table 2, and the types whose size -/// they determine. +/// One ML-DSA parameter set as this crate uses it: everything on [`MLDSAParamsBase`], plus the +/// packed forms of the vectors a key is stored as. /// -/// Sealed via a private supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] +/// Sealed through its supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] /// are the only implementations. -pub trait MLDSAParams: MLDSAParamsInternalTrait { - /* FIPS 204, Table 1: the values assigned by each parameter set. */ - - /// 𝜏, the number of ±1's in the polynomial 𝑐. - const tau: i32; - /// 𝜆, the collision strength of 𝑐̃, in bits. - const lambda: i32; - /// 𝛾1, the coefficient range of 𝐲. Always a power of two. - const gamma1: i32; - /// 𝛾2, the low-order rounding range. - const gamma2: i32; - /// 𝑘, the number of rows of 𝐀. - const k: usize; - /// ℓ, the number of columns of 𝐀. - const l: usize; - /// 𝜂, the private key range. - const eta: usize; - /// 𝜔, the maximum number of 1's in the hint 𝐡. - const omega: i32; - - /* FIPS 204, Table 2: sizes in bytes of keys and signatures. */ - - /// The length of an encoded public key. - const PK_LEN: usize; - /// The length of the FIPS 204 encoding of a private key. - /// - /// Named `FULL_SK_LEN` rather than `SK_LEN` because this crate's private keys are held as the - /// 32-byte seed 𝜉 and expanded on demand; see [`MLDSAParams::SK_LEN`]. - const FULL_SK_LEN: usize; - /// The length of a signature. - const SIG_LEN: usize; - - /* Algorithm meta-data */ - - /// The algorithm name, as reported by `Algorithm::ALG_NAME`. - const ALG_NAME: &'static str; - /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. - const MAX_SECURITY_STRENGTH: SecurityStrength; - /// The OID in component form, as reported by `AlgorithmOID::OID`. - const OID: &'static [u32]; - /// The DER encoding of [`MLDSAParams::OID`], as reported by `AlgorithmOID::OID_DER`. - const OID_DER: &'static [u8]; - - /* Derived. Never written out per parameter set -- see the module docs. */ - +pub trait MLDSAParams: MLDSAParamsBase { /// The length of a private key as this crate stores it: the 32-byte seed 𝜉, for every /// parameter set. FIPS 204, Section 4 notes that 𝜉 "is sufficient to generate the other parts - /// of the private key". + /// of the private key". The FIPS 204 encoding is [`MLDSAParamsBase::FULL_SK_LEN`] long. const SK_LEN: usize = MLDSA_SEED_LEN; - /// 𝛽, which FIPS 204, Table 1 defines as "𝛽 = 𝜏 ⋅ 𝜂". - const beta: i32 = Self::tau * Self::eta as i32; - - /// The length of the commitment hash 𝑐̃, which FIPS 204, Algorithm 26 (sigEncode) gives as - /// 𝑐̃ ∈ 𝔹^(𝜆/4). - const C_TILDE_LEN: usize = Self::lambda as usize / 4; - - /// The packed length of one coordinate of 𝐳: FIPS 204, Algorithm 26 (sigEncode) writes each of - /// the ℓ coordinates as 𝔹^(32⋅(1+bitlen (𝛾1−1))). - /// - /// This is also the number of bytes ExpandMask squeezes per coordinate: FIPS 204, - /// Algorithm 34, line 1 sets 𝑐 ← 1 + bitlen (𝛾1 − 1) and line 4 squeezes 32𝑐 bytes. - const POLY_Z_PACKED_LEN: usize = 32 * (1 + bitlen(Self::gamma1 as u32 - 1)); - - /// The packed length of one coordinate of 𝐰1: FIPS 204, Algorithm 28 (w1Encode) outputs - /// 𝔹^(32𝑘⋅bitlen ((𝑞−1)/(2𝛾2)−1)) for all 𝑘 coordinates together. - const POLY_W1_PACKED_LEN: usize = 32 * bitlen(((q - 1) / (2 * Self::gamma2)) as u32 - 1); - - /// The packed length of one coordinate of 𝐬1 or 𝐬2: FIPS 204, Algorithm 24 (skEncode), line 3 - /// packs each with BitPack(𝐬1[𝑖], 𝜂, 𝜂), and Algorithm 17 (BitPack) outputs - /// 𝔹^(32⋅bitlen (𝑎+𝑏)), so 32⋅bitlen (2𝜂). - const POLY_ETA_PACKED_LEN: usize = 32 * bitlen(2 * Self::eta as u32); - /// The packed length of the whole of 𝐬1, i.e. all ℓ coordinates. const S1_PACKED_LEN: usize = Self::POLY_ETA_PACKED_LEN * Self::l; @@ -129,23 +41,6 @@ pub trait MLDSAParams: MLDSAParamsInternalTrait { /// The packed length of the whole of 𝐭1, i.e. 𝑘 coordinates of SimpleBitPack output. const T1_PACKED_LEN: usize = POLY_T1PACKED_LEN * Self::k; - /// 𝛾1 − 𝛽, the rejection bound on ‖𝐳‖∞ (FIPS 204, Algorithm 7, line 23). - const gamma1_minus_beta: i32 = Self::gamma1 - Self::beta; - - /// 𝛾2 − 𝛽, the rejection bound on ‖𝐫0‖∞ (FIPS 204, Algorithm 7, line 23). - const gamma2_minus_beta: i32 = Self::gamma2 - Self::beta; - - /* Types whose size depends on the parameter set. */ - - /// The commitment hash 𝑐̃, of [`MLDSAParams::C_TILDE_LEN`] bytes. - type SigCTilde: ByteBuffer; - /// One packed coordinate of 𝐳, of [`MLDSAParams::POLY_Z_PACKED_LEN`] bytes. - /// - /// ExpandMask squeezes into a buffer of this same length; see - /// [`MLDSAParams::POLY_Z_PACKED_LEN`]. - type PolyZPacked: ByteBuffer; - /// One packed coordinate of 𝐰1, of [`MLDSAParams::POLY_W1_PACKED_LEN`] bytes. - type PolyW1Packed: ByteBuffer; /// The whole of 𝐬1 packed, of [`MLDSAParams::S1_PACKED_LEN`] bytes. type S1Packed: ByteBuffer; /// The whole of 𝐬2 packed, of [`MLDSAParams::S2_PACKED_LEN`] bytes. @@ -154,135 +49,24 @@ pub trait MLDSAParams: MLDSAParamsInternalTrait { type T1Packed: ByteBuffer; } -/// The ML-DSA-44 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA44Params; -/// The ML-DSA-65 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA65Params; -/// The ML-DSA-87 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA87Params; - -impl MLDSAParamsInternalTrait for MLDSA44Params {} -impl MLDSAParamsInternalTrait for MLDSA65Params {} -impl MLDSAParamsInternalTrait for MLDSA87Params {} - impl MLDSAParams for MLDSA44Params { - const tau: i32 = 39; - const lambda: i32 = 128; - const gamma1: i32 = 1 << 17; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 88; - const k: usize = 4; - const l: usize = 4; - const eta: usize = 2; - const omega: i32 = 80; - - const PK_LEN: usize = 1312; - const FULL_SK_LEN: usize = 2560; - const SIG_LEN: usize = 2420; - - const ALG_NAME: &'static str = ML_DSA_44_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; - - type SigCTilde = [u8; 32]; // 𝜆/4 = 128/4 - type PolyZPacked = [u8; 576]; // 32 * (1 + bitlen(2^17 - 1)) = 32 * 18 - type PolyW1Packed = [u8; 192]; // 32 * bitlen(44 - 1) = 32 * 6 type S1Packed = [u8; 384]; // 96 * 4 type S2Packed = [u8; 384]; // 96 * 4 type T1Packed = [u8; 1280]; // 320 * 4 } impl MLDSAParams for MLDSA65Params { - const tau: i32 = 49; - const lambda: i32 = 192; - const gamma1: i32 = 1 << 19; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 32; - const k: usize = 6; - const l: usize = 5; - const eta: usize = 4; - const omega: i32 = 55; - - const PK_LEN: usize = 1952; - const FULL_SK_LEN: usize = 4032; - const SIG_LEN: usize = 3309; - - const ALG_NAME: &'static str = ML_DSA_65_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; - - type SigCTilde = [u8; 48]; // 𝜆/4 = 192/4 - type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 - type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 type S1Packed = [u8; 640]; // 128 * 5 type S2Packed = [u8; 768]; // 128 * 6 type T1Packed = [u8; 1920]; // 320 * 6 } impl MLDSAParams for MLDSA87Params { - const tau: i32 = 60; - const lambda: i32 = 256; - const gamma1: i32 = 1 << 19; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 32; - const k: usize = 8; - const l: usize = 7; - const eta: usize = 2; - const omega: i32 = 75; - - const PK_LEN: usize = 2592; - const FULL_SK_LEN: usize = 4896; - const SIG_LEN: usize = 4627; - - const ALG_NAME: &'static str = ML_DSA_87_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; - - type SigCTilde = [u8; 64]; // 𝜆/4 = 256/4 - type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 - type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 type S1Packed = [u8; 672]; // 96 * 7 type S2Packed = [u8; 768]; // 96 * 8 type T1Packed = [u8; 2560]; // 320 * 8 } -/// The two distinct values 𝛾1 takes across the three parameter sets (FIPS 204, Table 1). -/// -/// The bit-packing routines have one layout per distinct 𝛾1, so they dispatch on these rather than -/// on the parameter set. ML-DSA-65 and ML-DSA-87 share the second value. -pub(crate) const GAMMA1_2_POW_17: i32 = MLDSA44Params::gamma1; -/// See [`GAMMA1_2_POW_17`]. -pub(crate) const GAMMA1_2_POW_19: i32 = MLDSA65Params::gamma1; - -/// The two distinct values 𝛾2 takes across the three parameter sets (FIPS 204, Table 1). -/// -/// As with 𝛾1, the routines that depend on 𝛾2 have one form per distinct value rather than one per -/// parameter set. ML-DSA-65 and ML-DSA-87 share the second value. -pub(crate) const GAMMA2_Q_MINUS_1_OVER_88: i32 = MLDSA44Params::gamma2; -/// See [`GAMMA2_Q_MINUS_1_OVER_88`]. -pub(crate) const GAMMA2_Q_MINUS_1_OVER_32: i32 = MLDSA65Params::gamma2; - -/// The weaker of two security strengths. -/// -/// [`SecurityStrength`]'s discriminants are assigned in increasing order of strength, so comparing -/// them as integers orders them. A `const fn` because the strength of a HashML-DSA pairing is a -/// defaulted associated const. -const fn weaker_of(a: SecurityStrength, b: SecurityStrength) -> SecurityStrength { - if (a as u8) <= (b as u8) { a } else { b } -} - /// A crate-private (aka "sealed") trait that prevents a new HashML-DSA pairing from being defined /// outside this crate. trait HashMLDSAParamsInternalTrait {} @@ -294,12 +78,12 @@ trait HashMLDSAParamsInternalTrait {} /// here, so [`crate::hash_mldsa::HashMLDSA`] takes one type rather than a parameter set plus a /// hash function plus a digest length. /// -/// Sealed via a private supertrait, so the six types below are the only implementations. +/// Sealed via a private supertrait, so the four types below are the only implementations. pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { /// The ML-DSA parameter set underneath. type MLDSA: MLDSAParams; /// PH, the pre-hash function. - type PreHash: Hash + HashAlgParams + AlgorithmOID + Default; + type PreHash: HashOneShot + HashAlgParams + AlgorithmOID; /// The algorithm name, as reported by `Algorithm::ALG_NAME`. /// @@ -315,9 +99,9 @@ pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { /// The strength claimed for the pairing, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. /// /// A HashML-DSA signature is no stronger than either of its two components, so this is the - /// weaker of the two. That is what caps, for example, HashML-DSA-87_with_SHA256 at 128 bits. + /// weaker of the two; in every pairing here the ML-DSA parameter set is the binding one. const MAX_SECURITY_STRENGTH: SecurityStrength = weaker_of( - ::MAX_SECURITY_STRENGTH, + ::MAX_SECURITY_STRENGTH, ::MAX_SECURITY_STRENGTH, ); } @@ -326,14 +110,6 @@ pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HashMLDSA44_with_SHA256Params; -/// The HashML-DSA-65_with_SHA256 pairing. -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct HashMLDSA65_with_SHA256Params; -/// The HashML-DSA-87_with_SHA256 pairing. -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct HashMLDSA87_with_SHA256Params; /// The HashML-DSA-44_with_SHA512 pairing. #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -348,8 +124,6 @@ pub struct HashMLDSA65_with_SHA512Params; pub struct HashMLDSA87_with_SHA512Params; impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA256Params {} -impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA256Params {} -impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA256Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA512Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA512Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA512Params {} @@ -359,16 +133,6 @@ impl HashMLDSAParams for HashMLDSA44_with_SHA256Params { type PreHash = SHA256; const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; } -impl HashMLDSAParams for HashMLDSA65_with_SHA256Params { - type MLDSA = MLDSA65Params; - type PreHash = SHA256; - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; -} -impl HashMLDSAParams for HashMLDSA87_with_SHA256Params { - type MLDSA = MLDSA87Params; - type PreHash = SHA256; - const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; -} impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { type MLDSA = MLDSA44Params; type PreHash = SHA512; @@ -377,145 +141,37 @@ impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { impl HashMLDSAParams for HashMLDSA65_with_SHA512Params { type MLDSA = MLDSA65Params; type PreHash = SHA512; - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; + const ALG_NAME: &'static str = HASH_ML_DSA_65_with_SHA512_NAME; } impl HashMLDSAParams for HashMLDSA87_with_SHA512Params { type MLDSA = MLDSA87Params; type PreHash = SHA512; - const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; + const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA512_NAME; } #[cfg(test)] mod tests { use super::*; - use crate::mldsa::d; - - /// FIPS 204, Table 1, transcribed column by column: the eight values each parameter set - /// assigns. `(tau, lambda, gamma1, gamma2, k, l, eta, omega)`. - const TABLE_1: [(i32, i32, i32, i32, usize, usize, usize, i32); 3] = [ - (39, 128, 131072, (q - 1) / 88, 4, 4, 2, 80), - (49, 192, 524288, (q - 1) / 32, 6, 5, 4, 55), - (60, 256, 524288, (q - 1) / 32, 8, 7, 2, 75), - ]; - - /// FIPS 204, Table 2, transcribed row by row: `(private key, public key, signature)` in bytes. - /// The private key column is the full FIPS encoding, which this crate calls `FULL_SK_LEN`. - const TABLE_2: [(usize, usize, usize); 3] = - [(2560, 1312, 2420), (4032, 1952, 3309), (4896, 2592, 4627)]; - - /// FIPS 204, Table 1 also tabulates 𝛽, which it labels "𝛽 = 𝜏 ⋅ 𝜂". - const TABLE_1_BETA: [i32; 3] = [78, 196, 120]; - - fn check_table_1(i: usize) { - let (tau, lambda, gamma1, gamma2, k, l, eta, omega) = TABLE_1[i]; - assert_eq!(P::tau, tau, "{}: 𝜏", P::ALG_NAME); - assert_eq!(P::lambda, lambda, "{}: 𝜆", P::ALG_NAME); - assert_eq!(P::gamma1, gamma1, "{}: 𝛾1", P::ALG_NAME); - assert_eq!(P::gamma2, gamma2, "{}: 𝛾2", P::ALG_NAME); - assert_eq!(P::k, k, "{}: 𝑘", P::ALG_NAME); - assert_eq!(P::l, l, "{}: ℓ", P::ALG_NAME); - assert_eq!(P::eta, eta, "{}: 𝜂", P::ALG_NAME); - assert_eq!(P::omega, omega, "{}: 𝜔", P::ALG_NAME); - assert_eq!(P::beta, TABLE_1_BETA[i], "{}: 𝛽 = 𝜏 ⋅ 𝜂", P::ALG_NAME); - } - - fn check_table_2(i: usize) { - let (full_sk_len, pk_len, sig_len) = TABLE_2[i]; - assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: private key size", P::ALG_NAME); - assert_eq!(P::PK_LEN, pk_len, "{}: public key size", P::ALG_NAME); - assert_eq!(P::SIG_LEN, sig_len, "{}: signature size", P::ALG_NAME); - // This crate stores the seed, not the expanded key, for every parameter set. - assert_eq!(P::SK_LEN, 32, "{}: stored private key size", P::ALG_NAME); - } - /// Each of the three sizes of Table 2 also has a formula in FIPS 204, and the two must agree. - /// Table 2 is what is written down above; this is what re-derives it. - fn check_table_2_formulas() { - // Algorithm 22 (pkEncode): 𝑝𝑘 ∈ 𝔹^(32+32𝑘(bitlen (𝑞−1)−𝑑)). - let pk_len = 32 + 32 * P::k * (bitlen((q - 1) as u32) - d as usize); - assert_eq!(P::PK_LEN, pk_len, "{}: Algorithm 22 output size", P::ALG_NAME); - - // Algorithm 24 (skEncode): 𝑠𝑘 ∈ 𝔹^(32+32+64+32⋅((𝑘+ℓ)⋅bitlen (2𝜂)+𝑑𝑘)). - let full_sk_len = - 32 + 32 + 64 + 32 * ((P::k + P::l) * bitlen(2 * P::eta as u32) + d as usize * P::k); - assert_eq!(P::FULL_SK_LEN, full_sk_len, "{}: Algorithm 24 output size", P::ALG_NAME); - - // Algorithm 26 (sigEncode): 𝜎 ∈ 𝔹^(𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘). - let sig_len = P::lambda as usize / 4 - + P::l * 32 * (1 + bitlen(P::gamma1 as u32 - 1)) - + P::omega as usize - + P::k; - assert_eq!(P::SIG_LEN, sig_len, "{}: Algorithm 26 output size", P::ALG_NAME); - } - - /// The associated types must be exactly as long as the consts that describe them; they are - /// written out by hand per parameter set, so this guards against a typo in one of them. - fn check_associated_type_sizes() { + /// The packed-vector types are written out by hand per parameter set, so this guards against + /// a typo in one of them. The per-coordinate buffers are checked the same way in + /// `bouncycastle-mldsa-common`. + fn check_packed_vector_sizes() { for (got, want, what) in [ - (size_of::(), P::C_TILDE_LEN, "SigCTilde"), - (size_of::(), P::POLY_Z_PACKED_LEN, "PolyZPacked"), - (size_of::(), P::POLY_W1_PACKED_LEN, "PolyW1Packed"), (size_of::(), P::S1_PACKED_LEN, "S1Packed"), (size_of::(), P::S2_PACKED_LEN, "S2Packed"), (size_of::(), P::T1_PACKED_LEN, "T1Packed"), ] { assert_eq!(got, want, "{}: {} vs its length const", P::ALG_NAME, what); } + // This crate stores the seed, not the expanded key, for every parameter set. + assert_eq!(P::SK_LEN, 32, "{}: stored private key size", P::ALG_NAME); } #[test] - fn test_parameter_sets_match_fips204_table_1() { - check_table_1::(0); - check_table_1::(1); - check_table_1::(2); - } - - #[test] - fn test_sizes_match_fips204_table_2() { - check_table_2::(0); - check_table_2::(1); - check_table_2::(2); - } - - #[test] - fn test_table_2_sizes_agree_with_the_encoding_formulas() { - check_table_2_formulas::(); - check_table_2_formulas::(); - check_table_2_formulas::(); - } - - #[test] - fn test_associated_types_are_the_length_their_consts_claim() { - check_associated_type_sizes::(); - check_associated_type_sizes::(); - check_associated_type_sizes::(); - } - - #[test] - fn test_bitlen_matches_its_definition() { - // FIPS 204 Section 2.3 defines bitlen 𝑥 as the length of the binary expansion of 𝑥. - assert_eq!(bitlen(0), 0); - assert_eq!(bitlen(1), 1); - assert_eq!(bitlen(2), 2); - assert_eq!(bitlen(3), 2); - assert_eq!(bitlen(4), 3); - // The two arguments the derivations above actually use, plus bitlen(𝑞 − 1) = 23. - assert_eq!(bitlen((1 << 17) - 1), 17); - assert_eq!(bitlen((1 << 19) - 1), 19); - assert_eq!(bitlen((q - 1) as u32), 23); - } - - #[test] - fn test_gamma_dispatch_constants_cover_every_parameter_set() { - // The packing routines dispatch on these; a parameter set whose 𝛾 is neither value would - // fall through to a panic at runtime rather than fail to compile, so pin them here. - for gamma1 in [MLDSA44Params::gamma1, MLDSA65Params::gamma1, MLDSA87Params::gamma1] { - assert!(gamma1 == GAMMA1_2_POW_17 || gamma1 == GAMMA1_2_POW_19); - } - for gamma2 in [MLDSA44Params::gamma2, MLDSA65Params::gamma2, MLDSA87Params::gamma2] { - assert!(gamma2 == GAMMA2_Q_MINUS_1_OVER_88 || gamma2 == GAMMA2_Q_MINUS_1_OVER_32); - } - assert_ne!(GAMMA1_2_POW_17, GAMMA1_2_POW_19); - assert_ne!(GAMMA2_Q_MINUS_1_OVER_88, GAMMA2_Q_MINUS_1_OVER_32); + fn test_packed_vector_types_are_the_length_their_consts_claim() { + check_packed_vector_sizes::(); + check_packed_vector_sizes::(); + check_packed_vector_sizes::(); } } diff --git a/crypto/mldsa-lowmemory/tests/cross_check_tests.rs b/crypto/mldsa-lowmemory/tests/cross_check_tests.rs new file mode 100644 index 000000000..0ea304109 --- /dev/null +++ b/crypto/mldsa-lowmemory/tests/cross_check_tests.rs @@ -0,0 +1,51 @@ +//! Cross-implementation check against `bouncycastle-mldsa`. The two crates carry near-duplicate +//! arithmetic, encodings and parameters with no shared source, so this pins that the same seed +//! gives the same public key and skEncode form in both, that the same mu and rnd give the same +//! signature bytes, and that each crate verifies the other's signatures. + +use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +use bouncycastle_core::traits::{SignaturePrivateKey, SignaturePublicKey, SignatureVerifier}; +use bouncycastle_core_test_framework::DUMMY_SEED; +use bouncycastle_mldsa as full; +use bouncycastle_mldsa_lowmemory as low; +#[allow(unused_imports)] +use full::{MLDSAPublicKeyTrait as _, MLDSATrait as _}; +#[allow(unused_imports)] +use low::{MLDSAPrivateKeyTrait as _, MLDSAPublicKeyTrait as _, MLDSATrait as _}; + +macro_rules! cross_check { + ($name:ident, $set:ident) => { + #[test] + fn $name() { + let seed = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); + let (pk_full, sk_full) = full::$set::keygen_from_seed(&seed).unwrap(); + let (pk_low, sk_low) = low::$set::keygen_from_seed(&seed).unwrap(); + + assert_eq!(pk_full.encode().as_slice(), pk_low.encode().as_slice(), "pkEncode"); + assert_eq!( + SignaturePrivateKey::encode(&sk_full).as_slice(), + sk_low.encode_full_sk().as_slice(), + "skEncode" + ); + + let msg = b"the two ML-DSA crates must agree byte for byte"; + let mu_full = full::MuBuilder::compute_mu(&pk_full.compute_tr(), msg, None).unwrap(); + let mu_low = low::MuBuilder::compute_mu(&pk_low.compute_tr(), msg, None).unwrap(); + assert_eq!(mu_full, mu_low, "mu"); + + let rnd = [0x5au8; 32]; + let sig_full = + full::$set::sign_mu_deterministic(&sk_full, None, &mu_full, rnd).unwrap(); + let sig_low = low::$set::sign_mu_deterministic(&sk_low, &mu_low, rnd).unwrap(); + assert_eq!(sig_full.as_slice(), sig_low.as_slice(), "signature bytes"); + + full::$set::verify(&pk_full, msg, None, &sig_low).unwrap(); + low::$set::verify(&pk_low, msg, None, &sig_full).unwrap(); + } + }; +} + +cross_check!(ml_dsa_44_agrees_with_bouncycastle_mldsa, MLDSA44); +cross_check!(ml_dsa_65_agrees_with_bouncycastle_mldsa, MLDSA65); +cross_check!(ml_dsa_87_agrees_with_bouncycastle_mldsa, MLDSA87); diff --git a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs index 4105b87cf..26e1465c6 100644 --- a/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/hash_mldsa_tests.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::traits::{SignatureVerifier, Signer}; +use bouncycastle_core::traits::{HashOneShot, SignatureVerifier, Signer}; use bouncycastle_hex as hex; #[cfg(test)] @@ -7,13 +7,13 @@ mod hash_mldsa_tests { use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Hash, PHSignatureVerifier, PHSigner}; + use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_mldsa_lowmemory::{ - HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, - HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA256, HashMLDSA87_with_SHA512, MLDSA44, - MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, - MLDSA87PrivateKey, MLDSA87PublicKey, MLDSATrait, + HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, + HashMLDSA87_with_SHA512, MLDSA44, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, + MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, MLDSA87PrivateKey, MLDSA87PublicKey, + MLDSATrait, }; use bouncycastle_mldsa_lowmemory::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; @@ -29,10 +29,8 @@ mod hash_mldsa_tests { tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); - // Test HashML-DSA-SHA256 as a ph signature alg + // Test HashML-DSA-44 with SHA256, the one SHA-256 pairing, as a ph signature alg tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); - tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); - tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); @@ -65,7 +63,7 @@ mod hash_mldsa_tests { // There is no exposed sign_deterministic() that does the ph computation internally and takes an rnd, // therefore ph has to be computed manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); @@ -76,7 +74,7 @@ mod hash_mldsa_tests { // There is no exposed sign_deterministic() that does the ph computation internally and takes an rnd, // therefore ph has to be computed manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); @@ -87,7 +85,7 @@ mod hash_mldsa_tests { // There is no exposed sign_deterministic() that does the ph computation internally and takes an rnd, // therefore ph has to be computed manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic(&sk, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); } @@ -114,7 +112,7 @@ mod hash_mldsa_tests { // BEGIN expected values let (expected_pk, expected_sk) = HashMLDSA44_with_SHA512::keygen_from_seed(&seed).unwrap(); - let expected_ph: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); + let expected_ph: [u8; 64] = SHA512::hash(msg).try_into().unwrap(); let mut expected_sig = [0u8; MLDSA44_SIG_LEN]; let bytes_written = HashMLDSA44_with_SHA512::sign_ph_deterministic_out( &expected_sk, ctx, &expected_ph, rnd, &mut expected_sig, @@ -243,23 +241,18 @@ mod hash_mldsa_tests { // `Algorithm` is implemented once, generically over the pairing, so nothing else states // these per algorithm. assert_eq!(HashMLDSA44_with_SHA256::ALG_NAME, "HashML-DSA-44_with_SHA256"); - assert_eq!(HashMLDSA65_with_SHA256::ALG_NAME, "HashML-DSA-65_with_SHA256"); - assert_eq!(HashMLDSA87_with_SHA256::ALG_NAME, "HashML-DSA-87_with_SHA256"); assert_eq!(HashMLDSA44_with_SHA512::ALG_NAME, "HashML-DSA-44_with_SHA512"); assert_eq!(HashMLDSA65_with_SHA512::ALG_NAME, "HashML-DSA-65_with_SHA512"); assert_eq!(HashMLDSA87_with_SHA512::ALG_NAME, "HashML-DSA-87_with_SHA512"); - // Derived as the weaker of the two components: SHA-256 caps every pairing it appears in at - // 128 bits; with SHA-512 the ML-DSA parameter set is what binds. + // Derived as the weaker of the two components: SHA-256 and ML-DSA-44 both give 128 bits; with SHA-512 the ML-DSA parameter set is what binds. assert_eq!(HashMLDSA44_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HashMLDSA65_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HashMLDSA87_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HashMLDSA44_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HashMLDSA65_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); assert_eq!(HashMLDSA87_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // NIST's Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 }, - // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The three SHA-256 pairings carry no OID in + // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The SHA-256 pairing carries no OID in // this implementation, which is why `AlgorithmOID` is still written out per alias rather // than derived from the pairing like the name and strength above. assert_eq!(HashMLDSA44_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 32]); @@ -280,6 +273,18 @@ mod hash_mldsa_tests { } } + /// Finishing a sign-initialised context as a verifier is an error, as it is on MLDSA, not a + /// panic. + #[test] + fn verify_final_on_a_sign_initialised_context_is_an_error() { + let (_pk, sk) = MLDSA44::keygen().unwrap(); + let ctx = HashMLDSA44_with_SHA256::do_sign_init(&sk, None).unwrap(); + assert!(matches!( + ctx.do_verify_final(&[0u8; MLDSA44_SIG_LEN]), + Err(SignatureError::GenericError(_)) + )); + } + #[test] fn prehash_lengths_match_the_hash_functions() { // `PH_LEN` is still a const generic on `HashMLDSA` -- `PHSigner` takes it as one -- but @@ -288,12 +293,12 @@ mod hash_mldsa_tests { // disagree with itself. What is left to check is whether that value matches the digest // the hash actually produces, which is what this test does. let msg = b"The quick brown fox"; - let ph256: [u8; 32] = SHA256::default().hash(msg).try_into().unwrap(); - let ph512: [u8; 64] = SHA512::default().hash(msg).try_into().unwrap(); + let ph256: [u8; 32] = SHA256::hash(msg).try_into().unwrap(); + let ph512: [u8; 64] = SHA512::hash(msg).try_into().unwrap(); - let (pk, sk) = HashMLDSA65_with_SHA256::keygen().unwrap(); - let sig = HashMLDSA65_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); - HashMLDSA65_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); + let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + let sig = HashMLDSA44_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); + HashMLDSA44_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph512, None).unwrap(); diff --git a/crypto/mldsa-lowmemory/tests/mldsa_bc-test-data.rs b/crypto/mldsa-lowmemory/tests/mldsa_bc-test-data.rs index 584652dbe..949fdb98d 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_bc-test-data.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_bc-test-data.rs @@ -13,7 +13,7 @@ use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Hash, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, XOF, XOFSqueezer, + Hash, HashOneShot, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, XOF, XOFSqueezer, }; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_hex as hex; @@ -673,8 +673,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // we're only testing SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -691,14 +690,16 @@ impl MldsaRspTestCase { HashMLDSA44_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) - .expect(&format!( - "paramSet: {}, is_hash: {}, count: {}", - parameter_set, IS_HASH_MLDSA, self.count - )); + .unwrap_or_else(|_| { + panic!( + "paramSet: {}, is_hash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + ) + }); } else { // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() // so need to manually compute mu @@ -749,8 +750,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // we're only testing SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -767,14 +767,16 @@ impl MldsaRspTestCase { HashMLDSA65_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) - .expect(&format!( - "paramSet: {}, isHash: {}, count: {}", - parameter_set, IS_HASH_MLDSA, self.count - )); + .unwrap_or_else(|_| { + panic!( + "paramSet: {}, isHash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + ) + }); } else { // note: we're exposing a sign_mu_deterministic(), but not sign_deterministic() // so need to manually compute mu @@ -819,8 +821,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // we're only testing SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -837,7 +838,7 @@ impl MldsaRspTestCase { HashMLDSA87_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) @@ -878,7 +879,7 @@ impl BustedMuBuilder { /// Algorithm 7 /// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀′, 64) pub fn compute_mu(msg: &[u8], tr: &[u8; 64]) -> Result<[u8; 64], SignatureError> { - let mut mu_builder = Self::do_init(&tr)?; + let mut mu_builder = Self::do_init(tr)?; mu_builder.do_update(msg); let mu = mu_builder.do_final(); diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index 85979fd52..f05e7c8bf 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -1,3 +1,47 @@ +struct Kat { + _parameter_set: &'static str, + deterministic: bool, + sk: &'static str, + message: &'static str, + ctx: &'static str, + signature: &'static str, +} + +// generated by hand against bc-java +// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java +const MLDSA44_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-44", + deterministic: true, + sk: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + message: "BE2E693BBF71C271A5AB6B2AC0CD1EE8BAA143B4D65E131C", + ctx: "", + signature: "b81e096e85567fd09d555774afc606ae1951429ea1b43bc45c98df6310f24cb81af58143a0c32294b0c2e09fdba17e72f845491e47c08c47d10550fe441f66bf48d18fbb5ea30bb7f3a8972f930dcb4442cd8394711e415ba65b1b5efd5c0c040dd17ab79584b8d130eb46d6766f918b05c6e09157c72d2b9554ecb5eb5b9cb0848fc2c16629558ae4fb71ee88c0a45b134b66a676e99c70375f770f8ee37dd9bf6082d75d2ffc0e94a6915649cdf0f76638000783b263ad71f71d5a38a589efde0e9919ab76fd950b678236b9d50651db8711c26bbcf1b99fe5a2e4f48c0f41f7bd8b654edc3e9da18b867f03104a13cd8d137e3471c9dd7cf2ab0a5b9154e0f933627cb153a37b430ed7748d924a14e50a609ce48fe1c360017fb72c820482b816d3a1cc06905003418e234e36a592bb02bcd2549311cd2658ebd31290eff7af81df4a6d31ee1423712acdd006e35d6801f90a07f109cf83d5318aa2e7e4e7d5c7d3b537293cb334054bf8ae4a2cc579b9898bf15b03884339dee7f2d9512531b610b3195b76d7b9b3256e88b36a67c316c236860bf2e044ba7a5063172cd282feceff292d7bc967f93499f71d6ba05c3dd6b11bcf30f0e93145065da0691edace80c6e98eb58a8ef71e2efe3b5d419d3cca385c4aaf8f35c3a4b0f374d962bff9850a8454137eaf93524f3de66aa0cd9dd2013f9a071ec668e0d77efb185d57faac28ce7ea0f50624d75200e28c8de77854b8f10197a6f0cc0ad6d07607d7fa7794a445443a46f7c647f9556e68ec66100d7ed885bd139b39f5b504fd6ecd29c544d962b94b21bf81dae5dbee60718c7b01318ba0d82af0f36188a556ea3140407b5e8c2dcf8cfff7572566150a15bde39e546f34d0721373c5b745c298cc0381101bdb0a652ac27bea90faa9cfd38c44539d58cb5ed863cb097f5508f51a020db28e99306d3e92333baf24d06e08b56fc8d007ce56236659ded6a0e43ff5f3e631427338d2da2e6e540292de3c30940999363340c1e6fddc907a8a0a2123777b532d8ac955d08588da2453908508d55404e73ce6d8dcdc17f94c691d1588f006a2045610ea57ab2cbb519fcf38da7a5a9f7459c4ca0d41683622a5c8b122309880ea4eba537c47a6b4788dd046bdf92b43e25d287320f739fb0dd8f7d97feca2d121529e93b82f336aef09b8fbf89445597c2823a531b6ab91bb1eed579ba94a1bf9d4bfc65836379983b8c5e298fd7f7d7fb827b802e5b3b0e3ccd8c51ae6b0fbf6ceb6f97abd76607f6911163225f156248d392af26af46d261db85f7996414f6c291cf15c59e45661ddaaa24f92cb48a0b2da5d862b266d6d0ec390b445da9e16bd5330ab48e2b4289ecc93d984ff426eb17d34162c314a99499863c8e78dbb802b0c5fa06a27e12d9741432d91bbb6fa9339dae46189d165e6dff8aa6382d0b66195333aa3b945e7b2b26be2fa899949429663b77d8897e685794683cbbf2a7d113bf5950675439e335f0a8df0e02b4730c0d3632aea985649712a27b7d492b21628fc3c3111ee3497aefd8f17aca5854a612f2e2b3ff30ffe708f616fb65ce49c85bac292874972ff2997e633a8bc39b1ef487ff6331d306aece4542df2b6d918513d611a50d6cb4a8086a009e909aa444f1d37fcd03526d6ba42ead0a2d4cb83197683b45a5626ba9fa2ec1fc24bbb4e4ab21a33355eef8c54ba5d82b72e5602bc72b020d0c8feb4d0b1518842fadffaa2e7fde8300e198cf7afeff1901a91f2b9d78671374eace911d333a91ad2ed5f6a932d5859e2acb1a09eeeab17af3d868a7d246f9cd73466a49ac5751af61fc4140b8ab1ffb60c794c10c8b07c2dae98425e04465a927fe4e88cadc0929e2a768174be133214b4b46141e1e3d8e9ed48f631e24e3efc276c35d35d20fe144e7124c7ac1772aa1a967b0af5526291d56c0dd404ad92265ab281483d6c39123f104c910b2217af79b9e9bdbf9b2561f304f51b6f406d482c069dda85a15b86d927c7c9a1de1a1036730315fc662ddd8754abd437192d240602fcfa366bd4c80acb931ae8b4775c2a2c3158471b773f5f36dd588937ba08f30b3e232a909e0d309c31510898229d5f533c72d57ded1e61c3c2815d373e2cc9dc41ab937056bce8f61373b502e480b7975b02a203b34fc57923075b1ac3df70a036ea30bd6db11ab0e58d447bb21f21cf88195427fca127ae10290b85cdab50038389786c20ea6b3a6c12920fd546de742f68ecf2965c1f7b01ca361da14a6799c7de0410a250bb62ddf26b0514405f6ef7721b29fc8b1f751453c7629ccdbc40c71a22e5e3948dfd09071711043dc9e483a34c31dbedc55be19ffce85a5055062bbbfe9608b93faabe8fee086832de8ca6b9f4aad3df54a8914b2bc37229ff49e3bf014d758d6cfd5e6665286e0e120ef9322dcbbb40fe2831b73cf59612b13fa62e62ed8419a4323c9ca406eb1d7110726d3e4e3825b3b5e50f72c7fbe92d154184e5403895ee9c23173a98816c13a4726859898485a1b8f36c29b2e5951499d722575dbf80775cfeab321f6967569022452394abd2c6e9f8ee2fd70a8b9fab3bf281abe6717c2fed16472167766af0ebc92b99ebb89649aa35e744bb9249a44b2c12cd50fe3e6e4274c7047967ba777e7e0aa7c600d2131cd5d536cf8e7834d0d93d8ae028b19d015a1bc5ba5dbb1031e1e2f25ad29fbdc42569ed4554aae8efde1543de9eff9cd04146f0e315869722dac4ad06663c154890f8dddb4075bf7268626a86dfd42cf43d9681cf399fd2cda03e2696ddbb713bd27b1c16193908898a04db43e960536755c0d9ccbaec637c0d8d6462474a11267291d863cf29fc09dd549307305e20e7acb1c63de3378aa74cf08807472f45f20013c0275d0003bcda5cce48d8f52304970f34498b567da4409b71725772bdead54f9a9e907c9b0d989599f6d0ceccea03559f56aa18a5084ada636989e80763b986a3837d360b75b644e9a80174aee3abbaf2483b3e1945f02e1f3567ed1f60f2b2a3b62ae0d6997ef62024b63639f1b63a84560ded1cb35e449f87229f535534c6a403bcee4aa402b98f3cc15dd3c833d9a05c108febceb0de6280af073ca03bfc6f7b99896bad33a580c8c74cac28fc50e4da4e0f3ba173e122c66f95827b1e343488f5387ac4615a196bcbfbbcda0f78aeff9dbcd6d6217ebedf12f75d6c22ec2d846322fc50e9d6a061b46e1be96b45e806f646399c6973a94241f7aff272c8c08c38002071e58ff05e2368a52c71e3013d06efecb566474657616465787e879fbac0cdd7e90f101c474a89939b9dabacb6c6d1132048536086878898b2b5b6cff80f383a4248658c8d98a9cdddee000000000000000000000000000000000000000000000000000e1c2a37", +}; + +// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// except I added an empty ctx instead of testing sign_internal directly +// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java +const MLDSA65_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-65", + deterministic: true, + sk: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f00", + message: "F7DB318231EFC5250F7AB18DEBC0340C53", + ctx: "", + signature: "7c8bf133e9d1373235f1fa3602be8d20505104c417105f612e6ac620f87e2343c45994fe4cd8f8f1354e6009e1e329dd972c1164520d4f21a7547a9696ca3d1eae8c1ddfc2ee756bff0dc31d95a9d39d90bc785769dd314ec227b55769c4796ddd00083d98dd9d7cad97083edc991dec8ae191d899a16c67ca65ef694921b924721ac1a763ab7fb607d1e434f41e3fd2b4cd8e39082a722acfca32f753fd576546fb3323cc18a6b8a441a8ad1f10f234a766ceaa3246d4e422915ccbefd6fca64b60d503d713480d0dece9ad7d93c56e2713620787813d7d14fc06a9a24a3ef4106e3075ab8f3acb30da128776cc9d98368bad562aecee44b7202daa11dd2a839386a15744cb8ff8bd5d9a71d862a729a86d1a3863a4d05bfd050cebb152728ad303e476fcd5b1e1603ec4f501635b750c92d9095ff0f0872c0c4ff8a5c476dc0d1252af6ead3fada16de77b887ab2cfb42885ef0af0d66e4e32fb255b34bcda02265c3d630c1baae069c0d61641abfb59fe50a146ddc082ceb2383368edb90708e74e4989f715dbb667efd4326e18efa563eafdb9b07f19ee13b242027473032da29b964496b2643adf94c9901cc846bd9a22d670523dd80ed1f9eea1fb6fc516b45ac937ade32ee739836c4a1520394cf012d1f38c4770c992675a367efa89c8d6cd66f6715412cb0e2e73bf00db2330a76d4e2468f5b18e73c416b12f986e2d2b76a088a42d9f5d1f1a1cdccbc593c203e62aa11febe7815d50e42f7d211c1f4a192ac27ca23f7b0b2cacd315380b190d36a52833b250856f40bdd5bf5265dbbf43488576131e2707272f32d7b7b11c91e6dfff4b28a8ccea14e6794a144d62a7562f563332691eee2123ac736798105a4066e7cfc6fdfe411b35b9a42d507b3d628c53d6d8ceca360c4c97d4a69b4537ec0e1335f7b03761c412da57f28421fa8eac4d43b4258270399554c0a3b94521c9d43ecb15f75acbc26620f57204108604d9eef0f833d329b911a87a3ff64083845810404c5f6559503f8336ffed042f9fcb50f9379a1c736ead4cad92b13eed19aeecda1b3da2ffe879f580a0e1db9e6fb3dfae6eacfff7a200d70295c781e237d2eabbb5caa02a742daa8429afe451b3447163822928c0445a35b714a19057aa5f2c19015bcfea3a829e890c49053b50c9a1851ba409b4212cb65130763dc576de1c74929b761e5deb6532a5b2d2ef01f58f7d29943c8fdc1e9003edd75647f0f244f81a69f0331324dc137ec4570e742f3d92a46267e8775c530893253693a324440f882a2d5010992b1ccf5469ca5dbbe59c7d06a5659ce1360e5b25c93142590afaf6e3604b796de9bd2a995e1255cbe4b3fae1547b2d07ed08eefc55656d0cf03e366152b6230b7647c334fe5679f821edbb5499462180585888c0b1ecd8f55bea2a3faf819701725b4ecdc7ad208913b57c2fc93c6303f15c1c997d7f063f71084d1b20d12a2a73ba2acd47ca12bdac2b668bfa4da38adb2148a75a8dd8331642db1648a628c403a519caebfb2dce4345bbfbcdec2a8d2505091f20d5b996a3b49979bbbc72e9b4c260af554dded8eb3cdac15439bc0442120335ebb3059c8e0cd7b4422780e515c706d773126d3c1990194f80a35f4d4f0ea6b728b0fb1779df4aacedab6cf4ce5fa07c30d65d1df611b6c92a093e4e6a7c3e901403b3877dc80e10580849a14508d141705f251c39e01f5a82b488de06a445716d3ef677c44a5771abff063bcd56f08afc5eed37db0a58eefaf4a21913a9cd32635995e3dbeaeb7aad09447ad6935b523e5b52eb8c6df5103958b624282f933ae6cce7173e440a823047d06074367fd2d3dd874e6d2134bef9b397d8a4a7a78f7d65540eb0444e4d7240cbde6147960985a9a89adb4538ce2cfe0d09fb5b87f2776b422b397ec459b86b7714242a94104a7e20d184cb3aee49345435c7de4babbfa8d48a8da2b2b8e262e787655034ff2878e90a85765d6b4f1156a68f26b70497d93938e9cd44b52a22e1b2333bc5a7061a6c6b9112be516e80c5fd8cc75b7c69adcc0a67e917b868b010d71b22d992664f716a64656b94e578866d972cb1ef1aba767eb6e9138ce138b2fb52edcd3cd4b70f18eb9dc3bd1311e149db677d78441cd7de9acce04009d63bd0051baf1ea8bd47e009cba83b6ad08b54467fc675f03fc502df3f3bb558c8bf3e0855a892825ebc3a0eff081b7a800b6f89e5b55446ff9afaa96fe967f59ed1afc560fe8388fb1e73829de5a38a1bad0dab5b844e99e9bd5fb5067050f0ddb57d567e7fca87b59fccaaaa9ad0ad687b083add39f456e77ee00d25ecb67e2336cc915c17bc50fd1435f099633a3771810a0ac0a9f02ed57334ee6f5f8bc99e14282c8104324e0bcc93183803aa30e24410d0475139119ff7453e8dd5db49bb33c1d83d894fe9bc0235223d566d4ca1b232326fb2d2cb994588fdb00b9a15d3692dad7a7191d5e93fd0edf0f73027aae76311b35e5ff487c37b329730bceee0f348982884409ef6c41d85d9b08e73b4056c159ed509f99cdad435b93d079df463cfee11fc25d5709264cd0cb764e020067ebbac3f16c00ac038eba776bc5ede1d96a8e28ae3dc33d8cf7c74c10172bd1ef96f089f11510795c3071b511c9aa7d71401a3f25e6cf0736d701d62a44bae6c84d25dbd487794783cce286960dc58e8b5b386ec11c49a986aebd599882d3956344b0d6bd793464ea2af20d79b66a18c1aacdb03a980b15e2b91c842aac41c3c95a1d5e88f8497c94144cab7473a136ea4b4d6fc5586395c1581e19125001049bd137e16183adf98241bef207097842185204193e1dbca9e8454a6d2d3c7cac267ce9ff2abb0864378296c578f4f49b830b4e48bc5dca8a823fd1592e05295fdde53c30f2dbc878d316e1332e92d82c7dd10a5e4b8154257a216f19869ba66da56966fef97ca49e41d1b346f9653e002a62750b8760a2640d6aff21caf57f8794ef959efceffc4ad4e53e2e09613ef0dcaefe630cd2501de62d65fc3fdb5568b8373e782ef4f876da9fab513d695cf324249fd6d2080200d039c4996fa62c7a3703b2725c0c54447ff8dc47ea875f0270ae8c900bf2ac32b86ca91540aa3b59f2a3c9f780b0f50c440e61b6781f16826767641049ea3f4c70eb3e5ab13936f3a501cb8909e6ffa25a4cbe42fa8c6691333adf46477bba3e0af0dc6d0389883f3e9f764e3418a5fa8ca11da24f640e38bb9b62017732dbdcf4dbf262d9be5dc2eb4e2d2645e4e8f59bca3db108672b9c8f33d47dbff755e2ea6a9f79d96a3228972ca45b7a4cf99fd02bcb4857a529d90b37743a97173c940444a4c74edeab33cf4045ec0c8fd0fdde4861f6706fb58483960fbb75fce1ecff890986a8488ab34f43a528d93b65fd839063ff85961ea21b211eb937e71f3f8d192ae794b15c3391f4751238396982acbc1776b8bce572bfa3b919fbac229c6d395a6d5352e470b5103948d11a167ee921af402f2fc453c25ec232a3157d0bf1b5822ab751c8a4cd60ec4d0f1690b9c8196739537c146e120e822e334903a7509d232fa462cfe9af018b175d484a8404f20a855d52f64e8ba11691c89ab63ee1f409aef036c048d165e8b9083cc42efaeaaaefad79a9b05a007982f664cdfbea191f7cec4d9c4f5f97e9cd809b1ed084d3ccfbf323c4fae45fc19b8472fc186da8ce7f9b4f32151be0dff2f29ee4b9fb8f297cb320cbbe519e2e91f7c3fd920dd427e7b7882b0e0d8176f192b916e0019abc55715d5b77ad9b9b17fa38d9ba78c38e95169bc5fabeee2f1f8ad4f834e0a19159b6f5d538606faae53817a912b12f356cfdf2b91df0817e27f10da74cc4366e100f35e5c1b4384e42dceca600d771fe5accce796fcd3bc950d9741473a8d6ea172debab3a75d95992f22cb540336b01c4a7ad5972debedf8bac9e34379972f2b80af7d870b096ad05cd4a5f006bc5816ece674f75088f8c5df543433724d7f57d868ba820576e9efea7e019728ed54bec5578e6ccad58905b2a0327d071a8065d7539bbd41b0c25445a7b4ff97ab5ac7ae9ef14b417216367bbec33ba76aeed5735b32463d1fa0b4ec1ddf86f3b24ed2ff4e7602967cfcfdd314747ba01bc522f7f0585c67775a513e64cedd145de0093ef9cdd9137ca397cb187fb5dfb26a120f65ad1e1376ce760ddb65d4e54cf5fa301b166d26253cebe08dfa5416c3df31b4e15669da870a5c5091e834757200f74ebdff05693258c98e3248667646f8e8395aa68e667ec31fa40554a34cb64976678e29fe036ff9d6019774700de214709213f9f5ee986054e249a5be3373400d911dc64128c072bad2ed84bcf3ee67fcea0837934171362b69dc697fe21602f5dfd47eb87658bdce859160c120df5fc3cb501d412ec3879612b15108936d2b7f4c5e11ab8a42b4e0e3118bfd9d0c4069fda0eb2a0a8639a0dd8fa7b2d9330e93fe374259e6a59e94bb5c2f439eb6ef9ea05fe15754ea983d769fac709d6a8f18a1ce47d02d6f0bc7afe67c54c4923d24b94686f6c7727a98f5496bc0b78d0d1199af7db4ff80db465de414a9c2ddee313c53727daed1e00143548f9da5b3e40b85d5f2088efbfd537abacee1000000000000000000000000000000000000000000050d15191d22", +}; + +// generated by hand against bc-java +// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java +const MLDSA87_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-87", + deterministic: true, + sk: "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0001", + message: "3F8599C466713387354B419392EFED39A9D76C0B5E45E079B3DB", + ctx: "", + signature: "541deb5ddb0e92ae138afd5e86f1989633e8f078bcf8218cf2bb75cd248b289d5297b7fec27bb45e59a26e85dda77cf0fbcccf5cf262856203453305327554fcf6dfb293a0c58d4f45a0c79c9fa2c0297744e18a554237dc6ccbabb581470311a3f7cf89ad2ff13dc9c4e88aa31a6571fbaacac7133d89624ff7e5ef43d51cf7bd1b826d82fd6dd66bc9f42d651ea3903e686d30816f808cab060aa452d41b39a4f31b9f0f643942d98390cd445ed85305ed16900cc3baf8a3632878e2343cac3469b8ffc4c94031618f3822eb478aae832c0473860fe3fd55dcdcadb65f9696f820e8cd9f63cb5715cd6b6c52d545beb6843fbc75a8cdce07bdfbfb548d749e76b6e557fbe12c781a19eb75c4568b83318f119cff2461beda087e5f7c1adf09d3b267ef24f07093abc365508c9f3dc5be04c8ece633fbaf57931b10886f1497a29cb8e46fe438cb59413fb374ee1e324cf00d89e4500ac4c0274978e1d757b2ab071c61c739afeabe207da69d041245d286293fe3a3fa41593d4b3f98a30c8660a005c487e9c86faf6a29955f7bac1654b34f8688211ca6060dd4f380d5af3f412ba8d0260c2f772c2d17db2629b0a2adcd4112d403df99391ef15bb99a577d20a69590284cf6e0be1794fc246a29d6c15c21a484aedc6f52987579a14bcfea14a030c2646131527b2aa4e8b17c1b5e91fe8ae4655e6d40d9898ef2b8b17a37cca3a6d73cac55247e2ac09d12644095a2dc89047e5f110aa0eb4b47a9f6bdf017cc671d82d9199f300a6a91aa9396053450e6e07bfc9248f8a7b65f02daddc03c91a68167b622f44dbe744baf9f768255c816862d990fa53237f8d7ec495bc0ba1e72a90b83c565101ea538dfc4804e72bf6f9dc05eb35a7b2f04067d8be00041526f46d8251542e97e9d403816c96e17fc647db3bf1c00880855caaf857d8a9ae76dae309486b4aa96d47fb15ef645fe1ca8a4e52dd567610c2a8aa04ddc948a14628b0a652ae5c0b4621cc59df503fa2cae7a3b5433bb1837767e6809291cc3b8b09c60064ca512fefc3e52b62881c40c6e655a7dcd8528a41f7c42ad6b63b2cf7da1fa274474f01bea6a2d0832c2c74e758f0061f3ec15513fdd559e6b5173662a648bf0ca9054edace5d97c434032729d82ab910382131fe267df78ee02cfef45edf0c9ca1b090e122011f10953021be469d0e62e1629ccbed6706124b6276b2ea77082be43a84f7e73c0e92fae90ced3c057f0acf35bd5e2bbe20f2e6459f00fe80efdb35d41c5644816220bb39309cb7c1923b2f609e245e43ee5291dbf58cd24e443c05576cefa5cc774d93b5a62c3aeb9e2c2452e1a2c0b949fc02a36d62020049d62a03ee5aa8305b7cca9fbd21c94c7fac58602872ba9a26f8ec23a1ec102528fe6c0e82339ce37173fc3ffcaafe537f41ec8eec089e06d9e49c40fd9163589a95ca74d2ccd4650ec971bcd9678ce2f7df94f74c745ec6e4377340079fd670ede55d1d5131bb7b5c4b64903c0e4865e3d43003612b602d5f5947471d009b687d910dcf7f2e807e8acb56af5d6bfcfdf5bfbea1dc4f860a4d4deaecb305e3e32cb66816394a9b9fdb5fde03b926cb50788a704ef8bbc919464b2bfe28b34ebfb7cfb44f9040daba70ee4163b52c4e2144d435d1fc29718aefc27bd82966fc65cc058df4385635df809d91e58e147914bb694426d08f71721a4b65c2cc6180425b224d0924c2c8f6514544ebe11141715ab08ef48e4bebd90d57b158d79359d598b1687df8754a4a821384d425c60767da977afc87b304f6651553a2d0fb01909f7715034924393408bffe8095b8d27eb8cd181adef31e38aaec72168ac1942502b492957191327692dc56d78706802b229d9d9e9ec82816c7047f9458972c1a24b55587d19225cca849295ca8d07d8d5000aa2c5c1bc1e2f39f2d6fdd0ba95d5f6239b36cc86b4cfd62d44bf9d299fc1ec13cd748dca123c94f0be8a6ce2899e8e84d9b5289a2bf0ad1f69645af3385c250029a9d1eaeeee1f22dacde920f8f66237dae504d4c0c42fd92c3eeaf8169e97b238aec66326d71a2374ba42083fd4b09708f0cf493748ef03efb5962685a50693a34be57e3c1d036ba9630a7f564260fd1a6852c2e883a9e0554aee9d23ee4cf2ed53a6998a2e0d731ebb847b1294c1d8067296fd03bdafc4f875cc60581baf81e9f1dfcfc4922acc1673c7896e8fc5f16bff3a6d5a83348fbfff1d90e9d14fae4024a58cf71803da38fffdcb5ed86a8cb91d8480b09f813c651773fc09ec86dd8fbad25ff1a5a7773664c3b8584892f562f77076d46ccd7bba39f4b9e5a00b6af1f10f5b870ff1f2ac686e6cc720e50942d8dd7099742a523dca21208d5d21b8527831c2a3a780ab18168f2e5ef43c7dc514ea719dd70c1bc3c1aba4839bf4ef55af560fea35dc94a4052d7bc8b68da81e3ea636ec210a4c3271d0aacb5913d2fcf87bad72820faf4fbb0d09724247446a97f8386c19a75cd9d29d4b0ab9f03f7c70a3406c2bdbb312666dc91efd420445a469466c2a2024bc1eb51e16b23bd075e8733eec07e64c7264ef497cbad5166eff63e2024b1205da225821482e30631abb401b5f0374c5ed7062be3326be8d77ceb29358b224b1d36d7825ad9d54a28ca66c64ff560af353413956837086dca174da510e5c529f5f0fd525eec5cf7240479850bd5618040f4ae961dc9a0b11b7b0afd530dbdec922a6ef500d83c0d1849055eea0eafe3832e58c7a92cddc495596ba5223820055fa76b690b84733c26f92784921180e64a8139ba12f4621255debfaaced0c1ec350bfd3560f76c4bc9b40ddcfd8b23f98c92b7c7f1ce1a1aed811a05eef9602a8abbd3009340788230fad7ffd56af02b0a4f60eef9afe84825f6c7f9f9fc4c7385e4de6da903c5f5bbc55894d2f2bcc0908c3d42a400dc6e8a5f34c291ec388f705eb31a186d85b3c91baec79eef0edcc4c1cbc97e7fd6307f865c36f9b5cc608ed7c5d022dd7a4e1f61cbf78dd2c129d41f0e9ee867d87bde240c37eba47a579576dd9489ae43ed23461b1e6043e29baaaca561dc80f97b26e4b308a4c8c9f9fb34cc19fd087a4e1ac83d677087a1d9a672eda63a0699ba3635707bdc6238515e82eae01e18c431d5763ce30d9cdb560e0325557908dcbb016461d92f48cc1260562a66ec2ac2033f45b96d859e0b4249fba2f8a4193c85009c9eef7de6064fcdf613361814d0c5b228d6cef300c4a0d85d68e559a6a080697ce216de5e6925a2eb8e5f5879f352618c686ca66d81e867925cf181284053916cc51f94272122d29062d30c0652d16f6d95575d92c95eccbef8f2d8e2b3b38864c3bcbd0de7f68ae991a72120bebf34821bfefa13e3bec1c1d3528dbab24a5ffeaae5c9499596e47e1f8b47f15e4faed41094eea13b3b661fc61e2521fd2970c691c661c471da9aa65cd768acaa14f5ba93dd966541f5d01f8970a39497e2b6c7d3dddb1188c3525a9d711efb622503356ee3f80238f01bfc165cb26bfed9a15c343354adfc2591d2fe8275aebf93f201463a9f7b5543e8430cffd57e84d22baf5c471f0efb76ebee62272cb27da66833a78f00a36c3c4b35d69bbeb9d69cfff27e66fa16b843293ef9009142c3469abba381fe7aac9e75aab955ff912e9a767f46f68f4a26f8c0a8439040187409e75e9e509796a67025053179913c729c7fcc27bf270a4957b0cf979f2be75bd1c11e5a474f2b7f5ba78ccb9898b5b99cc42f73c2179e43188c47a3d5e84aa75783e9db05ffc49ac190ed840217c34173ce90def9c84aa1c055f8617ccb54f2029c0705b8010a943df665f39e0e5430ba1a4b20b0ed4f92913ecf2530387921ee206262a2c49d20e0f027459b54a123bf1d3ab06e9237262566cb9b1f2b6bc85f4e6105d180e4cfa7b6e0c8209590c9ce5dab89867e538a4b8c1060f3364cb6337a6b5d4d75feb88134a1e3a19571e882d549b38ad6d6ba769ad4bedb041e744ff8bf2afafce2d64d73461388998073ced7af23787d99c650fa17683c26c89cb408fca9ec3a67261118297b67116a26af94afc5866ac292cc315f13085e233c1eb6d5522f30d4e1e5d9480bd15c5ec9cc302c7789d57f51874f8dc7a79c007fa2de15d8eacc8696e5d35d4a65d10840e95594dd655a7c51c80942d956dada91d01efe3acd736fa56431958578d0c443e66c7519dfba2e7847338cffa0bc947df28f5b2628116c7d6808217e98d78fc00d43f2b6b0a20c722362b5c02ea840fd19d9ca10f7f6cbf36369e80d16f2dd310e7c5e3d43cd8d9214bae2761a5e77e73f4c626ee807bab14c17e68315241bf6e632b546729d9b7d6d589dbf56306afc2980f0cb932ea0a5abd8a992b43cfabee2cff1d94935b8213d4aeb6c7416e1768d148376996e0dd17284935eab9645fe4ef638e2d041044d5a19fd35ec084a838b54e5fe7c63afb800fc043a666e020dece7fee30f5d1ce51e0a6fba97745d28efedc1a4ca20cdc1c65b21472456522e7069a52579fa71ca3866d24dc5abea774c737e05b3cf00cf6e171f8740da20b4c04d17de786534f78bf22fb66879cbd8b507fa953f74bcca3473e1d837ff4079699ae810b555460a4ff3896c7d5e234417c9f4555325a4f2b968e7e1c5fb425571486d07e80ea23a4858bfdee29ccb301456484f29a3c86946866166357bd9225ddf715a0030ee384c9617935fda2ce99928644dab3dbd5ca48108ce00a3ee8eddcfda8fd93200ee6c7a162ca0f400a40d9b3a82fdd117880b53986ae7e75ecc0a4cdbc867d88d3785c290628509ab671f670c8ff79d748f5e5ecad187b53aaea14c75979a3023013a8678ec4c94a4a8467e8ad8e87087a7f321d1574ccfda16f98434f639d9b2a5f1cd600eb7790343feaf262c7750a7bb5539ceaa82d06ee8617e224a7313b8b343ede0fa660e8b3cb7153ae582df86c13b22fbaa09f01c02061685d729c52a1ce778b7bfe167a80d70ecd91fba455a1766ba7709ef293c3cb51372ba33a624ebf490cf52f46f49e71cfa38602201a9f2671d723ca79f5e267c9ec12f34ce7f382f7726070f02bf330961e587fef4bb3f985df8228ed4ec95a0e9dc45ae394dd4de1f526c28c025d1de1f2e606722298c3103f716cf38343527520bf17e3383d4961179de7b28db5e25f90e83e5cd8a33146480eb9762b58405d326f44de501465aed7ce1f97b2bc8f5c1cb4c206b0f7a2f79f7806b5af2a4de009114541d6404fab70100b75ceb0deaaede4a260582a32a52400436ece37e1c85d4563a192d7d35a9a39fa3a0494805f4020f429f89e90e547a5d62a59ea1220bfbf4189f8a2d6cf00a4daae88708f0352c7627aa2a82f87da376c3435567b8c707c4540ae79d344101b5d38c36a35c34e8766ad30e44e45bbe98bccb7725ce095b10a749964e41e6d21e4de19cef1a0b27bb7b1c528c59170d5ab20ab8b73ab97611543f42806ec13a8b3d0543069bee738dc9ee796eb776ee7bbf2677d44798938f9cfc2fa7322d82f36a1c0cbca0f20641dc86f02d7f6a0fc0cdea441419083f621a253a3b58b50a3ac876b272cea21a7acd7648d356e720e39a0dc3a4a95201033eba587337c961700c1f4cc72d0edd38b5fac1c30c33881030c2d4805d8316d6693a3f61c4401604e0aa83be5d4b0585d9d2cb94dbcf95ae4a6a6262ff968df9a20ca00f0315e7742d6c66ce361f00f1e6e46270c08f499e81edc28d4e555add5f7c1b7c9dc5426e327fa50a272e39a88ce4a64add93c639f37ef9d9f35ff3d2c9b9edf8ab6cc2d1fc20b903dabe4d07641df5acbbc5a702c2a78b74f7b6879e3852cfa7fc801705814418afddef12cb1c18550cda8ed00a878e803478db2576491d0c6be1451433f5c526eba05c67026e6613269e9f879d388a0470ab81ecbf4c5cb7493647d077eff273c15375c32d12301b9fac37e1f5be7adce8107efd9edc9c6c2ff7a53005a0f96d26b9d59fc6d61bcef7ed509bf1f7b1ca297bbba020a67e41a3fb11ff1dbda68028ceed966ebd7d8afff42dc65f80ce63d14cd599f2c71e6ee86053ed6fe10722f727575b692f86ba4bb7f247ce4afc67aafa5a4949fd9c29f0bdacab2ce7c3c74714ff34c5b47b37e991a5f317863b4a4c279f3145eb3488a1daeca03fecc66fbcdf44770851b281fa91358bca53c7cf948b79d456459bc08c43eac0d12d75e0d94a79bfed8605befad8628573d4c647a5cc824bf3901153d34cff9d1d23da7bfaa482f76031a5cff7159bf0a9906d9166a5287e6234040ea27279a24238af98d0f90b869151a1eadd9016204fb18cf03b08aab2e9a680988e39e4240d4f7c86a24c08d2bfc75287ea9b20906f0a4e7577da2c78f73402a7c20bbf48c7d85291548a7b212b5dfc09381b75b607a9b9da0b3bcc2070d2d98f21d4a7881060e20466d902f444f707f8799afb3c6f631747abdc4e2f01584a5a7d8dd343b3cabbabbe3eb0000000000000000000000000000000000000000080d111722292f37", +}; /// This performs tests using the public interfaces of the crate. #[cfg(test)] mod mldsa_tests { @@ -116,20 +160,28 @@ mod mldsa_tests { tf.test_signature::(MLDSA87::keygen, false); } - /// This runs the full bitflipping tests and takes several minutes. - /// This test is left commented out, but feel free to un-comment it and run it. - // #[test] - // fn test_framework_signature_extensive() { - // - // let tf = TestFrameworkSignature::new(false, true); - // - // tf.test_signature::(true); - // - // tf.test_signature::(true); - // - // tf.test_signature::(true); - // - // } + /// Finishing a sign-initialised context as a verifier is an error, not a panic. + #[test] + fn verify_final_on_a_sign_initialised_context_is_an_error() { + let (_pk, sk) = MLDSA44::keygen().unwrap(); + let ctx = MLDSA44::do_sign_init(&sk, None).unwrap(); + assert!(matches!( + ctx.do_verify_final(&[0u8; MLDSA44_SIG_LEN]), + Err(SignatureError::GenericError(_)) + )); + } + + /// The full bit-flipping suite of the framework, several minutes per parameter set: + /// `cargo test -p bouncycastle-mldsa-lowmemory -- --ignored`. + #[test] + #[ignore] + fn test_framework_signature_extensive() { + let tf = TestFrameworkSignature::new(false, true); + + tf.test_signature::(MLDSA44::keygen, true); + tf.test_signature::(MLDSA65::keygen, true); + tf.test_signature::(MLDSA87::keygen, true); + } #[test] fn rfc9881_keygen() { @@ -167,10 +219,12 @@ mod mldsa_tests { // consistency check between returned pk and sk.get_public_key() assert_eq!(derived_pk, derived_sk.derive_pk()); - MLDSA44::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); + // The check is against the FIPS 204 skEncode() form, not the seed. + let full_sk_bytes = derived_sk.encode_full_sk(); + MLDSA44::keygen_from_seed_and_encoded(&seed, &full_sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); - wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); + let mut wrong_sk_bytes = full_sk_bytes; + wrong_sk_bytes[200] ^= 1; match MLDSA44::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), @@ -205,10 +259,12 @@ mod mldsa_tests { // consistency check between returned pk and sk.get_public_key() assert_eq!(derived_pk, derived_sk.derive_pk()); - MLDSA65::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); + // The check is against the FIPS 204 skEncode() form, not the seed. + let full_sk_bytes = derived_sk.encode_full_sk(); + MLDSA65::keygen_from_seed_and_encoded(&seed, &full_sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); - wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); + let mut wrong_sk_bytes = full_sk_bytes; + wrong_sk_bytes[200] ^= 1; match MLDSA65::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), @@ -243,10 +299,12 @@ mod mldsa_tests { // consistency check between returned pk and sk.get_public_key() assert_eq!(derived_pk, derived_sk.derive_pk()); - MLDSA87::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); + // The check is against the FIPS 204 skEncode() form, not the seed. + let full_sk_bytes = derived_sk.encode_full_sk(); + MLDSA87::keygen_from_seed_and_encoded(&seed, &full_sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); - wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); + let mut wrong_sk_bytes = full_sk_bytes; + wrong_sk_bytes[200] ^= 1; match MLDSA87::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } _ => panic!("sk_from_seed_and_encoded should fail with InvalidSignature"), @@ -382,7 +440,7 @@ mod mldsa_tests { MLDSA65::verify( &sk.derive_pk(), - &*hex::decode(MLDSA65_KAT1.message).unwrap(), + &hex::decode(MLDSA65_KAT1.message).unwrap(), Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap()), &sig, ) @@ -420,7 +478,7 @@ mod mldsa_tests { MLDSA87::verify( &sk.derive_pk(), - &*hex::decode(MLDSA87_KAT1.message).unwrap(), + &hex::decode(MLDSA87_KAT1.message).unwrap(), Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap()), &sig, ) @@ -462,7 +520,7 @@ mod mldsa_tests { let tr: [u8; MLDSA_TR_LEN]; { let (_, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - tr = sk.tr().clone(); + tr = sk.tr(); } // BEGIN expected values @@ -723,7 +781,7 @@ mod mldsa_tests { // println!("{:?}", hex::encode(busted_sig.as_slice())); // END GENERATE BUSTED SIG - match MLDSA44::verify(&pk, msg, None, busted_sig.as_slice().try_into().unwrap()) { + match MLDSA44::verify(&pk, msg, None, busted_sig.as_slice()) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("Expected verification to fail due to busted signature"), } @@ -962,616 +1020,3 @@ mod mldsa_tests { assert_eq!(sk87.encode_full_sk().len(), 4896); } } - -struct Kat { - _parameter_set: &'static str, - deterministic: bool, - sk: &'static str, - message: &'static str, - ctx: &'static str, - signature: &'static str, -} - -// generated by hand against bc-java -// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java -const MLDSA44_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-44", - deterministic: true, - sk: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - message: "BE2E693BBF71C271A5AB6B2AC0CD1EE8BAA143B4D65E131C", - ctx: "", - signature: "b81e096e85567fd09d555774afc606ae1951429ea1b43bc45c98df6310f24cb81af58143a0c32294b0c2e09fdba17e72f845491e47c08c47d10550fe441f66bf48d18fbb5ea30bb7f3a8972f930dcb4442cd8394711e415ba65b1b5efd5c0c040dd17ab79584b8d130eb46d6766f918b05c6e09157c72d2b9554ecb5eb5b9cb0848fc2c16629558ae4fb71ee88c0a45b134b66a676e99c70375f770f8ee37dd9bf6082d75d2ffc0e94a6915649cdf0f76638000783b263ad71f71d5a38a589efde0e9919ab76fd950b678236b9d50651db8711c26bbcf1b99fe5a2e4f48c0f41f7bd8b654edc3e9da18b867f03104a13cd8d137e3471c9dd7cf2ab0a5b9154e0f933627cb153a37b430ed7748d924a14e50a609ce48fe1c360017fb72c820482b816d3a1cc06905003418e234e36a592bb02bcd2549311cd2658ebd31290eff7af81df4a6d31ee1423712acdd006e35d6801f90a07f109cf83d5318aa2e7e4e7d5c7d3b537293cb334054bf8ae4a2cc579b9898bf15b03884339dee7f2d9512531b610b3195b76d7b9b3256e88b36a67c316c236860bf2e044ba7a5063172cd282feceff292d7bc967f93499f71d6ba05c3dd6b11bcf30f0e93145065da0691edace80c6e98eb58a8ef71e2efe3b5d419d3cca385c4aaf8f35c3a4b0f374d962bff9850a8454137eaf93524f3de66aa0cd9dd2013f9a071ec668e0d77efb185d57faac28ce7ea0f50624d75200e28c8de77854b8f10197a6f0cc0ad6d07607d7fa7794a445443a46f7c647f9556e68ec66100d7ed885bd139b39f5b504fd6ecd29c544d962b94b21bf81dae5dbee60718c7b01318ba0d82af0f36188a556ea3140407b5e8c2dcf8cfff7572566150a15bde39e546f34d0721373c5b745c298cc0381101bdb0a652ac27bea90faa9cfd38c44539d58cb5ed863cb097f5508f51a020db28e99306d3e92333baf24d06e08b56fc8d007ce56236659ded6a0e43ff5f3e631427338d2da2e6e540292de3c30940999363340c1e6fddc907a8a0a2123777b532d8ac955d08588da2453908508d55404e73ce6d8dcdc17f94c691d1588f006a2045610ea57ab2cbb519fcf38da7a5a9f7459c4ca0d41683622a5c8b122309880ea4eba537c47a6b4788dd046bdf92b43e25d287320f739fb0dd8f7d97feca2d121529e93b82f336aef09b8fbf89445597c2823a531b6ab91bb1eed579ba94a1bf9d4bfc65836379983b8c5e298fd7f7d7fb827b802e5b3b0e3ccd8c51ae6b0fbf6ceb6f97abd76607f6911163225f156248d392af26af46d261db85f7996414f6c291cf15c59e45661ddaaa24f92cb48a0b2da5d862b266d6d0ec390b445da9e16bd5330ab48e2b4289ecc93d984ff426eb17d34162c314a99499863c8e78dbb802b0c5fa06a27e12d9741432d91bbb6fa9339dae46189d165e6dff8aa6382d0b66195333aa3b945e7b2b26be2fa899949429663b77d8897e685794683cbbf2a7d113bf5950675439e335f0a8df0e02b4730c0d3632aea985649712a27b7d492b21628fc3c3111ee3497aefd8f17aca5854a612f2e2b3ff30ffe708f616fb65ce49c85bac292874972ff2997e633a8bc39b1ef487ff6331d306aece4542df2b6d918513d611a50d6cb4a8086a009e909aa444f1d37fcd03526d6ba42ead0a2d4cb83197683b45a5626ba9fa2ec1fc24bbb4e4ab21a33355eef8c54ba5d82b72e5602bc72b020d0c8feb4d0b1518842fadffaa2e7fde8300e198cf7afeff1901a91f2b9d78671374eace911d333a91ad2ed5f6a932d5859e2acb1a09eeeab17af3d868a7d246f9cd73466a49ac5751af61fc4140b8ab1ffb60c794c10c8b07c2dae98425e04465a927fe4e88cadc0929e2a768174be133214b4b46141e1e3d8e9ed48f631e24e3efc276c35d35d20fe144e7124c7ac1772aa1a967b0af5526291d56c0dd404ad92265ab281483d6c39123f104c910b2217af79b9e9bdbf9b2561f304f51b6f406d482c069dda85a15b86d927c7c9a1de1a1036730315fc662ddd8754abd437192d240602fcfa366bd4c80acb931ae8b4775c2a2c3158471b773f5f36dd588937ba08f30b3e232a909e0d309c31510898229d5f533c72d57ded1e61c3c2815d373e2cc9dc41ab937056bce8f61373b502e480b7975b02a203b34fc57923075b1ac3df70a036ea30bd6db11ab0e58d447bb21f21cf88195427fca127ae10290b85cdab50038389786c20ea6b3a6c12920fd546de742f68ecf2965c1f7b01ca361da14a6799c7de0410a250bb62ddf26b0514405f6ef7721b29fc8b1f751453c7629ccdbc40c71a22e5e3948dfd09071711043dc9e483a34c31dbedc55be19ffce85a5055062bbbfe9608b93faabe8fee086832de8ca6b9f4aad3df54a8914b2bc37229ff49e3bf014d758d6cfd5e6665286e0e120ef9322dcbbb40fe2831b73cf59612b13fa62e62ed8419a4323c9ca406eb1d7110726d3e4e3825b3b5e50f72c7fbe92d154184e5403895ee9c23173a98816c13a4726859898485a1b8f36c29b2e5951499d722575dbf80775cfeab321f6967569022452394abd2c6e9f8ee2fd70a8b9fab3bf281abe6717c2fed16472167766af0ebc92b99ebb89649aa35e744bb9249a44b2c12cd50fe3e6e4274c7047967ba777e7e0aa7c600d2131cd5d536cf8e7834d0d93d8ae028b19d015a1bc5ba5dbb1031e1e2f25ad29fbdc42569ed4554aae8efde1543de9eff9cd04146f0e315869722dac4ad06663c154890f8dddb4075bf7268626a86dfd42cf43d9681cf399fd2cda03e2696ddbb713bd27b1c16193908898a04db43e960536755c0d9ccbaec637c0d8d6462474a11267291d863cf29fc09dd549307305e20e7acb1c63de3378aa74cf08807472f45f20013c0275d0003bcda5cce48d8f52304970f34498b567da4409b71725772bdead54f9a9e907c9b0d989599f6d0ceccea03559f56aa18a5084ada636989e80763b986a3837d360b75b644e9a80174aee3abbaf2483b3e1945f02e1f3567ed1f60f2b2a3b62ae0d6997ef62024b63639f1b63a84560ded1cb35e449f87229f535534c6a403bcee4aa402b98f3cc15dd3c833d9a05c108febceb0de6280af073ca03bfc6f7b99896bad33a580c8c74cac28fc50e4da4e0f3ba173e122c66f95827b1e343488f5387ac4615a196bcbfbbcda0f78aeff9dbcd6d6217ebedf12f75d6c22ec2d846322fc50e9d6a061b46e1be96b45e806f646399c6973a94241f7aff272c8c08c38002071e58ff05e2368a52c71e3013d06efecb566474657616465787e879fbac0cdd7e90f101c474a89939b9dabacb6c6d1132048536086878898b2b5b6cff80f383a4248658c8d98a9cdddee000000000000000000000000000000000000000000000000000e1c2a37", -}; - -// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// except I added an empty ctx instead of testing sign_internal directly -// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java -const MLDSA65_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-65", - deterministic: true, - sk: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f00", - message: "F7DB318231EFC5250F7AB18DEBC0340C53", - ctx: "", - signature: "7c8bf133e9d1373235f1fa3602be8d20505104c417105f612e6ac620f87e2343c45994fe4cd8f8f1354e6009e1e329dd972c1164520d4f21a7547a9696ca3d1eae8c1ddfc2ee756bff0dc31d95a9d39d90bc785769dd314ec227b55769c4796ddd00083d98dd9d7cad97083edc991dec8ae191d899a16c67ca65ef694921b924721ac1a763ab7fb607d1e434f41e3fd2b4cd8e39082a722acfca32f753fd576546fb3323cc18a6b8a441a8ad1f10f234a766ceaa3246d4e422915ccbefd6fca64b60d503d713480d0dece9ad7d93c56e2713620787813d7d14fc06a9a24a3ef4106e3075ab8f3acb30da128776cc9d98368bad562aecee44b7202daa11dd2a839386a15744cb8ff8bd5d9a71d862a729a86d1a3863a4d05bfd050cebb152728ad303e476fcd5b1e1603ec4f501635b750c92d9095ff0f0872c0c4ff8a5c476dc0d1252af6ead3fada16de77b887ab2cfb42885ef0af0d66e4e32fb255b34bcda02265c3d630c1baae069c0d61641abfb59fe50a146ddc082ceb2383368edb90708e74e4989f715dbb667efd4326e18efa563eafdb9b07f19ee13b242027473032da29b964496b2643adf94c9901cc846bd9a22d670523dd80ed1f9eea1fb6fc516b45ac937ade32ee739836c4a1520394cf012d1f38c4770c992675a367efa89c8d6cd66f6715412cb0e2e73bf00db2330a76d4e2468f5b18e73c416b12f986e2d2b76a088a42d9f5d1f1a1cdccbc593c203e62aa11febe7815d50e42f7d211c1f4a192ac27ca23f7b0b2cacd315380b190d36a52833b250856f40bdd5bf5265dbbf43488576131e2707272f32d7b7b11c91e6dfff4b28a8ccea14e6794a144d62a7562f563332691eee2123ac736798105a4066e7cfc6fdfe411b35b9a42d507b3d628c53d6d8ceca360c4c97d4a69b4537ec0e1335f7b03761c412da57f28421fa8eac4d43b4258270399554c0a3b94521c9d43ecb15f75acbc26620f57204108604d9eef0f833d329b911a87a3ff64083845810404c5f6559503f8336ffed042f9fcb50f9379a1c736ead4cad92b13eed19aeecda1b3da2ffe879f580a0e1db9e6fb3dfae6eacfff7a200d70295c781e237d2eabbb5caa02a742daa8429afe451b3447163822928c0445a35b714a19057aa5f2c19015bcfea3a829e890c49053b50c9a1851ba409b4212cb65130763dc576de1c74929b761e5deb6532a5b2d2ef01f58f7d29943c8fdc1e9003edd75647f0f244f81a69f0331324dc137ec4570e742f3d92a46267e8775c530893253693a324440f882a2d5010992b1ccf5469ca5dbbe59c7d06a5659ce1360e5b25c93142590afaf6e3604b796de9bd2a995e1255cbe4b3fae1547b2d07ed08eefc55656d0cf03e366152b6230b7647c334fe5679f821edbb5499462180585888c0b1ecd8f55bea2a3faf819701725b4ecdc7ad208913b57c2fc93c6303f15c1c997d7f063f71084d1b20d12a2a73ba2acd47ca12bdac2b668bfa4da38adb2148a75a8dd8331642db1648a628c403a519caebfb2dce4345bbfbcdec2a8d2505091f20d5b996a3b49979bbbc72e9b4c260af554dded8eb3cdac15439bc0442120335ebb3059c8e0cd7b4422780e515c706d773126d3c1990194f80a35f4d4f0ea6b728b0fb1779df4aacedab6cf4ce5fa07c30d65d1df611b6c92a093e4e6a7c3e901403b3877dc80e10580849a14508d141705f251c39e01f5a82b488de06a445716d3ef677c44a5771abff063bcd56f08afc5eed37db0a58eefaf4a21913a9cd32635995e3dbeaeb7aad09447ad6935b523e5b52eb8c6df5103958b624282f933ae6cce7173e440a823047d06074367fd2d3dd874e6d2134bef9b397d8a4a7a78f7d65540eb0444e4d7240cbde6147960985a9a89adb4538ce2cfe0d09fb5b87f2776b422b397ec459b86b7714242a94104a7e20d184cb3aee49345435c7de4babbfa8d48a8da2b2b8e262e787655034ff2878e90a85765d6b4f1156a68f26b70497d93938e9cd44b52a22e1b2333bc5a7061a6c6b9112be516e80c5fd8cc75b7c69adcc0a67e917b868b010d71b22d992664f716a64656b94e578866d972cb1ef1aba767eb6e9138ce138b2fb52edcd3cd4b70f18eb9dc3bd1311e149db677d78441cd7de9acce04009d63bd0051baf1ea8bd47e009cba83b6ad08b54467fc675f03fc502df3f3bb558c8bf3e0855a892825ebc3a0eff081b7a800b6f89e5b55446ff9afaa96fe967f59ed1afc560fe8388fb1e73829de5a38a1bad0dab5b844e99e9bd5fb5067050f0ddb57d567e7fca87b59fccaaaa9ad0ad687b083add39f456e77ee00d25ecb67e2336cc915c17bc50fd1435f099633a3771810a0ac0a9f02ed57334ee6f5f8bc99e14282c8104324e0bcc93183803aa30e24410d0475139119ff7453e8dd5db49bb33c1d83d894fe9bc0235223d566d4ca1b232326fb2d2cb994588fdb00b9a15d3692dad7a7191d5e93fd0edf0f73027aae76311b35e5ff487c37b329730bceee0f348982884409ef6c41d85d9b08e73b4056c159ed509f99cdad435b93d079df463cfee11fc25d5709264cd0cb764e020067ebbac3f16c00ac038eba776bc5ede1d96a8e28ae3dc33d8cf7c74c10172bd1ef96f089f11510795c3071b511c9aa7d71401a3f25e6cf0736d701d62a44bae6c84d25dbd487794783cce286960dc58e8b5b386ec11c49a986aebd599882d3956344b0d6bd793464ea2af20d79b66a18c1aacdb03a980b15e2b91c842aac41c3c95a1d5e88f8497c94144cab7473a136ea4b4d6fc5586395c1581e19125001049bd137e16183adf98241bef207097842185204193e1dbca9e8454a6d2d3c7cac267ce9ff2abb0864378296c578f4f49b830b4e48bc5dca8a823fd1592e05295fdde53c30f2dbc878d316e1332e92d82c7dd10a5e4b8154257a216f19869ba66da56966fef97ca49e41d1b346f9653e002a62750b8760a2640d6aff21caf57f8794ef959efceffc4ad4e53e2e09613ef0dcaefe630cd2501de62d65fc3fdb5568b8373e782ef4f876da9fab513d695cf324249fd6d2080200d039c4996fa62c7a3703b2725c0c54447ff8dc47ea875f0270ae8c900bf2ac32b86ca91540aa3b59f2a3c9f780b0f50c440e61b6781f16826767641049ea3f4c70eb3e5ab13936f3a501cb8909e6ffa25a4cbe42fa8c6691333adf46477bba3e0af0dc6d0389883f3e9f764e3418a5fa8ca11da24f640e38bb9b62017732dbdcf4dbf262d9be5dc2eb4e2d2645e4e8f59bca3db108672b9c8f33d47dbff755e2ea6a9f79d96a3228972ca45b7a4cf99fd02bcb4857a529d90b37743a97173c940444a4c74edeab33cf4045ec0c8fd0fdde4861f6706fb58483960fbb75fce1ecff890986a8488ab34f43a528d93b65fd839063ff85961ea21b211eb937e71f3f8d192ae794b15c3391f4751238396982acbc1776b8bce572bfa3b919fbac229c6d395a6d5352e470b5103948d11a167ee921af402f2fc453c25ec232a3157d0bf1b5822ab751c8a4cd60ec4d0f1690b9c8196739537c146e120e822e334903a7509d232fa462cfe9af018b175d484a8404f20a855d52f64e8ba11691c89ab63ee1f409aef036c048d165e8b9083cc42efaeaaaefad79a9b05a007982f664cdfbea191f7cec4d9c4f5f97e9cd809b1ed084d3ccfbf323c4fae45fc19b8472fc186da8ce7f9b4f32151be0dff2f29ee4b9fb8f297cb320cbbe519e2e91f7c3fd920dd427e7b7882b0e0d8176f192b916e0019abc55715d5b77ad9b9b17fa38d9ba78c38e95169bc5fabeee2f1f8ad4f834e0a19159b6f5d538606faae53817a912b12f356cfdf2b91df0817e27f10da74cc4366e100f35e5c1b4384e42dceca600d771fe5accce796fcd3bc950d9741473a8d6ea172debab3a75d95992f22cb540336b01c4a7ad5972debedf8bac9e34379972f2b80af7d870b096ad05cd4a5f006bc5816ece674f75088f8c5df543433724d7f57d868ba820576e9efea7e019728ed54bec5578e6ccad58905b2a0327d071a8065d7539bbd41b0c25445a7b4ff97ab5ac7ae9ef14b417216367bbec33ba76aeed5735b32463d1fa0b4ec1ddf86f3b24ed2ff4e7602967cfcfdd314747ba01bc522f7f0585c67775a513e64cedd145de0093ef9cdd9137ca397cb187fb5dfb26a120f65ad1e1376ce760ddb65d4e54cf5fa301b166d26253cebe08dfa5416c3df31b4e15669da870a5c5091e834757200f74ebdff05693258c98e3248667646f8e8395aa68e667ec31fa40554a34cb64976678e29fe036ff9d6019774700de214709213f9f5ee986054e249a5be3373400d911dc64128c072bad2ed84bcf3ee67fcea0837934171362b69dc697fe21602f5dfd47eb87658bdce859160c120df5fc3cb501d412ec3879612b15108936d2b7f4c5e11ab8a42b4e0e3118bfd9d0c4069fda0eb2a0a8639a0dd8fa7b2d9330e93fe374259e6a59e94bb5c2f439eb6ef9ea05fe15754ea983d769fac709d6a8f18a1ce47d02d6f0bc7afe67c54c4923d24b94686f6c7727a98f5496bc0b78d0d1199af7db4ff80db465de414a9c2ddee313c53727daed1e00143548f9da5b3e40b85d5f2088efbfd537abacee1000000000000000000000000000000000000000000050d15191d22", -}; - -// generated by hand against bc-java -// This is almost tgId=1, tcId=1 from https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// except I added an empty ctx instead of testing sign_internal directly and re-ran the signature value against bc-java -const MLDSA87_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-87", - deterministic: true, - sk: "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0001", - message: "3F8599C466713387354B419392EFED39A9D76C0B5E45E079B3DB", - ctx: "", - signature: "541deb5ddb0e92ae138afd5e86f1989633e8f078bcf8218cf2bb75cd248b289d5297b7fec27bb45e59a26e85dda77cf0fbcccf5cf262856203453305327554fcf6dfb293a0c58d4f45a0c79c9fa2c0297744e18a554237dc6ccbabb581470311a3f7cf89ad2ff13dc9c4e88aa31a6571fbaacac7133d89624ff7e5ef43d51cf7bd1b826d82fd6dd66bc9f42d651ea3903e686d30816f808cab060aa452d41b39a4f31b9f0f643942d98390cd445ed85305ed16900cc3baf8a3632878e2343cac3469b8ffc4c94031618f3822eb478aae832c0473860fe3fd55dcdcadb65f9696f820e8cd9f63cb5715cd6b6c52d545beb6843fbc75a8cdce07bdfbfb548d749e76b6e557fbe12c781a19eb75c4568b83318f119cff2461beda087e5f7c1adf09d3b267ef24f07093abc365508c9f3dc5be04c8ece633fbaf57931b10886f1497a29cb8e46fe438cb59413fb374ee1e324cf00d89e4500ac4c0274978e1d757b2ab071c61c739afeabe207da69d041245d286293fe3a3fa41593d4b3f98a30c8660a005c487e9c86faf6a29955f7bac1654b34f8688211ca6060dd4f380d5af3f412ba8d0260c2f772c2d17db2629b0a2adcd4112d403df99391ef15bb99a577d20a69590284cf6e0be1794fc246a29d6c15c21a484aedc6f52987579a14bcfea14a030c2646131527b2aa4e8b17c1b5e91fe8ae4655e6d40d9898ef2b8b17a37cca3a6d73cac55247e2ac09d12644095a2dc89047e5f110aa0eb4b47a9f6bdf017cc671d82d9199f300a6a91aa9396053450e6e07bfc9248f8a7b65f02daddc03c91a68167b622f44dbe744baf9f768255c816862d990fa53237f8d7ec495bc0ba1e72a90b83c565101ea538dfc4804e72bf6f9dc05eb35a7b2f04067d8be00041526f46d8251542e97e9d403816c96e17fc647db3bf1c00880855caaf857d8a9ae76dae309486b4aa96d47fb15ef645fe1ca8a4e52dd567610c2a8aa04ddc948a14628b0a652ae5c0b4621cc59df503fa2cae7a3b5433bb1837767e6809291cc3b8b09c60064ca512fefc3e52b62881c40c6e655a7dcd8528a41f7c42ad6b63b2cf7da1fa274474f01bea6a2d0832c2c74e758f0061f3ec15513fdd559e6b5173662a648bf0ca9054edace5d97c434032729d82ab910382131fe267df78ee02cfef45edf0c9ca1b090e122011f10953021be469d0e62e1629ccbed6706124b6276b2ea77082be43a84f7e73c0e92fae90ced3c057f0acf35bd5e2bbe20f2e6459f00fe80efdb35d41c5644816220bb39309cb7c1923b2f609e245e43ee5291dbf58cd24e443c05576cefa5cc774d93b5a62c3aeb9e2c2452e1a2c0b949fc02a36d62020049d62a03ee5aa8305b7cca9fbd21c94c7fac58602872ba9a26f8ec23a1ec102528fe6c0e82339ce37173fc3ffcaafe537f41ec8eec089e06d9e49c40fd9163589a95ca74d2ccd4650ec971bcd9678ce2f7df94f74c745ec6e4377340079fd670ede55d1d5131bb7b5c4b64903c0e4865e3d43003612b602d5f5947471d009b687d910dcf7f2e807e8acb56af5d6bfcfdf5bfbea1dc4f860a4d4deaecb305e3e32cb66816394a9b9fdb5fde03b926cb50788a704ef8bbc919464b2bfe28b34ebfb7cfb44f9040daba70ee4163b52c4e2144d435d1fc29718aefc27bd82966fc65cc058df4385635df809d91e58e147914bb694426d08f71721a4b65c2cc6180425b224d0924c2c8f6514544ebe11141715ab08ef48e4bebd90d57b158d79359d598b1687df8754a4a821384d425c60767da977afc87b304f6651553a2d0fb01909f7715034924393408bffe8095b8d27eb8cd181adef31e38aaec72168ac1942502b492957191327692dc56d78706802b229d9d9e9ec82816c7047f9458972c1a24b55587d19225cca849295ca8d07d8d5000aa2c5c1bc1e2f39f2d6fdd0ba95d5f6239b36cc86b4cfd62d44bf9d299fc1ec13cd748dca123c94f0be8a6ce2899e8e84d9b5289a2bf0ad1f69645af3385c250029a9d1eaeeee1f22dacde920f8f66237dae504d4c0c42fd92c3eeaf8169e97b238aec66326d71a2374ba42083fd4b09708f0cf493748ef03efb5962685a50693a34be57e3c1d036ba9630a7f564260fd1a6852c2e883a9e0554aee9d23ee4cf2ed53a6998a2e0d731ebb847b1294c1d8067296fd03bdafc4f875cc60581baf81e9f1dfcfc4922acc1673c7896e8fc5f16bff3a6d5a83348fbfff1d90e9d14fae4024a58cf71803da38fffdcb5ed86a8cb91d8480b09f813c651773fc09ec86dd8fbad25ff1a5a7773664c3b8584892f562f77076d46ccd7bba39f4b9e5a00b6af1f10f5b870ff1f2ac686e6cc720e50942d8dd7099742a523dca21208d5d21b8527831c2a3a780ab18168f2e5ef43c7dc514ea719dd70c1bc3c1aba4839bf4ef55af560fea35dc94a4052d7bc8b68da81e3ea636ec210a4c3271d0aacb5913d2fcf87bad72820faf4fbb0d09724247446a97f8386c19a75cd9d29d4b0ab9f03f7c70a3406c2bdbb312666dc91efd420445a469466c2a2024bc1eb51e16b23bd075e8733eec07e64c7264ef497cbad5166eff63e2024b1205da225821482e30631abb401b5f0374c5ed7062be3326be8d77ceb29358b224b1d36d7825ad9d54a28ca66c64ff560af353413956837086dca174da510e5c529f5f0fd525eec5cf7240479850bd5618040f4ae961dc9a0b11b7b0afd530dbdec922a6ef500d83c0d1849055eea0eafe3832e58c7a92cddc495596ba5223820055fa76b690b84733c26f92784921180e64a8139ba12f4621255debfaaced0c1ec350bfd3560f76c4bc9b40ddcfd8b23f98c92b7c7f1ce1a1aed811a05eef9602a8abbd3009340788230fad7ffd56af02b0a4f60eef9afe84825f6c7f9f9fc4c7385e4de6da903c5f5bbc55894d2f2bcc0908c3d42a400dc6e8a5f34c291ec388f705eb31a186d85b3c91baec79eef0edcc4c1cbc97e7fd6307f865c36f9b5cc608ed7c5d022dd7a4e1f61cbf78dd2c129d41f0e9ee867d87bde240c37eba47a579576dd9489ae43ed23461b1e6043e29baaaca561dc80f97b26e4b308a4c8c9f9fb34cc19fd087a4e1ac83d677087a1d9a672eda63a0699ba3635707bdc6238515e82eae01e18c431d5763ce30d9cdb560e0325557908dcbb016461d92f48cc1260562a66ec2ac2033f45b96d859e0b4249fba2f8a4193c85009c9eef7de6064fcdf613361814d0c5b228d6cef300c4a0d85d68e559a6a080697ce216de5e6925a2eb8e5f5879f352618c686ca66d81e867925cf181284053916cc51f94272122d29062d30c0652d16f6d95575d92c95eccbef8f2d8e2b3b38864c3bcbd0de7f68ae991a72120bebf34821bfefa13e3bec1c1d3528dbab24a5ffeaae5c9499596e47e1f8b47f15e4faed41094eea13b3b661fc61e2521fd2970c691c661c471da9aa65cd768acaa14f5ba93dd966541f5d01f8970a39497e2b6c7d3dddb1188c3525a9d711efb622503356ee3f80238f01bfc165cb26bfed9a15c343354adfc2591d2fe8275aebf93f201463a9f7b5543e8430cffd57e84d22baf5c471f0efb76ebee62272cb27da66833a78f00a36c3c4b35d69bbeb9d69cfff27e66fa16b843293ef9009142c3469abba381fe7aac9e75aab955ff912e9a767f46f68f4a26f8c0a8439040187409e75e9e509796a67025053179913c729c7fcc27bf270a4957b0cf979f2be75bd1c11e5a474f2b7f5ba78ccb9898b5b99cc42f73c2179e43188c47a3d5e84aa75783e9db05ffc49ac190ed840217c34173ce90def9c84aa1c055f8617ccb54f2029c0705b8010a943df665f39e0e5430ba1a4b20b0ed4f92913ecf2530387921ee206262a2c49d20e0f027459b54a123bf1d3ab06e9237262566cb9b1f2b6bc85f4e6105d180e4cfa7b6e0c8209590c9ce5dab89867e538a4b8c1060f3364cb6337a6b5d4d75feb88134a1e3a19571e882d549b38ad6d6ba769ad4bedb041e744ff8bf2afafce2d64d73461388998073ced7af23787d99c650fa17683c26c89cb408fca9ec3a67261118297b67116a26af94afc5866ac292cc315f13085e233c1eb6d5522f30d4e1e5d9480bd15c5ec9cc302c7789d57f51874f8dc7a79c007fa2de15d8eacc8696e5d35d4a65d10840e95594dd655a7c51c80942d956dada91d01efe3acd736fa56431958578d0c443e66c7519dfba2e7847338cffa0bc947df28f5b2628116c7d6808217e98d78fc00d43f2b6b0a20c722362b5c02ea840fd19d9ca10f7f6cbf36369e80d16f2dd310e7c5e3d43cd8d9214bae2761a5e77e73f4c626ee807bab14c17e68315241bf6e632b546729d9b7d6d589dbf56306afc2980f0cb932ea0a5abd8a992b43cfabee2cff1d94935b8213d4aeb6c7416e1768d148376996e0dd17284935eab9645fe4ef638e2d041044d5a19fd35ec084a838b54e5fe7c63afb800fc043a666e020dece7fee30f5d1ce51e0a6fba97745d28efedc1a4ca20cdc1c65b21472456522e7069a52579fa71ca3866d24dc5abea774c737e05b3cf00cf6e171f8740da20b4c04d17de786534f78bf22fb66879cbd8b507fa953f74bcca3473e1d837ff4079699ae810b555460a4ff3896c7d5e234417c9f4555325a4f2b968e7e1c5fb425571486d07e80ea23a4858bfdee29ccb301456484f29a3c86946866166357bd9225ddf715a0030ee384c9617935fda2ce99928644dab3dbd5ca48108ce00a3ee8eddcfda8fd93200ee6c7a162ca0f400a40d9b3a82fdd117880b53986ae7e75ecc0a4cdbc867d88d3785c290628509ab671f670c8ff79d748f5e5ecad187b53aaea14c75979a3023013a8678ec4c94a4a8467e8ad8e87087a7f321d1574ccfda16f98434f639d9b2a5f1cd600eb7790343feaf262c7750a7bb5539ceaa82d06ee8617e224a7313b8b343ede0fa660e8b3cb7153ae582df86c13b22fbaa09f01c02061685d729c52a1ce778b7bfe167a80d70ecd91fba455a1766ba7709ef293c3cb51372ba33a624ebf490cf52f46f49e71cfa38602201a9f2671d723ca79f5e267c9ec12f34ce7f382f7726070f02bf330961e587fef4bb3f985df8228ed4ec95a0e9dc45ae394dd4de1f526c28c025d1de1f2e606722298c3103f716cf38343527520bf17e3383d4961179de7b28db5e25f90e83e5cd8a33146480eb9762b58405d326f44de501465aed7ce1f97b2bc8f5c1cb4c206b0f7a2f79f7806b5af2a4de009114541d6404fab70100b75ceb0deaaede4a260582a32a52400436ece37e1c85d4563a192d7d35a9a39fa3a0494805f4020f429f89e90e547a5d62a59ea1220bfbf4189f8a2d6cf00a4daae88708f0352c7627aa2a82f87da376c3435567b8c707c4540ae79d344101b5d38c36a35c34e8766ad30e44e45bbe98bccb7725ce095b10a749964e41e6d21e4de19cef1a0b27bb7b1c528c59170d5ab20ab8b73ab97611543f42806ec13a8b3d0543069bee738dc9ee796eb776ee7bbf2677d44798938f9cfc2fa7322d82f36a1c0cbca0f20641dc86f02d7f6a0fc0cdea441419083f621a253a3b58b50a3ac876b272cea21a7acd7648d356e720e39a0dc3a4a95201033eba587337c961700c1f4cc72d0edd38b5fac1c30c33881030c2d4805d8316d6693a3f61c4401604e0aa83be5d4b0585d9d2cb94dbcf95ae4a6a6262ff968df9a20ca00f0315e7742d6c66ce361f00f1e6e46270c08f499e81edc28d4e555add5f7c1b7c9dc5426e327fa50a272e39a88ce4a64add93c639f37ef9d9f35ff3d2c9b9edf8ab6cc2d1fc20b903dabe4d07641df5acbbc5a702c2a78b74f7b6879e3852cfa7fc801705814418afddef12cb1c18550cda8ed00a878e803478db2576491d0c6be1451433f5c526eba05c67026e6613269e9f879d388a0470ab81ecbf4c5cb7493647d077eff273c15375c32d12301b9fac37e1f5be7adce8107efd9edc9c6c2ff7a53005a0f96d26b9d59fc6d61bcef7ed509bf1f7b1ca297bbba020a67e41a3fb11ff1dbda68028ceed966ebd7d8afff42dc65f80ce63d14cd599f2c71e6ee86053ed6fe10722f727575b692f86ba4bb7f247ce4afc67aafa5a4949fd9c29f0bdacab2ce7c3c74714ff34c5b47b37e991a5f317863b4a4c279f3145eb3488a1daeca03fecc66fbcdf44770851b281fa91358bca53c7cf948b79d456459bc08c43eac0d12d75e0d94a79bfed8605befad8628573d4c647a5cc824bf3901153d34cff9d1d23da7bfaa482f76031a5cff7159bf0a9906d9166a5287e6234040ea27279a24238af98d0f90b869151a1eadd9016204fb18cf03b08aab2e9a680988e39e4240d4f7c86a24c08d2bfc75287ea9b20906f0a4e7577da2c78f73402a7c20bbf48c7d85291548a7b212b5dfc09381b75b607a9b9da0b3bcc2070d2d98f21d4a7881060e20466d902f444f707f8799afb3c6f631747abdc4e2f01584a5a7d8dd343b3cabbabbe3eb0000000000000000000000000000000000000000080d111722292f37", -}; - -// struct NistRng { -// _seed: String, -// } -// -// impl NistRng { -// pub(crate) fn new(seed: &str) -> Self { -// Self { _seed: seed.to_string() } -// } -// } -// -// -// #[test] -// fn generate_key_pair() { -// let random = TestRng::new(); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), new_ml_dsa_44()); -// assert!(kpg.generate_key_pair().is_ok()); -// } -// -// #[test] -// fn test_key_encoding() { -// let random = NistRng::new( -// "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1", -// ); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), new_ml_dsa_44()); -// let result = kpg.generate_key_pair(); -// assert!(result.is_ok(), "ML-DSA-44 key generation failed"); -// let kp = result.unwrap(); -// -// let expected_pub_key = hex::decode("1C0EE1111B08003F28E65E8B3BDEB037CF8F221DFCDAF5950EDB38D506D85BEF6177E3DE0D4F1EF5847735947B56D08E841DB2444FA2B729ADEB1417CA7ADF42A1490C5A097F002760C1FC419BE8325AAD0197C52CED80D3DF18E7774265B289912CECA1BE3A90D8A4FDE65C84C610864E47DEECAE3EEA4430B9909559408D11A6ABDB7DB9336DF7F96EAB4864A6579791265FA56C348CB7D2DDC90E133A95C3F6B13601429F5408BD999AA479C1018159550EC55A113C493BE648F4E036DD4F8C809E036B4FBB918C2C484AD8E1747AE05585AB433FDF461AF03C25A773700721AA05F7379FE7F5ED96175D4021076E7F52B60308EFF5D42BA6E093B3D0815EB3496646E49230A9B35C8D41900C2BB8D3B446A23127F7E096D85A1C794AD4C89277904FC6BFEC57B1CDD80DF9955030FDCA741AFBDAC827B13CCD5403588AF4644003C2265DFA4D419DBCCD2064892386518BE9D51C16498275EBECF5CDC7A820F2C29314AC4A6F08B2252AD3CFB199AA42FE0B4FB571975C1020D949E194EE1EAD937BFB550BB3BA8E357A029C29F077554602E1CA2F2289CB9169941C3AAFDB8E58C7F2AC77291FB4147C65F6B031D3EBA42F2ACFD9448A5BC22B476E07CCCEDA2306C554EC9B7AB655F1D7318C2B7E67D5F69BEDF56000FDA98986B5AB1B3A22D8DFD6681697B23A55C96E8710F3F98C044FB15F606313EE56C0F1F5CA0F512E08484FCB358E6E528FFA89F8A866CCFF3C0C5813147EC59AF0470C4AAD0141D34F101DA2E5E1BD52D0D4C9B13B3E3D87D1586105796754E7978CA1C68A7D85DF112B7AB921B359A9F03CBD27A7EAC87A9A80B0B26B4C9657ED85AD7FA2616AB345EB8226F69FC0F48183FF574BCD767B5676413ADB12EA2150A0E97683EE54243C25B7EA8A718606F86993D8D0DACE834ED341EEB724FE3D5FF0BC8B8A7B8104BA269D34133A4CF8300A2D688496B59B6FCBC61AE96062EA1D8E5B410C5671F424417ED693329CD983001FFCD10023D598859FB7AD5FD263547117100690C6CE7438956E6CC57F1B5DE53BB0DC72CE9B6DEAA85789599A70F0051F1A0E25E86D888B00DF36BDBC93EF7217C45ACE11C0790D70E9953E5B417BA2FD9A4CAF82F1FCE6F45F53E215B8355EF61D891DF1C794231C162DD24164B534A9D48467CDC323624C2F95D4402FF9D66AB1191A8124144AFA35D4E31DC86CAA797C31F68B85854CD959C4FAC5EC53B3B56D374B888A9E979A6576B6345EC8522C9606990281BF3EF7C5945D10FD21A2A1D2E5404C5CF21220641391B98BCF825398305B56E58B611FE5253203E3DF0D22466A73B3F0FBE43B9A62928091898B8A0E5B269DB586B0E4DDEF50D682A12D2C1BE824149AA254C6381BB412D77C3F9AA902B688C81715A59C839558556D35ED4FC83B4AB18181F40F73DCD76860D8D8BF94520237C2AC0E463BA09E3C9782380DC07FE4FCBA340CC2003439FD2314610638070D6C9EEA0A70BAE83B5D5D3C5D3FDE26DD01606C8C520158E7E5104020F248CEAA666457C10AEBF068F8A3BD5CE7B52C6AF0ABD5944AF1AD4752C9113976083C03B6C34E1D47ED69644CAD782C2F7D05F8A148961D965FA2E1723A8DDEBC22A90CD783DD1F4DB38FB9AE5A6714B3D946781643D317B7DD79381CF789A9588BB3E193B92A0B60D6B07D047F6984B0609EC57543C394CA8D5E5BCC2A731A79618BD1E2E0DA8704AF98F20F5F8F5452DDF646B95B341DD7F0D2CC1FA15BD9895CD5B65AA1CB94B5E2E788FDA9825B656639193D98328154A4F2C35495A38B6EA0D2FFAAA35DF92C203C7F31CBBCA7BD03C3C2302190CECD161FD49237E4F839E3F3").expect("pk couldn't be read"); -// let expected_priv_key = hex::decode("1C0EE1111B08003F28E65E8B3BDEB037CF8F221DFCDAF5950EDB38D506D85BEF394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB187D0AD11522110931494BF2CAEAE36979711BC585B32F08C78496F379D604D5321C8C62B59EDC23AE1FC7742135918E01B02E411630E26E675400D5AD2C776FCC0A6711A966C11312AD9A821D8086542A600A4B42C1940720242628106210A43852331709308108B188C022492C1B28412C4218B042181C8610248059C9201C0348819326C582046891868A2C28D82346A1C094200A28CE3A6491C112CC24812E0902191985062C084622451CA062C64240E1BB3312496854B4606DB2668C38268441046C9B6211404811445502442084422710B92459AA0811A91709C241003957004C504C82692D29200C0B260C0A26809190AA2300E188969E0008DD84862DA14712018051907440412409B1240118010D142819928508B1091022464A0206D1246211C838C1B4769010690CC062481846920982C24120521B15041360298446ED1A63111056AD3A840CAA84C62B00003134A53344614194004C54CE306695AB08961168ECB10808B168ED990640B94602483851AB30454262251B8251C424A0B814842C4445A102023808409B7254CC64814854D19380E601651D8326A0A918908C170E0964D18468C01328D91C4054A0061230868A2104210A8611306218A248E620689C9B24508278451200D980466DC42054424852426282221612016090BA62C0A1144E0928158480D422210A006098B246E81288CC0248090308D8436404CA68450042494B68DA2926D18B344A00085E3B805140504A4C290842281C3262D0B2066CC903198382810166CC13445C0102224C688034632D840901C20680415289A188144988D9C206E9C302CC1B820614221080310A0C28C58128553204C0330814CA48D44C08D51404C1CA72C440865A03840DA20808106858C260DE2A88C9C4411594228C42604441426A1426408C0851101869B483199B20C80464459A88C0042089882900AB54562244812960544124600C88813A061E1284D0AB9914B962099B84400314E98128500B60183A00D14150E1881101901224A06681A498DE1A28411C63121262591A06D030524A1B6089444724334125BB42041B650D0888D0B074D1C94644C208E8B8808E0300944200549864D03134E19C9840937611A43684A80900204311C1742184080C8308EE1A241C33404A3282251247188D6FEF46712CA182872AB2919678AFF9D94E743E063A39E0C35CAF72A7F2EDA28E65858520D5D8467DE747CF340653B52C268F55413F5ADDC7D49011EC33EDD537423A84288869337AEA0781A124269071451722DB3BB8F2CE5B1552F83D2AF07F25613918A9F4E6F1257603888E589308CA5F95F07143D23BAAE17520B36B6E0E94FAF6845EB2131AEC383E63BC8644EE5F1ACCBA82F9211E57AFCBF509C1131A37466BC91B357DCBBBC14CCC319C4CC6AC75FCDC82C6596D07770C8277AD370B192A0B4E05F812E0E265D2912AA29F03FC9F72DFA69C9B1291A3FC583642B235F6991A954788347F60A0328C48ECEE51BA02DFF323ABD911667CB14549B618F1C5D250CAC9E35E071601992FBEC0BAE6F74213081404744D12F2A0E04BDB265E0924CADA40D1FA1F38ACA4606BFD4575712B8260A456FDDEEEFE7CA259BCDA97B9B939A5FD2889C9B49FB7D4E3553DEA61B3339BD0E6B16BF3BB227103BF9202E72DC502E28F7CE1559A4631F372520324E4EBA07545F78BF4D94B0E5B8BF51B8F176533D5CFEA5232F283A47605FA65DDB17C891C251011C4E98EEB6EB00CB65BA31C8F025C87A9FE02DBC10C5D83A065EBA5D7B2A19D5A1CB2C160AE166E867F2AF8C7D49D63FB83A614957FC0A3B5A5C74990E9A2B02120C7E6DE37E155FB472F50F0A45E47CF5F9D7A4C82982C9DC86AE877C3FD1885943E439FB003C7A9A42F71B4FF6F0A28B140CBDBA6E71B13AC31B23DE9EAB7837E15A69F833EB7B56A71D8BC2CAF1F2A31C345BD5F46EE013A7C689372337191DAA800C0AC6C46C9FF688B1A01347F257C474AA3D97C1D63A8C00E0A37B681673F57C1C9C8FCCD46F174C74A29D84CEB71F7E6B2F8CD2B089ED43F7C96DAE81A223418C20B16F1DF3D1A978AE28F6DF35EC559D04D20EC74B224AEA31A289B015B069E9CBBBF7CF6DE94CFB2A96E4AE3462C96003CDDA87DB561AF2CE3C0BA1D90413FDCE3CCF4390C02C1CB9F654F4820EC33015457D4A629FBF39419CAB7642D6885E103FCE0D4206CCE7C12C6FC44FA33AD0864C3371A7CBE820E3B371B656A38F2E7FF18FE4A50C8AB3F85D783FB57835CED8490B84EE0D99AF0D64C483CEB6366FF54F8AC8A40DB1AFA573A4FB326C74F0236ECEF3DA7120665CCE05DD654B5071723A8348E7CD7793513819B61CB64E1328E8B22E7664BD6B41B5710D19EA8809D4450850E907DFC4D0B75F588CECE962E9E0937CE1402446A4D2891A46E6617FB29D4FCD712606F7819ECA60F7E0D5B19E7FFB57C73C16FFEEB90038410CB9FCBB5E9D51EB3EB6297E9FF6AB7088FE2D9B237BC24CF7F8290118A5E0E00A0B903FB6375C848176CD0A8C8875CC59199CDA11A87A78F65CC404330B087571FD0633E27129FDAB5A8A1F793E52412B0083FD5C74DB3CF60C2543CE7C91B2800E40203F8D99FE5FDE5B108E7EDC80EBB9BB34986EC5C5A8F580E75752907FF0F294C866C2CF1F362E840B6881BD43219201781C63B0039A95BCFB4A0FECE569DF00523CE9C084B022B3B022242E28419796ACF0A0C995F948DBFFFD30D77ED105A3C9943C406B305BC81A6A248A291548F2A67F438D966A57D53F4B7BE15354E581BE16F7AD64D164E85787DF5849C810AFC28D06482F441B5FDE3DB2ED36DD25AA6664D4D43FFA32EDA25689C9F4A5D514FC66231C5401520922524438EF1DC78D693C9718DEBBD243312674C899F18910E389C8EBE505824BCC42CD4A9ACE193768220219011F3B1F335427BFF9E8BDED5C08711A09C2B71CB964C56A8393BFD2B56E9B6B2F513E682587DC1B8ED196066326871025628036700063176D345DE384E182D6C417A32AB11095EF59BB4D171B9CF81D17AC42664DED933CCB722C69857FFC53C8E7F2474B0CB2DFF2DDC8A5C601C84A701981199BCCF74112A6EC062C4FEB601A028AF01032ADB6BD15D4C2B9550AA850AD62CCC3A3665D5212B12E0FD5C5326A1E5EB1F10D557D94605E8E3F356E08FF7FD884ED3C4205463594C9AF2F39E4B1274695234B54EECED93F460EDF1A13C2CB4B17D322F6F79FE16F0357C1C4739863E796791F8647FABF730AB00E0DA509706D94571740F61F7BAF366D2774C9B5B8C61DD6BE9819A6028B264BB2E4AEA54B56D4ECAB5B528CE0C0C0CCDB73023352CB00445BAB6F7467B4644D4361C464FAC6B5B137D32391021B475FCB5F31774FD8ECABDF65475F25574C65559CB331F41C0F498B74DD941C344C50D8E64F9578714A32561FAACEAF78148E6DA4B566826925714B17108AFDD546385A3CD454D5CAA16960916282A47C4315CE236BD9E3255C604EBDC39772DB5CE0B236").expect("sk couldn't be read"); -// assert_eq!(expected_pub_key, kp.public.get_encoded(), "Public key does not match"); -// assert_eq!(expected_priv_key, kp.private.get_encoded(), "Private key does not match"); -// -// let pub_key = -// MlDsaPublicKeyParameters::init_from_encoding(new_ml_dsa_44(), expected_pub_key.as_slice()); -// assert_eq!(pub_key.t1(), kp.public.t1(), "Public key parameter t1 does not match"); -// assert_eq!(pub_key.rho(), kp.public.rho(), "Public key parameter rho does not match"); -// -// let priv_key_result = MlDsaPrivateKeyParameters::init_from_encoding( -// new_ml_dsa_44(), -// expected_priv_key.as_slice(), -// Some(pub_key), -// ); -// assert!(priv_key_result.is_ok(), "Private key generation from encoding failed"); -// let priv_key = priv_key_result.unwrap(); -// assert_eq!(priv_key.rho(), kp.private.rho(), "Private key parameter rho does not match"); -// assert_eq!(priv_key.k(), kp.private.k(), "Private key parameter k does not match"); -// assert_eq!(priv_key.tr(), kp.private.tr(), "Private key parameter tr does not match"); -// assert_eq!(priv_key.s1(), kp.private.s1(), "Private key parameter s1 does not match"); -// assert_eq!(priv_key.s2(), kp.private.s2(), "Private key parameter s2 does not match"); -// assert_eq!(priv_key.t0(), kp.private.t0(), "Private key parameter t0 does not match"); -// assert_eq!(priv_key.t1(), kp.private.t1(), "Private key parameter t1 does not match"); -// } -// -// #[test] -// fn run_kat() { -// println!("{}", "ML-DSA-44"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_44.rsp"), new_ml_dsa_44()); -// println!("{}", "ML-DSA-65"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_65.rsp"), new_ml_dsa_65()); -// println!("{}", "ML-DSA-87"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_87.rsp"), new_ml_dsa_87()); -// } -// -// fn run_test_vectors(test_vectors: HashMap, parameters: MlDsaParameters) { -// for (count, tc) in test_vectors { -// //println!("count {}", count); // HashMaps are not ordered in Rust, i.e. test cases are in arbitrary order -// -// // -// // Generate keys and test. -// // -// let random = NistRng::new(tc.seed.as_str()); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), parameters); -// let kp = match kpg.generate_key_pair() { -// Ok(kp) => kp, -// Err(error) => panic!("{}", error.to_string()), -// }; -// -// assert_eq!(tc.sk, kp.private.get_encoded(), "count {} private key does not match", count); -// assert_eq!(tc.pk, kp.public.get_encoded(), "count {} public key does not match", count); -// -// // -// // Signature test -// // -// let signer = match MlDsaSigner::init(true, Box::new(kp.private), None) { -// Ok(signer) => signer, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let mut sig_generated = match signer.generate_signature(&tc.msg) { -// Ok(sig) => sig, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let mut sig_attached = sig_generated.clone(); -// sig_attached.append(&mut tc.msg.to_vec()); -// -// assert_eq!(sig_attached, tc.sm, "count {} signature does not match", count); -// -// // -// // Verify -// // -// let verifier = match MlDsaSigner::init(false, Box::new(kp.public), None) { -// Ok(verifier) => verifier, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let vrfyrespass = match verifier.verify_signature(&tc.msg, sig_generated.as_slice()) { -// Ok(ver) => ver, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// sig_generated[3] = sig_generated[3].wrapping_add(1); -// let vrfyresfail = match verifier.verify_signature(&tc.msg, sig_generated.as_slice()) { -// Ok(ver) => ver, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// -// assert!(vrfyrespass, "count {} verify failed", count); -// assert!(!vrfyresfail, "count {} verify for bad signature didn't fail", count); -// } -// } -// -// fn get_seed_buf() -> HashMap<&'static str, &'static str> { -// HashMap::from([ -// ( -// "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1", -// "7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D", -// ), -// ( -// "64335BF29E5DE62842C941766BA129B0643B5E7121CA26CFC190EC7DC3543830557FDD5C03CF123A456D48EFEA43C868", -// "4B622DE1350119C45A9F2E2EF3DC5DF50A759D138CDFBD64C81CC7CC2F513345", -// ), -// ( -// "BFF58FDA9DB4C2D8BD02E4647868D4A2FA12500A65CA4C9F918B505707FA775951018D9149C97D443EA16B07DD68435B", -// "1D836E889E46259BCD1CCD2B369583C5B47CFBB919EC2B72C280247CB15A5569", -// ), -// ( -// "58C094D217BC13EDFDBEA57EDBF3A536F8F69FED1D54648CE3D0CCB4847A5C9917C2E2BC4D5F620E937F0D329FCF8A16", -// "539577CB7F2088FBEDFF1B53F235D607321857DB32BBA645F8DF3A89DD426552", -// ), -// ( -// "F1902A7815F37BC7F5802D8CBCE5B48D82EB85691718062BFB84D8C06AA41D6E9039B0A107245DAFA4EC109A57332914", -// "2CA59C6CF33C53803749F69EF5ABFA9482FCEE7EFD87FBF17135ECC3FF3FD7F7", -// ), -// ( -// "75224ECC026C18159FF92256844D0ADF953F0A4DD8D74D4EBF1DC5EE8F5630B011A447FD4DC34A2404D620CA0E1F273E", -// "E17E72290E49A44C9C534F211195257CF13B0D45405782CEDA2D7F982A551721", -// ), -// ( -// "447F03C8CD27EDAA1FA0436DA492812F57AC946479A9F1F90EC4F5E913A05F8AB0DD7645026A96510F6D40AF05D85B07", -// "3B7388E675DE5C59A78AF095481C7DD999C6EEA898595B1E7DCDA7EDC3A2C25C", -// ), -// ( -// "8C151C556DA912A82DEB32144C8A8C9090CFAF5C12AB822AC3C72618837A41C2453B715EEFF3724CAFE69B1ADCAE9DDA", -// "DC9F40CABE2E8E4F3D1538FBC1ADA27B61B99081455AB0C4C41B5B3DA8101000", -// ), -// ( -// "9B42F41492530EAC81992F17613EFDF155F407D7E67F18AE193EDCE714D65D1031E7AD10839AAB46D0850EAF5997AB4D", -// "1DADE637AE98C393260F5BBBE288373100DD7AF37EBA913C528D2B7B998767CB", -// ), -// ( -// "11134936880F5A11ED3504CF7B273E55A351FCCB10943BBBD186623EE6C7A13A6565C3080D1F536BFDB018F99C4E46CD", -// "8866693CEE12B909E32A0C64381796633666417E1246B51A2643564B464B4113", -// ), -// ( -// "98DDA6B97E89A479D5EE214E660DD6B5D8F6CC638A1CD4F462A0EC545F5B0B0A1A403AADF566F7B1C0C5FFCA29B36FCB", -// "D6DAD5B2746422F4487B72536D70DF88AF4B2F9040AA45999F8D7784EF696DA0", -// ), -// ( -// "D34A0AAD27ECAD31A5E08E9A2D7901A9B85F864D9B1B46F40CDCA0B3615B2CBA04EF82AD7BD8CF627C3E861477030BE2", -// "68E7818F33B97BA6166768C395BD010CEF7BCE9995891D164303B53C1123A991", -// ), -// ( -// "4FDA9FB6929E3F391901D69FA0AA2F25A9657D249A620F1B9E305A5965676BA76794CAD3355EB632579C3958CA7D443D", -// "35B153A7706109D4A13D7C4B26AA5B56D9E3FAC53B47E91B0C10BD4E0EAAFC19", -// ), -// ( -// "B0E6A23FAB10A7A333E3720BE00D31507917F39C5EFE1C98CA18BEB5C3101FB4479B478A1558C4C00398C55C9822FC44", -// "0E1A1634FB2396E187CD8980EF29663C42DC3EF963CCD491F817A84283A11FA0", -// ), -// ( -// "0A98A2BD2B9FF42CFC18D3396BAD052E1D0F3372854DA69A318B142F7A1AAC609C3861263BD8FB0549DA7266784DB8B4", -// "B0BFA060F1C1A70F1AC55E321E6186A6613605DD732574B5FE6E14F0FF6F7A82", -// ), -// ( -// "9887F1FD854241A301EE0120645CD8E119B43F7BEE11F77A835E9ADF518C3A51CB76D86653FBE73AA716264C146797EE", -// "A33BC0A7A08C13C0D4C1174DDD886AAC4C5666E1F4831F006C9519D36B2CE882", -// ), -// ( -// "5B485527C3B9A5E5B7579950049CD357975D4BCFEF83FE33C087ACBFCC10A0BE4225E7F8A5F77203B5FC7C0B5FC0E78B", -// "C7E33FA5329142B668CCDDE1057EB7A8619397537F2B4C6D6755B3B9FF936441", -// ), -// ( -// "327CE565CFF6CD9A25EDD84F482FA0758B78CBC246567DAE98B818314AE28CD438E339043EB3FF16E1C2B4B104A717B8", -// "7611B5B7D4195D5F8B97244B6811748EFEA929EA272E66435A36D0BD16E3BF21", -// ), -// ( -// "790FC03F956D1301A735504075B67A05944A762E0A4BDA77BB8C036C5CF911E2B561EC1CA6AA355D5CEC919AED42A1D2", -// "5A1E3E05C72CEF1A73EF98840DA035E4FD2552912DB8DAE28A79011DE4BBC1A4", -// ), -// ( -// "716354F7DEAE272CD26929C0932CA257AED1DD23D67260726B5213D82E61466FA99BB6A7D81DEE9D0EBE03DEEE4DBFC7", -// "8F3920A235EEC3659CFCFE62931474204EAE264959702F901D461B66D9BB563D", -// ), -// ( -// "A32E6FF879EC8866A5F5E4F6318DA8FE6743812ED2CF5FB94F5C3AA3EDF953CBC32665810B71B2CFEBF343A571CBC570", -// "0B2B3EB50681403A0B9A99B25041A489C6D45D2A49DE0EC83E1FD10922ABE2D5", -// ), -// ( -// "5A64401EF8E63AEE18E8CC0162845DC7AF388230E86728ECB330007F2546F949764273EA05B397FE71F567E1527FA445", -// "8217D32CD15658D39CDCA92C41B59F5780869A68838A3579DEA48B5E3EA768AA", -// ), -// ( -// "3222E4B55D6767E300FDE03DB3D8227E19FB8B08EA9B923FEDE18D699DC3694EFFA7C4DAE2AF57E4A0162B7C564199BD", -// "CC625322C9D52898E7F60AE47BC2847E20F3722794DE41E30FDB20CA1A093208", -// ), -// ( -// "F41B3C6225245C06455272A6A073F363E5F19F09A0B146AFCDFC2B3B0EA64BAA3F90359F32B2D1017608B03064E90AB2", -// "950226D6AB0B774C5F439AFCFD0113B5DBF5905960C445F5E6E03E5D5C687A9A", -// ), -// ( -// "A08AD391E0FC57A83B74CA8CF44DB67F8178262ED9B20AA0163CDD8274AC2BE05F558B112B094244370C1AAAB75077E6", -// "A6B534767A6D839FD19075AE0BA10147C46862BF7BBCBE83F2B72F72F1368A1F", -// ), -// ( -// "6E0A8EF5156D693FD0140BC4A31084E79773A83F42C8D133AC8A9D62DE3CD74511F893DCB26041E6B35E2B175408FCE7", -// "103164ED522DF0DB131C15E139C0F83D9B1B7A1B6ECF7F89A5248CAD7E68DE8C", -// ), -// ( -// "49CC05312D1DBE216FF03B60575017A6A1464C06D2C5A4A6F973AD9F275F7C66163A29A803BE759B117043862D277C27", -// "BC962D978F38881085C1B813BC90EEE44AD9E7651681C20BA46402F557C454DE", -// ), -// ( -// "C33EE43A9CBB4347BFAF71147B7FBDD88D212462CB06FBE695A35402C503CD15732B7D0E8BF829A555B9167BCFA2F2BF", -// "C3DE54854A4060EA09ED92A363F71C7863EBA64195E9AC79E7AD7EB6A183CFAC", -// ), -// ( -// "19CB4BE2332F7FF0C078BC001FAB3C5FD8569A76EBCE373D1ED4FC8EB5D744C6464E2B5EECB9EE836CD5D87BEDA78BA7", -// "828B9804524BDD17D0EB387368B01B0E95B4960057ED63FC2289D858201E207E", -// ), -// ( -// "6BD93FD13C0299B3EC7403638673F3DBC449F3A617B691DDF73C072B62BF028913375D7460BED2CF9FDCA517690CBAC3", -// "4A84CA5C3954FAAFA11AE87FCBE701EBB5AFBCC5F8ECAE7786D10821E01ADA5A", -// ), -// ( -// "1787C82DA9F2E6CA9ACF7D6CCA70116A1724902C81EDC1439F332C74807AF2BCCCCDC7AC1788BA798520B2999F39DC3B", -// "3E74AE2B1D49EE6F149076F0BAE2D26A5CADFD5DE7BEF66DFCAE6B588A1F4067", -// ), -// ( -// "9E6E12F025B2A57B0F5A3A9FA70396FC332E1802608E5CA07CC4FBA922F1FE5DEA6721B96F1BA2BFB97825A19F08FF2F", -// "39550BD2782D66FA95380F5F101D827377B11410F8BF3BCCFBE0E504FC09AE38", -// ), -// ( -// "569B8B9BDB707B19CD6F9BEB29F304D603C1509B9CF25987C280C342E870B1E13EFC7DD7E41DC85BF4F42D0493B84B0F", -// "B18F0FDF9DC4F514107F88CC43FB29190608EBC5A2CD00B49FE20631761038DF", -// ), -// ( -// "F32C3715B0BA8C1D0BD59F0645E9697DFCF9AEAF761A71ECDF9672215B9F138C0502D7214F6B1BB4D6612432F9FBED5E", -// "D4FA14DA39548392300A41BE413EBD53BD7BCBD045B4D3C8CA44ABC9599E269D", -// ), -// ( -// "B0C7530A52AC9F561C2C14548D3A5F5053396B738EA1C7A5190F5AB01C9C38719C4DBE856E42D37A114FA24FD5DF5081", -// "C796FD12D1FEB1DF46B162C38292684C09059E4463CB95DBDBF498A4DD4F7F00", -// ), -// ( -// "B2FD7BFAAFB667C9DABE5915C3BC271EF41F18588666A6F4990C09D098E62DB590110DF6A56F08C5E0DE65B00F91D60F", -// "A18A366A5ECACAE4732DC9E954333EAD153203013BAC4E3C50BEE15269F983FB", -// ), -// ( -// "C08E846A8E039C8655651919A8433D475F494899FB617DC3B4715DEF0C992C195CE38158B7FF40E0684B30FD7E623265", -// "585E714D565AA66078BC2B12699F1E86C6FF30A1ABC8CBD19563BCDDD2F1F6D2", -// ), -// ( -// "1D9C060EA0408A068BD982D9694D39D02BA5A473378F6F9F09349F686566F331E767263FAFF5DC0E823BB6F648843876", -// "662CF70D3D5E95A9C6A33BD7C6ABF0E8CD23AB2D2D9420878C4835DE14A6C606", -// ), -// ( -// "A4563D09AD21D3916BF4636301F2E64183A8F003DA186753D7F2DC3BE0089BA09C62B8A52B72C2C8451213606801FB29", -// "1924A71628292AA3D2D34EA72E2BFC2520864205F54EC6F19F7714733AA34CC9", -// ), -// ( -// "811A8A2ED2917CC616FAF246C5F9BB902E5FBF5430AB078AD6CE871CF8C160512A748216EFAB3A4CE1271AAFEA12C11B", -// "EF7BA21809AE7E0BC3230B6061C5FEE206D805572CF1345198E1EF22A8FE7322", -// ), -// ( -// "41CC9DB2E90239AB5158A2628E7478D0B3512FDF84CD27A4CA5FE3119A455C22045F198C3C5C39F491FB975BD1CFF7F8", -// "CB0B305FB54E1CB23B63EC1F6F4689137E5048D095FB3EADC854C852CA86BE93", -// ), -// ( -// "1C13369824A3FDD41B1065E17297574715D9BD9CE5BB733D36D22C31B62BB1033989A604D78BFB1A0746BD4A2271FC0C", -// "F92FFA3A36F43F9177763AD320FD651D9357C6D99F09549FE6AF12943B58BE90", -// ), -// ( -// "7AD6C7DF00C9E52A75290D28DA946305D83CCF6DE2515C19A8E26850C34C8C2E545E2E32108F13B9C97F87AB68D10131", -// "5D3CCE926A795ABC5F6632CABFF8BF66275DCC7E4A4AB3B8399D23E62A28BD16", -// ), -// ( -// "38FFDE9B60DEDB5BBFAD6C52AA02EF6D49369BF276C99E588D796A4F260E0FF0A65C96C35863BAACFFD9B212EC305E7F", -// "BA2386BA92AA89049C64ECFE60FDDBE136815D3874527414B63ED32215F2E06F", -// ), -// ( -// "ACC98B16DCC9A50EF57F332D66255CA56C2BB679CAE705B4297F1418DA845861448DA6CC5CC458DE6C6E96128EEB2898", -// "B4E1AF25E8DC6934BA391A89984A358702BDD36838BABEBD982638703F20EEF8", -// ), -// ( -// "8BEA4E384E73C7E0B47381B3063334291A0F06D28DB61B5BF65B01D0A747722E0AA62B81AD46C00C8A5C31494E513836", -// "A62875A3A6D305E120DC7975962552126CD844554857C2943872A4E524A6EEB5", -// ), -// ( -// "CFA713E4A63A6FFBA43BFB898956DC400507F68AD164C3D24A67B5F8D7548C9DB44DAA43E5E4A0990325A4233089318A", -// "76AE71DED1F9E73AF77A2FEAE4EEF80F87414DFB7580FB4AE0325BFF20D74A5D", -// ), -// ( -// "1F3193EBC58EF65E9E396D69220ADB8ADC729BB388A72CEC9028A094F1CBDED21CFB0C41356AF31E0CF66A3B0D843666", -// "A8F65BE046001A6814F537915BE3F03F3670E1169E4AAA6D7E726174ACAEC77C", -// ), -// ( -// "CF5A04DDB5EBC45328F703D486D24443A7692D65AA55F054E3078DB76A7939590A3F35CF1A21E82A845445DD1B64A85A", -// "802E08C14F6E3446BBF7F4666C8DDF7755DC718C3E02B7865FF33E9D8290ABEC", -// ), -// ( -// "8C3D2FBBE0D39E293AF2D2CC5A9BEDEAAE3752DFD19CDC1E186D41E717A0412AA429CBDF005445AFDE684656B5D17690", -// "23D7A85A824DF3D904A511281A973C979F67F5BFAF3AB0546E85D0597F91120F", -// ), -// ( -// "C10427EF0B26328163F85D45E22EC5215415326F013FF31EDD58BD3E97B1A72FF07D275D4C1B517F4661B0638F75640C", -// "771CBB7C9FBD9FC5DB93E3E4DE6C034E58BE9BADE93748C42297142124696234", -// ), -// ( -// "4B6B73E042CE76DBE39535E45D3BB2F3B9F8B2BDA170E76CC88666844703E32B2367460A0F6A0A2E3F4E7A6CD32BE998", -// "FA812D8CC3A9631A0239474EB93AD3A2A3480F2D973D3324228EF92A3B043163", -// ), -// ( -// "3D4607399F6FCBE074FD2BEAB1A7571239D6BE6308617866B65B892EE65399E14DC7FA612CDBC5F7E23116FA86C3133D", -// "C660B84D558A7E6B4EAC47C7B62135668E0EF0FBF74D514EAA3D0D428014282A", -// ), -// ( -// "7031BA806F4D8BC28529163B239E0EE836871C51D2D62B601B71D6F2B69B203C81440F8FFC09C3AAD94DB1D880160671", -// "929F309AB3F90CDD9C21EB77A7CA762CA3AFCACBFE3E67B056290835694BA3D8", -// ), -// ( -// "C8671A5D752CC6DDF075C899797603A625C142485EAC3D57CAF14F2244D7F84D116B28F959912A758E519D588A6A07EB", -// "DAE1EBA78AD1568590348088AAE88C1ABEB59626EF65991CD76AB81198E52837", -// ), -// ( -// "D780D7688AF364949A196657A066BD48FFA8DC45B4885279B6DEF362E5957F398CDCE1D20FC3F8F63A275C325FCCE654", -// "15ED428927A7EB0C7C2DC7A98CFBB77BDD773FA8747B8232A6EC4B87CD7DBCE1", -// ), -// ( -// "36AB8588F5233D15674677535A682382C29968FF824031AF646F58FCAF0E83C1C486B1E75479149FD6F4D9E8397CAF73", -// "BA7E359B1F669783521AD35EDABE97141A816C2FABF0AD0E001E21F73CCF7736", -// ), -// ( -// "4E94DD734A371A7C6AD4A567038CF93BAACE2B9D30F3862198DC55D2F21F8FDC9A7AE5DCA1541712179E3AB1FFA3F792", -// "9DAEF95C8D5A61D3A3A267FEFB9F37D6E677D7BA26A3A5BFDBDA8C281BE89CCB", -// ), -// ( -// "D9281003AC5F7673E0E9A7BC29C4ED75E6B0F228DF49D11A2599BFF2DA9E887163BB26DBA4F071FBCE02891540EC6F1C", -// "0531B4105CB209585F9FBC29CAA57E64C2D40F0829931A42CAF7701717D9096E", -// ), -// ( -// "750A74866BE8DF4E60BC14BF36E6D83ABF6DCBB86792D125CF0980007C5435F40F87BA96498A88252D9C5C6710807652", -// "7F8515AA82DBC9EC8CF1DED5AB58EC0D08CF686E25A8C01FB1109A3C68D19E48", -// ), -// ( -// "A832D4AAE8076C4EFE8319A74CE315928AB765BB629075254CBC63EAAE691C220F4B5E1839E9A99D8747AACD7C2F1EE3", -// "5AF3838060E0F83352A75A0EC4ACE2CE8BA119BF89F34CB4D6B8E27007CAE7FB", -// ), -// ( -// "09B8441F47235EFC82D71933A0037FA4F69124C3BAD4EF6A3A7178B417A3FDA874081B7EEFD7EF1BF234C752458FBBAD", -// "B323D48B567F7EFFFFD47A7C9ABF0ADD5F11141737A8AF62B56E042EE498AD6E", -// ), -// ( -// "D2629CEEAE5C95D3C34C1FFCC2338B4A97782BDFCD39111E18540B69DB035B352D012857111F816F03550BFE5F56ABEE", -// "C1CF3107EA9B283419E27DC563ECCE950BEA78C048A3F49FB42128819959E51C", -// ), -// ( -// "EAA4FB8EF0290A499A1D92EE398A8D7E71CD3CBF01A36750DA4B7EFF175DA26D17AC4ECE49A84C88D1D2C2493563C26D", -// "A50FC40F0D9EFA5D254943DC599F7DCC2F6D197A4D2666D5D69CFACCDA560817", -// ), -// ( -// "5909111F333F3E939105DFF8532548927EBF289F31A72F4C1B0C66816D8B68F64622F36A9BC85E63601BEE8EE7CB3DC5", -// "4C0F0EF1CA8073A562D5414584EDF268913D53D5FB39FA639E02E900891EA82C", -// ), -// ( -// "238461A224ABEECCF709AB6CACF4EDD372D45E5F4274095273A49AFE614F2BF713134ABF68B4DD058E6D7B612C3658C3", -// "5BA8AD9B66C93CDE7E7E616A97FED8AA91BC7235FB4DB086CB4021877780C6B4", -// ), -// ( -// "83C653708FAF3E5F6FBC9DFBE6FB5E83E572A7688645D75D2C4835B28695DEA4BD7093740D0FF43237354EAD1C978BC2", -// "25D957B9BF68326D2EFEC93DA464F43E3DF16DD6571CEB1AB68BD58E87734A51", -// ), -// ( -// "BC81485EE93AAD8B464B5199FFEF9FEFC06EA97645BDFE0B4E915B812E606A77F93917ED925E882161CBB909747AC4C8", -// "24F0CD3B05D964F82D3702BF0A613139808D49283286294BE57E13A983C3C961", -// ), -// ( -// "DE9E2742591A5AF6A6153DA85A510C39FD31A2ACD8A8511F190A9A5E5753E63D9801A8019508E67DEB1E9219CC18BA3A", -// "5EF2732B63A1CBD2CC239EA6FED62F7FEB3102715A5BAFD8C83AAC33702FACCF", -// ), -// ( -// "272E459EAB6A0BDF720E4C5B79E641C95BAB66C3CEE261D0E3596BB04D232ACE0A1CE24BACCAAE9037665A962C711B08", -// "6A27B1666AB6FC4483D14CF84EDE49FEDFE05BB24E008AE8A01C52D83B8D40FE", -// ), -// ( -// "DCC58DFC13B035323ED44BE50A7096F697C9C143518FED50A59181160960203831A9904847BA20B85E99FFA63E4AB0B2", -// "2A855572E7E2ADD2888022BDB585B61577A75A31AA8ACEDE59ACB27EEB2936AE", -// ), -// ( -// "270BEDAA7BCD43990FD8B4F44FFB63A3AE8E991BB2BF84DA7BC2CCD1A079C579AEBE2082ACBAB7FF286DE795F31973B4", -// "7967E9DE70A7F95E69371F812C2FBF932CEC07AB4C235AE9E8A6799F3F537D36", -// ), -// ( -// "F151196F55A9ED88F1663AF6BD24B2CB9DCAF3C9B313CD8F0A27639D3CDAE72EA90D60ED5C7C6AB697A06185E5A2E215", -// "5ABA340A8E541568FBEF49E77F94CFB4B3A5E9CF14C6755CE6412CF86CF62898", -// ), -// ( -// "C7ECD1EC1A3D83F5116C0AA4345FB3ADB4D9F81BD79896BC4932EE2F9D2D1F179BAF7A002D88F4F69071A7931E7F7FAE", -// "DF4853F482CC1D0B3A2D71E9EACA064E57C5D100DF79BD004BA81B43EACEC401", -// ), -// ( -// "5DE03CAB3CBD81B8805A17E0FFC2105C3BCDC8D782EAAB161A15AAA543FED59353C1FBE03E7F36B955FC51C9B30F0C93", -// "95DA16B844BAF559C2CD6E68B237614BB9927D90811106347B5849FEE2F48640", -// ), -// ( -// "63742CEFAE9868C3C0B31DDE0F9D378FD5D71BE7CC3F0B6ECD393DB55FB043CF00264852C45D1836CC12B9C872A20251", -// "F258FF1178CC42A3CEBE238C8418B4974812A05F43B8FA95639CC46BC0738BC5", -// ), -// ( -// "B887F07DB5358C3FDC2402947BBC87ABD064B02A859FE8DB37B5BCBB916020443DABA5534A0778FD0B1C05EF3ABE6269", -// "5EFBBBE99CF5C2B6830FB8E990250BE308E662200526889EA973C8D33823EC19", -// ), -// ( -// "D08A139CC7147ECAF4B1D1E434EB2EFA2B2607B0033D8BA989133E496DC9F3654944C7AF91CBB79866443E8C4E8217ED", -// "47A048D8799784F6EC385EB984E70C62CE7C8A107232871B69B99F7BF4C3DBB5", -// ), -// ( -// "A315BCF0E6835892ADFA07C034BFCD39F80B62925A95490B20170BD29378E11559C7F1CD296377FF1E01284EC727FFCD", -// "1D9E243A35118BC7C50A50746E1CF19C9FC310C7D54181FB95F44753EAB1B94A", -// ), -// ( -// "8B47E0EBE786914C9A52D547106CEB4A3D3DE938B3244E02E5F9660954C4C95A23F2476FCCB487673AAD0513820905DF", -// "0A46FDA6BA71125F3415E8BB6C2D8C00601107FA563E7F6386486A88F87701FB", -// ), -// ( -// "07CD8F8AB7CD12EA7CC94103B8623D6F0FEA2BAFD2325BF6089DF5351BDBB9A94525C3C6B72D3820F2E4D5F9E7C849F8", -// "5229DFE11090EFFBE94EE161054CD5FF58B31E23F567B282DB42EB1FE42E44AA", -// ), -// ( -// "3D598F7C498D8A1095C40945975380554BEF6142578638A7627E2C0A21C59C579F8E8CDA309348FC54C764C899FB93E9", -// "18AA77795AA6D7ADE8B6CDCED81A1959A8329677F042283DC8CA71E13EB3ADEE", -// ), -// ( -// "6CDB757AD36DF99E52F535C2680431D5FF36C812D8EA19399F666F2FDD66D3A842A7A5AE1038359AB618FA58A0A6E840", -// "4B2D6EC32BE9C5D8FA11F3FC0008F4F26B945064D98362AD912F452692AC383D", -// ), -// ( -// "A97269579EB70D268C58D94FF744329B197F722A8A407B788510DDCACA34C8CD4C72FFC14B76300C86AEA1E4CFA66BA4", -// "196B8144DFBFB47EC01E96A6B8443211D6C9C4AA7853A8131B5218349BD6D953", -// ), -// ( -// "483A81716F91A43ACA6764C4BD2A57C9156B762E9174EA49730A6BEB9CB19A0B3755E37BA47EC524BBE2FA25B9FEF687", -// "C171793029D0CBAF8D2661A823243AD50D67F2619533180F25B50C94B1310389", -// ), -// ( -// "30F0E117513AAF27AB2516BCEADD1188B4BBDE76E57DFAF43CBF2D70723D941E8F875C5EBF02BD7D67AE81ABCC54440A", -// "615FA91F3D206B908649399F216950EC7B2420EB04AEC6ABFCB7B4528E8E33E6", -// ), -// ( -// "070FFB907EE8AB7152A9D380DEA2C4C4796780FCFD80906C5E489B917A45D5E7EDFE6F37C4420E5480E8BB599FE36451", -// "626C19B8553B2D9E5A47A758615D80B15BE11FD016D3A1962D8DE58ED5CA2219", -// ), -// ( -// "EDBCC4F6AD0F30066947D678A368B960CCD164889D77730516B444ED2DF10B49C101902F5FA227377C3163A0045B34E4", -// "B0D4BA39ADB4E8712B3A3E6495ABA2F04A29E45C68671A960BC0D8D89900C97E", -// ), -// ( -// "DEEE61A2FAC04E4D6B7A250124DFD91518D9B90A71FA02665E3088760BF69CB3CD7B6977F860A7026819D178623C9676", -// "E04328A783C10DACD96702D2E726BB11CE4ECC571564CE7CB10722D1C98C2842", -// ), -// ( -// "DAB6C05E29342106CC34769BF419ADCC88010C05B57E673A503E63AE7A4EE55B72AB2CA86C4EF57FC8C02D2E0C8694A1", -// "CEE2E53099D8CB576F8F76C5C155470F87A6EC5F7D73256A0A2AEE62CBC53597", -// ), -// ( -// "0CAF47BD9AABD7D09FFAD404449BBAB2E1D48E80AC78550831A365BED8765420DBBE9A566EFDF20D4E5233D7848582E4", -// "02D9755C369932E7F99A2E1614B03E2C86D713563785965E008BA987A6C89F49", -// ), -// ( -// "9564E88F336C091EAD50C893F3EAA8351FA388682F433F7A72A34731020B9C96DFCF75EF5EAE47E12684AFA51EFB49B7", -// "2B2BF7CBB0957A86BBEC97001B60C7C6AD98A56E94542FF561F78FED211DA755", -// ), -// ( -// "4D0788DE958A707899D5DCC02F756A10DEA2EFE0214F5E01B3281DF4E013CA75523ECEC64723D6C8BEC0B92C4F821D8F", -// "049569CC5FB969C6EEBB8AEA1AF1FCAF46F8A9E6CD6C796FC7193592BCA9CF23", -// ), -// ( -// "55A9C7A0B49706090BC0702ECFC070AB060427FFC820C3FE05B499B59AEB125F2DB4787A5910B88C6F8FAF0A69BE0AE5", -// "9D89B9A327DF0D341CD2968BA9218BBC3E934502CD88919D8BB16DD3D39FEBF7", -// ), -// ( -// "CEECCCD3F7BB922650E3F6E8F20C47AF17C1C1053EA8FE08226F167D67C3B0781BD774C4C7AAD23C6AB0B9F3E3F96F97", -// "4544C2F21054605B0EEE46F62A87DFCBC3BFEC473B9850886266F478BF9E33D7", -// ), -// ( -// "2489C04BA57D149A60F446670C13C29998B52F3BAD548A751D7134B694DB25ABFA034FB4BA45E105AE27D575CBD02B99", -// "D9931E321732BD82EC9CA1DF12BA48549BFC7D3E76A404B71892F4198777FFBB", -// ), -// ( -// "26CF860726D4DFA38AE07399838BB336F1BEE59E9F23AE4C81E73D49964997EF21CB5F5412F9A70A1EC39FC6228C36CA", -// "F838451E4A5929B8BAE9084B40B1DC0EDFB76A9354BF27F981960C88B0BA3A11", -// ), -// ( -// "13F1F446D9AA5AC853278BF74C9E6447A6CE4294C037867F43DF554370EE261D05C7260EEBF46D6694D0850B8343FBE5", -// "8E4334B2589D0CAECF0FD9BA584EA26A4123D4543A8A0FE126D4A7E07F6067AF", -// ), -// ( -// "6F6E47E8336ADEE99B2C52CF2DC8D461E0A54C3DF2F08199A9F0816AF8455381054CE47A7766726D3AFC2E2F2BEAF8E8", -// "37519A02E8021F2257259C0D2E499AF3533C8ED8DD5BF7751CCE920D79B518FA", -// ), -// ( -// "CB2E6226615393FC3BD4AB3A412AAA030AAD40E8648EE6B56D2C1591D8B97915D88F2D22F7221377B4B04CF2AE9ECC4E", -// "690482BFF6C1D0BA6C071DD395ADF69E55E1BFC4E0992A8650FFB5E60A02B172", -// ), -// ]) -// } -// -// struct TestCase { -// seed: String, -// mlen: i32, -// msg: Vec, -// pk: Vec, -// sk: Vec, -// smlen: i32, -// sm: Vec, -// } -// -// fn read_test_vectors(path: &str) -> HashMap { -// let mut test_vectors: HashMap = HashMap::new(); -// let string_content: Vec = -// fs::read_to_string(path).unwrap().lines().map(String::from).collect(); -// -// let mut i = 0; -// while i < string_content.len() { -// if string_content[i].starts_with("count = ") { -// let count = string_content[i].split(" = ").collect::>()[1].to_string(); -// let seed = string_content[i + 1].split(" = ").collect::>()[1].to_string(); -// let mlen = string_content[i + 2].split(" = ").collect::>()[1] -// .parse::() -// .unwrap(); -// let msg = hex::decode(string_content[i + 3].split(" = ").collect::>()[1]) -// .expect("msg couldn't be read"); -// let pk = hex::decode(string_content[i + 4].split(" = ").collect::>()[1]) -// .expect("pk couldn't be read"); -// let sk = hex::decode(string_content[i + 5].split(" = ").collect::>()[1]) -// .expect("sk couldn't be read"); -// let smlen = string_content[i + 6].split(" = ").collect::>()[1] -// .parse::() -// .unwrap(); -// let sm = hex::decode(string_content[i + 7].split(" = ").collect::>()[1]) -// .expect("sm couldn't be read"); -// let v = TestCase { seed, mlen, msg, pk, sk, smlen, sm }; -// test_vectors.insert(count, v); -// i += 7; -// } -// i += 1; -// } -// -// test_vectors -// } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_wycheproof.rs b/crypto/mldsa-lowmemory/tests/mldsa_wycheproof.rs index ce1cddb29..8a30f5a6e 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_wycheproof.rs @@ -174,10 +174,7 @@ impl MLDSASignSeedTestCase { rnd: test["rnd"].as_str().map(|s| s.to_string()), tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, comment: test["comment"].as_str().unwrap_or("").to_string(), - msg: match test["msg"].as_str() { - Some(msg) => Some(msg.to_string()), - None => None, - }, + msg: test["msg"].as_str().map(|msg| msg.to_string()), mu: test["mu"].as_str().unwrap_or("").to_string(), ctx: test["ctx"].as_str().map(|s| s.to_string()), sig: test["sig"].as_str().unwrap_or("").to_string(), @@ -253,7 +250,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -354,7 +351,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -455,7 +452,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -572,7 +569,7 @@ impl MLDSAVerifyTestCase { match MLDSA44::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { @@ -612,7 +609,7 @@ impl MLDSAVerifyTestCase { match MLDSA65::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { @@ -652,7 +649,7 @@ impl MLDSAVerifyTestCase { match MLDSA87::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { diff --git a/crypto/mldsa/Cargo.toml b/crypto/mldsa/Cargo.toml index 6b852769e..2be30c098 100644 --- a/crypto/mldsa/Cargo.toml +++ b/crypto/mldsa/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true +bouncycastle-mldsa-common.workspace = true bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true diff --git a/crypto/mldsa/benches/mldsa_benches.rs b/crypto/mldsa/benches/mldsa_benches.rs index 09ecf27e7..2ce089b45 100644 --- a/crypto/mldsa/benches/mldsa_benches.rs +++ b/crypto/mldsa/benches/mldsa_benches.rs @@ -165,7 +165,7 @@ fn bench_mldsa_sign_from_seed(c: &mut Criterion) { /*** ML-DSA-44 ***/ let tr = { let (_pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - sk.tr().clone() + *sk.tr() }; // Signing nonce; it increments each time @@ -185,7 +185,7 @@ fn bench_mldsa_sign_from_seed(c: &mut Criterion) { /*** ML-DSA-65 ***/ let tr = { let (_pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); - sk.tr().clone() + *sk.tr() }; // Signing nonce; it increments each time @@ -206,7 +206,7 @@ fn bench_mldsa_sign_from_seed(c: &mut Criterion) { let tr = { let (_pk, sk) = MLDSA87::keygen_from_seed(&seed).unwrap(); - sk.tr().clone() + *sk.tr() }; // Signing nonce; it increments each time @@ -307,19 +307,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA44_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA44::sign(&mldsa44_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-44", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA44::verify(&mldsa44_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA44::verify(&mldsa44_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); @@ -331,19 +329,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA65_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA65::sign(&mldsa65_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-65", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA65::verify(&mldsa65_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA65::verify(&mldsa65_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); @@ -355,19 +351,17 @@ fn bench_mldsa_verify(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA87_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA87::sign(&mldsa87_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-87", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box(MLDSA87::verify(&mldsa87_pk, msg, Some(&ctx.to_le_bytes()), sig).unwrap()) + for (sig, ctx) in &sigs { + black_box(MLDSA87::verify(&mldsa87_pk, msg, Some(&ctx.to_le_bytes()), sig)) + .unwrap(); } }) }); @@ -396,27 +390,22 @@ fn bench_mldsa_verify_with_expanded_key(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA44_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA44::sign(&mldsa44_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-44", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box( - MLDSA44::verify_with_expanded_key( - &pk_expanded, - msg, - Some(&ctx.to_le_bytes()), - sig, - ) - .unwrap(), - ) + for (sig, ctx) in &sigs { + black_box(MLDSA44::verify_with_expanded_key( + &pk_expanded, + msg, + Some(&ctx.to_le_bytes()), + sig, + )) + .unwrap(); } }) }); @@ -429,27 +418,22 @@ fn bench_mldsa_verify_with_expanded_key(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA65_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA65::sign(&mldsa65_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-65", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box( - MLDSA65::verify_with_expanded_key( - &pk_expanded, - msg, - Some(&ctx.to_le_bytes()), - sig, - ) - .unwrap(), - ) + for (sig, ctx) in &sigs { + black_box(MLDSA65::verify_with_expanded_key( + &pk_expanded, + msg, + Some(&ctx.to_le_bytes()), + sig, + )) + .unwrap(); } }) }); @@ -462,27 +446,22 @@ fn bench_mldsa_verify_with_expanded_key(c: &mut Criterion) { // use ctx to make them different (in addition to the signing nonce being different) let mut sigs = Vec::<([u8; MLDSA87_SIG_LEN], u128)>::with_capacity(1000); - let mut ctx = 0u128; - for _ in 0..1000 { + for ctx in 0..1000u128 { sigs.push((MLDSA87::sign(&mldsa87_sk, msg, Some(&ctx.to_le_bytes())).unwrap(), ctx)); - ctx += 1 } group.throughput(criterion::Throughput::Elements(sigs.len() as u64)); group.bench_function("ML-DSA-87", |b| { b.iter(|| { - for i in 0..sigs.len() { - let (sig, ctx) = &sigs[i]; - black_box( - MLDSA87::verify_with_expanded_key( - &pk_expanded, - msg, - Some(&ctx.to_le_bytes()), - sig, - ) - .unwrap(), - ) + for (sig, ctx) in &sigs { + black_box(MLDSA87::verify_with_expanded_key( + &pk_expanded, + msg, + Some(&ctx.to_le_bytes()), + sig, + )) + .unwrap(); } }) }); diff --git a/crypto/mldsa/src/aux_functions.rs b/crypto/mldsa/src/aux_functions.rs index bc9f1b1e0..56ffe1aa7 100644 --- a/crypto/mldsa/src/aux_functions.rs +++ b/crypto/mldsa/src/aux_functions.rs @@ -1,380 +1,16 @@ -//! Implements auxiliary functions for ML-DSA as defined in Section 7 of FIPS 204. +//! The auxiliary functions of FIPS 204, Section 7, over whole vectors and matrices. The +//! coefficient- and polynomial-level functions they are built from are in +//! `bouncycastle_mldsa_common::aux_functions`. use crate::matrix::{MatrixTrait, VectorTrait}; -use crate::mldsa::{G, H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN, d, q, q_inv}; -use crate::params::{ - GAMMA1_2_POW_17, GAMMA1_2_POW_19, GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, - MLDSAParams, +use crate::mldsa::N; +use crate::params::{MLDSAParams, MLDSAParamsBase}; +use bouncycastle_mldsa_common::aux_functions::{ + bit_unpack_gamma1, bitpack_gamma1, expand_mask_poly, power_2_round, rej_bounded_poly, + rej_ntt_poly, }; -use crate::polynomial::Polynomial; -use bouncycastle_core::traits::{Hash, XOF, XOFSqueezer}; use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; -/// Algorithm 14 CoeffFromThreeBytes(𝑏0, 𝑏1, 𝑏2) -/// Output: An integer modulo 𝑞 or ⊥. -pub(crate) fn coeff_from_three_bytes(b: &[u8; 3]) -> Result { - // This is the exact alg from FIPS 204: - // let mut b2_prime = b2; - // if b2_prime > 127 { - // // set the top bit of b2_prime to 0 - // b2_prime = b2_prime - 128; - // } - - // but this is equivalent and feels more constant-time: - let b2_prime = b[2] & 0x7F; - - let z: i32 = ((b2_prime as i32) << 16) | ((b[1] as i32) << 8) | (b[0] as i32); - - // mutants note: yeah, this could be z <= q and nothing changes because z == q is the same as z == 0 - if z < q { Ok(z) } else { Err(()) } -} - -/// Algorithm 15 CoeffFromHalfByte(𝑏) -/// Let 𝜂 ∈ {2, 4}. Generates an element of {−𝜂, −𝜂 + 1, … , 𝜂} ∪ {⊥}. -/// Input: Integer 𝑏 ∈ {0, 1, … , 15}. -/// Output: An integer between −𝜂 and 𝜂, or ⊥. -#[inline(always)] -pub(crate) fn coeff_from_half_byte(b: u8) -> Result { - if P::eta == 2 && b < 15 { - // Original code is bad because '%' is not constant-time. - // Ok(2 - (b % 5) as i32) - // I'm still not convinced this is constant-time, but maybe it's closer? And I can't come up with anything better. - let b = match b { - b if b < 5 => b, - b if b < 10 => b - 5, - _ => b - 10, - }; - Ok(2 - b as i32) - } else { - if P::eta == 4 && b < 9 { Ok(4 - b as i32) } else { Err(()) } - } -} - -/// A specific instantiation of Algorithm 16 SimpleBitPack(𝑤, 𝑏) with the constants set for packing the t1 vector -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in [0, 𝑏]. -/// Output: A byte string of length 32 ⋅ bitlen 𝑏. -pub(crate) fn simple_bit_pack_t1(w: &Polynomial) -> [u8; POLY_T1PACKED_LEN] { - let mut output = [0u8; POLY_T1PACKED_LEN]; - for i in 0..N / 4 { - output[5 * i] = w[4 * i] as u8; - output[5 * i + 1] = ((w[4 * i] >> 8) | (w[4 * i + 1] << 2)) as u8; - output[5 * i + 2] = ((w[4 * i + 1] >> 6) | (w[4 * i + 2] << 4)) as u8; - output[5 * i + 3] = ((w[4 * i + 2] >> 4) | (w[4 * i + 3] << 6)) as u8; - output[5 * i + 4] = (w[4 * i + 3] >> 2) as u8; - } - output -} - -/// A variant of Algorithm 17 BitPack specific to a=eta, b=eta -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−eta, eta]. -/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). - -// the hope here is that the compiler will aggressively inline this function, -// and optimize away the branching. -#[inline(always)] -pub(crate) fn bit_pack_eta(w: &Polynomial, r: &mut [u8]) { - // `>=` rather than `==`: skEncode reuses one buffer sized for the largest 𝜂 across all three - // parameter sets and copies out only the first `POLY_ETA_PACKED_LEN` bytes, so for 𝜂 = 2 the - // buffer is deliberately longer than what gets written. Exactly that many bytes are written out - // either way, so this still catches an undersized buffer. - debug_assert!(r.len() >= P::POLY_ETA_PACKED_LEN); - - // temp swap space - let mut t: [u8; 8] = [0; 8]; - - match P::eta { - // MLDSA44 and MLDSA87 - 2 => { - let eta: i32 = 2; - for i in 0..N / 8 { - t[0] = (eta - w[8 * i]) as u8; - t[1] = (eta - w[8 * i + 1]) as u8; - t[2] = (eta - w[8 * i + 2]) as u8; - t[3] = (eta - w[8 * i + 3]) as u8; - t[4] = (eta - w[8 * i + 4]) as u8; - t[5] = (eta - w[8 * i + 5]) as u8; - t[6] = (eta - w[8 * i + 6]) as u8; - t[7] = (eta - w[8 * i + 7]) as u8; - - r[3 * i] = t[0] | (t[1] << 3) | (t[2] << 6); - r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7); - r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); - } - } - // MLDSA65 - 4 => { - let eta: i32 = 4; - for i in 0..N / 2 { - t[0] = (eta - w[2 * i]) as u8; - t[1] = (eta - w[2 * i + 1]) as u8; - r[i] = t[0] | t[1] << 4; - } - } - _ => panic!("Invalid eta value"), - } -} - -/// A variant of Algorithm 17 BitPack specific to packing the t0 polynomial with a=2^{d-1}-1, b=2^{d-1} -/// Encodes a polynomial 𝑤 into a byte string. -/// Input: 𝑎, 𝑏 ∈ ℕ and 𝑤 ∈ 𝑅 such that the coefficients of 𝑤 are all in \[−eta, eta]. -/// Output: A byte string of length 32 ⋅ bitlen (𝑎 + 𝑏). -pub(crate) fn bit_pack_t0(t0: &Polynomial) -> [u8; POLY_T0PACKED_LEN] { - let mut r = [0u8; POLY_T0PACKED_LEN]; - - let mut t = [0; 8]; - for i in 0..N / 8 { - t[0] = (1 << (d - 1)) - t0[8 * i]; - t[1] = (1 << (d - 1)) - t0[8 * i + 1]; - t[2] = (1 << (d - 1)) - t0[8 * i + 2]; - t[3] = (1 << (d - 1)) - t0[8 * i + 3]; - t[4] = (1 << (d - 1)) - t0[8 * i + 4]; - t[5] = (1 << (d - 1)) - t0[8 * i + 5]; - t[6] = (1 << (d - 1)) - t0[8 * i + 6]; - t[7] = (1 << (d - 1)) - t0[8 * i + 7]; - - r[13 * i] = t[0] as u8; - r[13 * i + 1] = (t[0] >> 8) as u8; - r[13 * i + 1] |= (t[1] << 5) as u8; - r[13 * i + 2] = (t[1] >> 3) as u8; - r[13 * i + 3] = (t[1] >> 11) as u8; - r[13 * i + 3] |= (t[2] << 2) as u8; - r[13 * i + 4] = (t[2] >> 6) as u8; - r[13 * i + 4] |= (t[3] << 7) as u8; - r[13 * i + 5] = (t[3] >> 1) as u8; - r[13 * i + 6] = (t[3] >> 9) as u8; - r[13 * i + 6] |= (t[4] << 4) as u8; - r[13 * i + 7] = (t[4] >> 4) as u8; - r[13 * i + 8] = (t[4] >> 12) as u8; - r[13 * i + 8] |= (t[5] << 1) as u8; - r[13 * i + 9] = (t[5] >> 7) as u8; - r[13 * i + 9] |= (t[6] << 6) as u8; - r[13 * i + 10] = (t[6] >> 2) as u8; - r[13 * i + 11] = (t[6] >> 10) as u8; - r[13 * i + 11] |= (t[7] << 3) as u8; - r[13 * i + 12] = (t[7] >> 5) as u8; - } - - r -} - -/// A variant of Algorithm 17 specific to packing z in the signature value in \[−𝛾1 + 1, 𝛾1]. -pub(crate) fn bitpack_gamma1(z: &Polynomial) -> P::PolyZPacked { - let mut out = ::ZEROED; - let r = out.as_mut(); - - let mut t: [u32; 4] = [0; 4]; - // One layout per distinct 𝛾1 rather than one per parameter set; `P::gamma1` is a constant - // after monomorphization, so only the matching arm survives. - match P::gamma1 { - // MLDSA-44 - GAMMA1_2_POW_17 => { - for i in 0..N / 4 { - t[0] = (P::gamma1 - z[4 * i]) as u32; - t[1] = (P::gamma1 - z[4 * i + 1]) as u32; - t[2] = (P::gamma1 - z[4 * i + 2]) as u32; - t[3] = (P::gamma1 - z[4 * i + 3]) as u32; - - r[9 * i] = t[0] as u8; - r[9 * i + 1] = (t[0] >> 8) as u8; - r[9 * i + 2] = ((t[0] >> 16) | (t[1] << 2)) as u8; - r[9 * i + 3] = (t[1] >> 6) as u8; - r[9 * i + 4] = ((t[1] >> 14) | (t[2] << 4)) as u8; - r[9 * i + 5] = (t[2] >> 4) as u8; - r[9 * i + 6] = ((t[2] >> 12) | (t[3] << 6)) as u8; - r[9 * i + 7] = (t[3] >> 2) as u8; - r[9 * i + 8] = (t[3] >> 10) as u8; - } - } - // MLDSA-65 and -87 have the same GAMMA1 value - GAMMA1_2_POW_19 => { - for i in 0..N / 2 { - t[0] = (P::gamma1 - z[2 * i]) as u32; - t[1] = (P::gamma1 - z[2 * i + 1]) as u32; - - r[5 * i] = t[0] as u8; - r[5 * i + 1] = (t[0] >> 8) as u8; - r[5 * i + 2] = ((t[0] >> 16) | (t[1] << 4)) as u8; - r[5 * i + 3] = (t[1] >> 4) as u8; - r[5 * i + 4] = (t[1] >> 12) as u8; - } - } - _ => { - panic!("Invalid gamma1 value") - } - } - - out -} - -/// A specific instantiation of Algorithm 18 SimpleBitUnpack(v, 𝑏) with the constants set for unpacking the t1 vector -/// Input: 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen 𝑏. -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [0, 2𝑐 − 1], where 𝑐 = bitlen 𝑏. -/// When 𝑏 + 1 is a power of 2, the coefficients are in [0, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size -pub(crate) fn simple_bit_unpack_t1(v: &[u8; POLY_T1PACKED_LEN]) -> Polynomial { - let mut w = Polynomial::new(); - - for i in 0..N / 4 { - w[4 * i] = ((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) & 0x3FF; - w[4 * i + 1] = (((v[5 * i + 1] as i32) >> 2) | ((v[5 * i + 2] as i32) << 6)) & 0x3FF; - w[4 * i + 2] = (((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) & 0x3FF; - w[4 * i + 3] = (((v[5 * i + 3] as i32) >> 6) | ((v[5 * i + 4] as i32) << 2)) & 0x3FF; - } - - w -} - -/// A variant of Algorithm 19 BitUnpack specific to a=eta, b=eta -/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). -/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size - -// the hope here is that the compiler will aggressively inline this function, -// and optimize away the branching. -#[inline(always)] -pub(crate) fn bit_unpack_eta(v: &[u8]) -> Polynomial { - debug_assert_eq!(v.len(), P::POLY_ETA_PACKED_LEN); - - let mut w = Polynomial::new(); - - match P::eta { - // MLDSA44 and MLDSA87 - 2 => { - let eta: i32 = 2; - for i in 0..N / 8 { - w[8 * i] = (v[3 * i] & 7) as i32; - w[8 * i + 1] = ((v[3 * i] >> 3) & 7) as i32; - w[8 * i + 2] = ((v[3 * i] >> 6) | (v[3 * i + 1] << 2) & 7) as i32; - w[8 * i + 3] = ((v[3 * i + 1] >> 1) & 7) as i32; - w[8 * i + 4] = ((v[3 * i + 1] >> 4) & 7) as i32; - w[8 * i + 5] = ((v[3 * i + 1] >> 7) | (v[3 * i + 2] << 1) & 7) as i32; - w[8 * i + 6] = ((v[3 * i + 2] >> 2) & 7) as i32; - w[8 * i + 7] = ((v[3 * i + 2] >> 5) & 7) as i32; - - w[8 * i] = eta - w[8 * i]; - w[8 * i + 1] = eta - w[8 * i + 1]; - w[8 * i + 2] = eta - w[8 * i + 2]; - w[8 * i + 3] = eta - w[8 * i + 3]; - w[8 * i + 4] = eta - w[8 * i + 4]; - w[8 * i + 5] = eta - w[8 * i + 5]; - w[8 * i + 6] = eta - w[8 * i + 6]; - w[8 * i + 7] = eta - w[8 * i + 7]; - } - } - // MLDSA65 - 4 => { - let eta: i32 = 4; - for i in 0..N / 2 { - w[2 * i] = (v[i] & 0x0F) as i32; - w[2 * i + 1] = (v[i] >> 4) as i32; - - w[2 * i] = eta - w[2 * i]; - w[2 * i + 1] = eta - w[2 * i + 1]; - } - } - _ => panic!("Invalid eta value"), - } - - w -} - -/// A variant of Algorithm 19 BitUnpack specific to unpacking the t0 polynomial with a=2^{d-1}-1, b=2^{d-1} -/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). -/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. -pub(crate) fn bit_unpack_t0(a: &[u8; POLY_T0PACKED_LEN]) -> Polynomial { - let mut t0 = Polynomial::new(); - - for i in 0..N / 8 { - t0[8 * i] = ((a[13 * i] as i32) | ((a[13 * i + 1] as i32) << 8)) & 0x1FFF; - t0[8 * i + 1] = ((((a[13 * i + 1] as i32) >> 5) | (a[13 * i + 2] as i32) << 3) - | ((a[13 * i + 3] as i32) << 11)) - & 0x1FFF; - t0[8 * i + 2] = (((a[13 * i + 3] as i32) >> 2) | ((a[13 * i + 4] as i32) << 6)) & 0x1FFF; - t0[8 * i + 3] = ((((a[13 * i + 4] as i32) >> 7) | (a[13 * i + 5] as i32) << 1) - | ((a[13 * i + 6] as i32) << 9)) - & 0x1FFF; - t0[8 * i + 4] = ((((a[13 * i + 6] as i32) >> 4) | (a[13 * i + 7] as i32) << 4) - | ((a[13 * i + 8] as i32) << 12)) - & 0x1FFF; - t0[8 * i + 5] = (((a[13 * i + 8] as i32) >> 1) | ((a[13 * i + 9] as i32) << 7)) & 0x1FFF; - t0[8 * i + 6] = ((((a[13 * i + 9] as i32) >> 6) | (a[13 * i + 10] as i32) << 2) - | ((a[13 * i + 11] as i32) << 10)) - & 0x1FFF; - t0[8 * i + 7] = (((a[13 * i + 11] as i32) >> 3) | ((a[13 * i + 12] as i32) << 5)) & 0x1FFF; - - t0[8 * i] = (1 << (d - 1)) - t0[8 * i]; - t0[8 * i + 1] = (1 << (d - 1)) - t0[8 * i + 1]; - t0[8 * i + 2] = (1 << (d - 1)) - t0[8 * i + 2]; - t0[8 * i + 3] = (1 << (d - 1)) - t0[8 * i + 3]; - t0[8 * i + 4] = (1 << (d - 1)) - t0[8 * i + 4]; - t0[8 * i + 5] = (1 << (d - 1)) - t0[8 * i + 5]; - t0[8 * i + 6] = (1 << (d - 1)) - t0[8 * i + 6]; - t0[8 * i + 7] = (1 << (d - 1)) - t0[8 * i + 7]; - } - - t0 -} - -/// A variant of Algorithm 19 BitUnpack specific to a=𝛾1 − 1, b=𝛾1 -/// Input: 𝑎, 𝑏 ∈ ℕ and a byte string 𝑣 of length 32 ⋅ bitlen (𝑎 + 𝑏). -/// Output: A polynomial 𝑤 ∈ 𝑅 with coefficients in [𝑏 − 2𝑐 + 1, 𝑏], where 𝑐 = bitlen (𝑎 + 𝑏). -/// When 𝑎 + 𝑏 + 1 is a power of 2, the coefficients are in [−𝑎, 𝑏]. -/// -/// Note: caller is responsible for ensuring correct input array size -pub(crate) fn bit_unpack_gamma1(v: &[u8]) -> Polynomial { - let mut w = Polynomial::new(); - - match P::gamma1 { - // MLDSA-44 - GAMMA1_2_POW_17 => { - for i in 0..N / 4 { - w[4 * i] = (((v[9 * i] as i32) | ((v[9 * i + 1] as i32) << 8)) - | ((v[9 * i + 2] as i32) << 16)) - & 0x3FFFF; - w[4 * i + 1] = ((((v[9 * i + 2] as i32) >> 2) | ((v[9 * i + 3] as i32) << 6)) - | ((v[9 * i + 4] as i32) << 14)) - & 0x3FFFF; - w[4 * i + 2] = ((((v[9 * i + 4] as i32) >> 4) | ((v[9 * i + 5] as i32) << 4)) - | ((v[9 * i + 6] as i32) << 12)) - & 0x3FFFF; - w[4 * i + 3] = ((((v[9 * i + 6] as i32) >> 6) | ((v[9 * i + 7] as i32) << 2)) - | ((v[9 * i + 8] as i32) << 10)) - & 0x3FFFF; - - w[4 * i] = P::gamma1 - w[4 * i]; - w[4 * i + 1] = P::gamma1 - w[4 * i + 1]; - w[4 * i + 2] = P::gamma1 - w[4 * i + 2]; - w[4 * i + 3] = P::gamma1 - w[4 * i + 3]; - } - } - // MLDSA-65 and -87 have the same GAMMA1 value - GAMMA1_2_POW_19 => { - for i in 0..N / 2 { - w[2 * i] = (((v[5 * i] as i32) | ((v[5 * i + 1] as i32) << 8)) - | ((v[5 * i + 2] as i32) << 16)) - & 0xFFFFF; - w[2 * i + 1] = ((((v[5 * i + 2] as i32) >> 4) | ((v[5 * i + 3] as i32) << 4)) - | ((v[5 * i + 4] as i32) << 12)) - & 0xFFFFF; - - w[2 * i] = P::gamma1 - w[2 * i]; - w[2 * i + 1] = P::gamma1 - w[2 * i + 1]; - } - } - _ => { - panic!("Invalid gamma1 value") - } - }; - - w -} - /// Algorithm 26 sigEncode(̃𝑐_tilde, 𝐳, 𝐡) /// Encodes a signature into a byte string. /// Input: 𝑐_tilde ∈ 𝔹𝜆/4, 𝐳 ∈ 𝑅ℓ with coefficients in [−𝛾1 + 1, 𝛾1], 𝐡 ∈ 𝑅𝑘 @@ -403,20 +39,30 @@ pub(crate) fn sig_encode( // This inlines Algorithm 20 HintBitPack(𝐡) - let mut m: usize = 0; + // Algorithm 20 + // 2: Index ← 0 + let mut index: usize = 0; + // 3: for 𝑖 from 0 to 𝑘 − 1 do for i in 0..P::k { + // 4: for 𝑗 from 0 to 255 do for j in 0..N { + // 5-8: if 𝐡[𝑖]_𝑗 ≠ 0 then 𝑦[Index] ← 𝑗; Index ← Index + 1 if h.elems()[i][j] != 0 { - output[pos + m] = j as u8; - m += 1; + output[pos + index] = j as u8; + index += 1; } - output[pos + P::omega as usize + i] = m as u8; } + // 10: 𝑦[𝜔 + 𝑖] ← Index + output[pos + P::omega as usize + i] = index as u8; } SIG_LEN } +/// What [`sig_decode`] returns: (c_tilde, z, h). +pub(crate) type DecodedSig

= + (

::SigCTilde,

::VecL,

::VecK); + /// Algorithm 27 sigDecode(𝜎) /// Reverses the procedure sigEncode. /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. @@ -424,7 +70,7 @@ pub(crate) fn sig_encode( /// Output: (c_tilde, z, h) pub(crate) fn sig_decode( sig: &[u8; SIG_LEN], -) -> Result<(P::SigCTilde, P::VecL, P::VecK), ()> { +) -> Result, ()> { debug_assert_eq!(SIG_LEN, P::SIG_LEN); let mut c_tilde = ::ZEROED; let mut z = P::VecL::new(); @@ -488,164 +134,6 @@ pub(crate) fn sig_decode( Ok((c_tilde, z, h)) } -/// Algorithm 29 SampleInBall(𝜌) -/// Samples a polynomial 𝑐 ∈ 𝑅 with coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. -/// Input: A seed 𝜌 ∈ 𝔹𝜆/4 -/// Output: A polynomial 𝑐 in 𝑅. -pub(crate) fn sample_in_ball(rho: &P::SigCTilde) -> Polynomial { - // 1: 𝑐 ← 0 - let mut c = Polynomial::new(); - - // 2: ctx ← H.Init() - // 3: ctx ← H.Absorb(ctx, 𝜌) - // 4: (ctx, 𝑠) ← H.Squeeze(ctx, 8) - let mut h = H::new(); - h.do_update(rho.as_ref()); - let mut s = [0u8; 8]; - let mut h = h.into_squeezer(); - h.do_output_out(&mut s); - - // 5: ℎ ← BytesToBits(𝑠) - // ▷ ℎ is a bit string of length 64 - let mut signs: u64 = 0; - for (i, item) in s.iter().enumerate().take(8) { - signs |= (*item as u64) << (8 * i); - } - - // 6: for 𝑖 from 256 − 𝜏 to 255 do - - // let mut pos = 8; - // let mut b; - let mut j = [0u8]; - for i in (N - P::tau as usize)..N { - // 7: (ctx, 𝑗) ← H.Squeeze(ctx, 1) - // Note: Even though it may appear that pre-squeezing a buffer outside the loop would be faster, - // testing it both ways doesn't make a noticeable difference, so this has been left as is - // for better correspondence with the FIPS sample algorithm. - h.do_output_out(&mut j); - - // 8: while 𝑗 > 𝑖 do - while j[0] as usize > i { - // ▷ rejection sampling in {0, … , 𝑖} - // 9: (ctx, 𝑗) ← H.Squeeze(ctx, 1) - h.do_output_out(&mut j); - } - - // 11: 𝑐𝑖 ← 𝑐𝑗 - c[i] = c[j[0] as usize]; - - // 12: 𝑐𝑗 ← (−1)^ℎ[𝑖+𝜏−256] - c[j[0] as usize] = (1u64.wrapping_sub(2 * (signs & 1))) as i32; - signs >>= 1; - } - - // check post-condition - // coefficients from {−1, 0, 1} and Hamming weight 𝜏 ≤ 64. - #[cfg(debug_assertions)] - { - let mut hamming_weight: i32 = 0; - for i in 0..N { - debug_assert!((-1..=1).contains(&c[i])); - if c[i] != 0 { - hamming_weight += 1; - } - } - debug_assert!(hamming_weight > 0); - debug_assert!(hamming_weight <= 64); - } - - c -} - -/// Algorithm 30 RejNTTPoly(𝜌) -/// This is supposed to take a rho: [u8; 34], which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) -/// but to avoid needing to copy bytes and allocate more memory, -/// that is split into a [u8;32] and a [u8;2] -pub(crate) fn rej_ntt_poly(rho: &[u8; 32], nonce: &[u8; 2]) -> Polynomial { - let mut w_hat = Polynomial::new(); - let mut j: usize = 0; - let mut g = G::new(); - g.do_update(rho); - g.do_update(nonce); - - // SHAKE is fairly inefficient if only 3 bytes are squeezed at a time, so instead this implementation does a block. - // Size is not a limitation, so long as it's a multiple of 3. - // 288 seemed to be the sweet spot found during experimentation and benchmarking. - // It's probably around the average rejection rate, and 288 is a multiple of both 3 (required for this alg) - // and 8 (efficient for SHAKE). - let mut s = [0u8; 288]; - let mut g = g.into_squeezer(); - g.do_output_out(&mut s); - let mut idx: usize = 0; - - while j < N { - if idx == s.len() { - g.do_output_out(&mut s); - idx = 0; - } - w_hat[j] = match coeff_from_three_bytes(&s[idx..idx + 3].try_into().unwrap()) { - Ok(c) => c, - Err(_) => { - // those three bytes were out of range for a coefficient, so go again with the next three bytes - // from the SHAKE stream. - idx += 3; - continue; - } - }; - idx += 3; - j += 1; - } - - w_hat -} - -/// Algorithm 31 RejBoundedPoly(𝜌) -/// Samples an element 𝑎 ∈ 𝑅 with coefficients in \[−𝜂, 𝜂\] computed via rejection sampling from 𝜌. -/// Input: A seed 𝜌 ∈ 𝔹66 . -/// Output: A polynomial 𝑎 ∈ 𝑅. -/// -/// This is supposed to take a rho: [u8; 66], which is: 𝜌||IntegerToBytes(𝑠, 1)||IntegerToBytes(𝑟, 1) -/// but to avoid needing to copy bytes and allocate more memory, -/// that is split into a [u8;64] and a [u8;2] -pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2]) -> Polynomial { - let mut a = Polynomial::new(); - let mut j: usize = 0; - let mut h = H::new(); - h.do_update(rho); - h.do_update(nonce); - - // size doesn't really matter - // 312 seemed to be the sweet spot from playing with benchmarks - // maybe something to do with the average rejection rate? - // Also, 312 is a multiple of 8 (efficient for SHAKE) - let mut z_arr = [0u8; 312]; - let mut h = h.into_squeezer(); - h.do_output_out(&mut z_arr); - let mut idx: usize = 0; - - while j < N { - let z0 = coeff_from_half_byte::

(z_arr[idx] & 0x0F); // equiv to % 16 (but faster, and more importantly, constant-time) - let z1 = coeff_from_half_byte::

(z_arr[idx] >> 4); // equiv to div_floor(16) (but faster, and more importantly, constant-time) - - if z0.is_ok() { - a[j] = z0.unwrap(); - j += 1; - } /* else: do nothing */ - if z1.is_ok() && j < 256 { - a[j] = z1.unwrap(); - j += 1; - } /* else: do nothing */ - - idx += 1; - if idx == z_arr.len() { - h.do_output_out(&mut z_arr); - idx = 0; - } - } - - a -} - /// Algorithm 32 ExpandA(𝜌) /// Samples a 𝑘 × ℓ matrix 𝐀̂ of elements of 𝑇𝑞. /// in other words: derives the public matrix from the public seed. @@ -706,153 +194,15 @@ pub(crate) fn power_2_round_vec(v: &V) -> (V, V) { pub(crate) fn expand_mask(rho: &[u8; 64], mu: u16) -> P::VecL { let mut y = P::VecL::new(); - // 1: 𝑐 ← 1 + bitlen (𝛾1 − 1) - // ▷ 𝛾1 is always a power of 2 - // The 32𝑐 bytes squeezed on line 4 are exactly `P::POLY_Z_PACKED_LEN`, so the buffer for them - // is `P::PolyZPacked`; see the docs on [`MLDSAParams::POLY_Z_PACKED_LEN`]. - + // 2: for 𝑟 from 0 to ℓ − 1 do + // 3-5: lines 3 to 5 are one coordinate, see `expand_mask_poly`. for r in 0..P::l { - // 3: 𝜌′ ← 𝜌||IntegerToBytes(𝜇 + 𝑟, 2) - // 4: 𝑣 ← H(𝜌′, 32𝑐) - let v = { - let mut h = H::new(); - h.do_update(rho); - h.do_update(&(mu + (r as u16)).to_le_bytes()); - let mut v = ::ZEROED; - let mut h = h.into_squeezer(); - h.do_output_out(v.as_mut()); - v - }; - - // 5: 𝐲[𝑟] ← BitUnpack(𝑣, 𝛾1 − 1, 𝛾1) - y.elems_mut()[r] = bit_unpack_gamma1::

(v.as_ref()); + y.elems_mut()[r] = expand_mask_poly::

(rho, mu + (r as u16)); } y } -/// Algorithm 35 Power2Round(𝑟) -/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integers (𝑟1, 𝑟0). -pub(crate) fn power_2_round(r: i32) -> (i32, i32) { - const u: i32 = (1 << (d - 1)) - 1; - const v: i32 = -1 << d; - - let t = r + u; - let r0 = r - (t & v); - - (t >> d, r0) -} - -#[test] -// FIPS 204 describes the output as easy to check: -// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1 2^𝑑 + 𝑟0 mod 𝑞. -fn test_power_2_round() { - test(1); - test(q - 3); - test(q); - test(q + 3); - - fn test(r: i32) { - let (r1, r0) = power_2_round(r); - let mut res = ((r1 << d) + r0) % q; - if res < 0 { - res += q; - } - assert_eq!(r % q, res); - } -} - -/// Algorithm 36 Decompose(𝑟) -/// Decomposes 𝑟 into (𝑟1, 𝑟0) such that 𝑟 ≡ 𝑟1(2𝛾2) + 𝑟0 mod 𝑞. -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integers (𝑟1, 𝑟0). -pub(crate) fn decompose(r: i32) -> (i32, i32) { - // 1: 𝑟+ ← 𝑟 mod 𝑞 - // 2: 𝑟0 ← 𝑟+ mod±(2𝛾2) - // 3: if 𝑟+ − 𝑟0 = 𝑞 − 1 then - // 4: 𝑟1 ← 0 - // 5: 𝑟0 ← 𝑟0 − 1 - // 6: else 𝑟1 ← (𝑟+ − 𝑟0)/(2𝛾2) - // 7: end if - // 8: return (𝑟1, 𝑟0) - - // By the powers of someone much more clever than me, this is equivalent. - - let mut r1: i32; - let mut r0 = (r + 127) >> 7; - - match P::gamma2 { - // MLDSA-44 - GAMMA2_Q_MINUS_1_OVER_88 => { - // (q - 1) / 88 - r0 = (r0 * 11275 + (1 << 23)) >> 24; - r0 ^= ((43 - r0) >> 31) & r0; - } - // ML-DSA-65 and -87 have the same GAMMA2 - GAMMA2_Q_MINUS_1_OVER_32 => { - // (q - 1) / 32; - r0 = (r0 * 1025 + (1 << 21)) >> 22; - r0 &= 15; - } - _ => { - // this branch should never compile - unimplemented!() - } - } - - r1 = r - r0 * 2 * P::gamma2; - - // mutants note: the choice of (q - 1) is a bit arbitrary in that after doing the bit-shifting, - // this seems to work out mathematically equivalent if doing q/2, or (q+3)/2, but here it is left as (q-1)/2 - // since that's algorithmically correct, and just ignore the mutants results. - r1 -= (((q - 1) / 2 - r1) >> 31) & q; - - (r0, r1) -} - -/// Algorithm 37 HighBits(𝑟) -/// Returns 𝑟1 from the output of Decompose (𝑟). -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integer 𝑟1. -pub(crate) fn high_bits(r: i32) -> i32 { - // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) - // 2: return 𝑟1 - let (r1, _) = decompose::

(r); - r1 -} - -/// Algorithm 38 LowBits(𝑟) -/// Returns 𝑟0 from the output of Decompose (𝑟). -/// Input: 𝑟 ∈ ℤ𝑞. -/// Output: Integer 𝑟0. -pub(crate) fn low_bits(r: i32) -> i32 { - // 1: (𝑟1, 𝑟0) ← Decompose(𝑟) - // 2: return 𝑟0 - let (_, r0) = decompose::

(r); - r0 -} - -/// Algorithm 39 MakeHint(𝑧, 𝑟) -/// Computes hint bit indicating whether adding 𝑧 to 𝑟 alters the high bits of 𝑟. -/// Input: 𝑧, 𝑟 ∈ ℤ𝑞. -/// Output: Boolean. -pub(crate) fn make_hint(z: i32, r: i32) -> i32 { - // // 1: 𝑟1 ← HighBits(𝑟) - // let r1 = high_bits::

(r); - // - // // 2: 𝑣1 ← HighBits(𝑟 + 𝑧) - // let v1 = high_bits::

(r + z); - // - // // 3: return [[𝑟1 ≠ 𝑣1]] - // if r1 != v1 { 1 } else { 0 } - - // By the powers of someone much more clever than me, this is equivalent. - // mutants note: we do not have KATs that exercise all branches of this if - if z <= P::gamma2 || z > q - P::gamma2 || (z == q - P::gamma2 && r == 0) { 0 } else { 1 } -} - /// Creates the hint vector from two Vector's, and also returns its hamming weight (ie the number of 1's). pub(crate) fn make_hint_vecs(r: &P::VecK, s: &P::VecK) -> (P::VecK, i32) { let mut out = P::VecK::new(); @@ -869,186 +219,12 @@ pub(crate) fn make_hint_vecs(r: &P::VecK, s: &P::VecK) -> (P::Ve (out, count) } -/// Algorithm 40 UseHint(ℎ, 𝑟) -/// Returns the high bits of 𝑟 adjusted according to hint ℎ. -/// Input: Boolean ℎ, 𝑟 ∈ ℤ𝑞. -/// Output: 𝑟1 ∈ ℤ with 0 ≤ 𝑟1 ≤ (𝑞−1) / 2*gamma2). -pub(super) fn use_hint(a: i32, hint: i32) -> i32 { - let (a0, a1) = decompose::

(a); - - if hint == 0 { - return a0; - } - - debug_assert!(hint == 1); - - match P::gamma2 { - GAMMA2_Q_MINUS_1_OVER_88 => { - // mutants note: this passes unit tests if it's a1 >= 0 - // it is left like this because it matches the spec - if a1 > 0 { - if a0 == 43 { 0 } else { a0 + 1 } - } else { - if a0 == 0 { 43 } else { a0 - 1 } - } - } - // ML-DSA65 and 87 have the same GAMMA2 - GAMMA2_Q_MINUS_1_OVER_32 => { - // mutants note: this passes unit tests if it's a1 >= 0 - // it is left like this because it matches the spec - if a1 > 0 { (a0 + 1) & 15 } else { (a0 - 1) & 15 } - } - _ => { - panic!("Invalid GAMMA2 value") - } - } -} - -pub(crate) fn use_hint_polys( - wp_approx: &Polynomial, - h: &Polynomial, - out: &mut Polynomial, -) { - for i in 0..N { - out[i] = use_hint::

(wp_approx[i], h[i]); - } -} - +/// Algorithm 40 UseHint(ℎ, 𝑟) applied to every coefficient of every coordinate. pub(crate) fn use_hint_vecs(h: &P::VecK, wp_approx: &P::VecK) -> P::VecK { - let mut out = P::VecK::new(); + let mut out = *wp_approx; for i in 0..P::k { - use_hint_polys::

(&wp_approx.elems()[i], &h.elems()[i], &mut out.elems_mut()[i]); - } - - out -} - -/// Algorithm 45 MultiplyNTT(𝑎, 𝑏)̂ -/// Computes the product 𝑎 ∘̂ 𝑏 of two elements 𝑎, 𝑏 ∈ 𝑇𝑞. -/// Input: 𝑎, 𝑏 ∈ 𝑇𝑞. -/// Output: 𝑐 ∈ 𝑇𝑞. -/// Multiply the coefficients in this polynomial by those in another polynomial and perform montgomery reduction. -/// Also called pointwise Montgomery multiplication -pub(crate) fn multiply_ntt(a: &Polynomial, b: &Polynomial) -> Polynomial { - let mut out = Polynomial::new(); - for i in 0..N { - out[i] = montgomery_reduce((a[i] as i64) * (b[i] as i64)); + out.elems_mut()[i].use_hint::

(&h.elems()[i]); } out } - -/// FIPS 204 Algorithm 49 -/// As described in FIPS 204 Appendix A, montgomery reduction allows for efficient computation -/// of expressions of the form c = a * b (mod q). -/// The output is not necessarily less than q in absolute value, but it is less than 2q in absolute value -pub(crate) fn montgomery_reduce(a: i64) -> i32 { - debug_assert!(a > -((q as i64) << 31) && a < ((q as i64) << 31)); - - // 2: 𝑡 ← ((𝑎 mod 2^32) ⋅ QINV) mod 2^32 - let t: i32 = (a as i32).wrapping_mul(q_inv); - - // 3: 𝑟 ← (𝑎 − 𝑡 ⋅ 𝑞)/2^32 - ((a - ((t as i64) * (q as i64))) >> 32) as i32 -} - -pub(crate) fn conditional_add_q(a: i32) -> i32 { - a + ((a >> 31) & q) -} - -/// Plain (non-Montgomery) reduction: for 𝑎 ≤ 2^31 − 2^22 − 1 returns 𝑟 ≡ 𝑎 (mod 𝑞) with |𝑟| ≤ 6283008 < 𝑞. -/// `reduce32` in the reference implementation (pq-crystals/dilithium, ref/reduce.c). -/// -/// This is not in FIPS 204 since it assumes all arithmetic is done mod q. -/// In this implementation, sums of Montgomery products are left unreduced, but `inv_ntt` (Algorithm 42) -/// needs |input| < 𝑞 to stay within an `i32`, so this is applied to those sums first. Omitting it before -/// the verifier's NTT⁻¹ is exploitable (, Wycheproof mldsa_87_verify tcId 240/241). -pub(crate) fn reduce32(a: i32) -> i32 { - // The reference implementation's stated input bound; above it `a + 2^22` overflows. - debug_assert!(a <= i32::MAX - (1 << 22)); - let t = (a + (1 << 22)) >> 23; - a - t * q -} - -/// Constants for NTT -pub(crate) const ZETAS: [i32; 256] = [ - 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251, - -2091905, 3119733, -2884855, 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488, - -1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, - 280005, 2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439, -3861115, - -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267, -1643818, 3505694, - -3821735, 3507263, -2140649, -1600420, 3699596, 811944, 531354, 954230, 3881043, 3900724, - -2556880, 2071892, -2797779, -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, - 2176455, -1585221, -1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922, - 3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047, -671102, -1228525, - -22981, -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944, 508951, 3097992, - 44288, -1100098, 904516, 3958618, -3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, - -1316856, 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330, 1285669, - -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961, 2091667, 3407706, 2316500, - 3817976, -3342478, 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181, -3520352, - -3759364, -1197226, -3193378, 900702, 1859098, 909542, 819034, 495491, -1613174, -43260, - -522500, -655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297, - 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044, 2842341, 2691481, -2590150, - 1265009, 4055324, 1247620, 2486353, 1595974, -3767016, 1250494, 2635921, -3548272, -2994039, - 1869119, 1903435, -1050970, -1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, - -1962642, -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031, -542412, - -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 2432395, 2454455, - -164721, 1957272, 3369112, 185531, -1207385, -3183426, 162844, 1616392, 3014001, 810149, - 1652634, -3694233, -1799107, -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, - 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, -2939036, - -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306, - -1362209, 3937738, 1400424, -846154, 1976782, -]; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - /// These are the results it's giving; I'm not sure if these are "correct" or not. - fn test_conditional_add_q() { - assert_eq!(conditional_add_q(-q - 1), -1); - assert_eq!(conditional_add_q(-q), 0); - assert_eq!(conditional_add_q(-q - 2), -2); - assert_eq!(conditional_add_q(-q + 1), 1); - assert_eq!(conditional_add_q(-1), q - 1); - assert_eq!(conditional_add_q(0), 0); - assert_eq!(conditional_add_q(1), 1); - assert_eq!(conditional_add_q(q - 1), q - 1); - assert_eq!(conditional_add_q(q), q); - assert_eq!(conditional_add_q(q + 1), q + 1); - } - - #[test] - fn test_reduce32() { - // congruent to the input mod q, and within the reference implementation's stated output range - for &a in &[ - 0, - 1, - -1, - q - 1, - q, - q + 1, - -q, - -q - 1, - 8 * q, - -8 * q, - 6283008, - -6283008, - i32::MIN, - i32::MAX - (1 << 22), - ] { - let r = reduce32(a); - assert!((-6283008..=6283008).contains(&r), "reduce32({a}) = {r} out of range"); - assert_eq!( - (r as i64 - a as i64).rem_euclid(q as i64), - 0, - "reduce32({a}) = {r} not congruent" - ); - assert!(r.abs() < q); - } - // the largest sum inv_ntt may see: (l + 1) products each in (-q, q), for l = 7 - assert!(reduce32(8 * q - 8).abs() < q); - assert!(reduce32(-(8 * q - 8)).abs() < q); - } -} diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index f7478c3d6..a2e38d8e5 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -30,7 +30,7 @@ //! use bouncycastle_core::errors::SignatureError; //! use bouncycastle_mldsa::{HashMLDSA65_with_SHA512, MLDSATrait, HashMLDSA44_with_SHA512}; //! use bouncycastle_core::traits::{ -//! Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, +//! HashOneShot, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, //! }; //! use bouncycastle_sha2::SHA512; //! @@ -38,7 +38,7 @@ //! //! // Here, and in contrast to External Mu mode of ML-DSA, the message can be pre-hashed before //! // even generating the signing key. -//! let ph: [u8; 64] = SHA512::default().hash(msg).as_slice().try_into().unwrap(); +//! let ph: [u8; 64] = SHA512::hash(msg).as_slice().try_into().unwrap(); //! //! //! let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); @@ -65,15 +65,14 @@ //! so if more sophisticated keygen functions are needed, just use them from [`MLDSA`]. //! But a simple [`HashMLDSA::keygen`] is provided. -use crate::mldsa::{H, MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSATrait}; +use crate::mldsa::{MLDSA_RND_LEN, MLDSATrait, MuBuilder}; use crate::mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; use crate::mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; use crate::mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPublicKeyInternalTrait}; use crate::params::{ - HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA256Params, - HashMLDSA65_with_SHA512Params, HashMLDSA87_with_SHA256Params, HashMLDSA87_with_SHA512Params, - HashMLDSAParams, MLDSAParams, + HashMLDSA44_with_SHA256Params, HashMLDSA44_with_SHA512Params, HashMLDSA65_with_SHA512Params, + HashMLDSA87_with_SHA512Params, HashMLDSAParams, MLDSAParams, }; use crate::{ MLDSA, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, @@ -84,34 +83,28 @@ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, PHSignatureVerifier, PHSigner, RNG, SignatureVerifier, Signer, - XOF, XOFSqueezer, + Algorithm, AlgorithmOID, Hash, HashOneShot, PHSignatureVerifier, PHSigner, RNG, + SignatureVerifier, Signer, }; use bouncycastle_rng::HashDRBG_SHA512; use core::marker::PhantomData; -// Imports needed only for docs -#[allow(unused_imports)] -use crate::mldsa::MuBuilder; - /*** Constants ***/ -/// -pub const HASH_ML_DSA_44_with_SHA256_NAME: &str = "HashML-DSA-44_with_SHA256"; -/// -pub const HASH_ML_DSA_65_WITH_SHA256_NAME: &str = "HashML-DSA-65_with_SHA256"; -/// -pub const HASH_ML_DSA_87_with_SHA256_NAME: &str = "HashML-DSA-87_with_SHA256"; -/// -pub const HASH_ML_DSA_44_with_SHA512_NAME: &str = "HashML-DSA-44_with_SHA512"; -/// -pub const HASH_ML_DSA_65_WITH_SHA512_NAME: &str = "HashML-DSA-65_with_SHA512"; -/// -pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{ + HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, + HASH_ML_DSA_65_with_SHA512_NAME, HASH_ML_DSA_87_with_SHA512_NAME, +}; /*** Pub Types ***/ /// The HashML-DSA-44_with_SHA256 signature algorithm. +/// +/// The only SHA-256 pairing. FIPS 204 Section 5.4 requires the pre-hash to provide "at least +/// lambda bits of classical security strength against both collision and second preimage +/// attacks", which footnote 6 puts at a digest of "at least 2 lambda bits"; SHA-256 meets that for +/// ML-DSA-44 (lambda = 128) and not for ML-DSA-65 or -87, which pair only with SHA-512 here. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA256 = HashMLDSA< HashMLDSA44_with_SHA256Params, @@ -123,30 +116,6 @@ pub type HashMLDSA44_with_SHA256 = HashMLDSA< MLDSA44_SIG_LEN, >; -/// The HashML-DSA-65_with_SHA256 signature algorithm. -#[allow(non_camel_case_types)] -pub type HashMLDSA65_with_SHA256 = HashMLDSA< - HashMLDSA65_with_SHA256Params, - MLDSA65PublicKey, - MLDSA65PrivateKey, - { HashMLDSA65_with_SHA256Params::PH_LEN }, - MLDSA65_PK_LEN, - MLDSA65_SK_LEN, - MLDSA65_SIG_LEN, ->; - -/// The HashML-DSA-87_with_SHA256 signature algorithm. -#[allow(non_camel_case_types)] -pub type HashMLDSA87_with_SHA256 = HashMLDSA< - HashMLDSA87_with_SHA256Params, - MLDSA87PublicKey, - MLDSA87PrivateKey, - { HashMLDSA87_with_SHA256Params::PH_LEN }, - MLDSA87_PK_LEN, - MLDSA87_SK_LEN, - MLDSA87_SIG_LEN, ->; - /// The HashML-DSA-44_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] pub type HashMLDSA44_with_SHA512 = HashMLDSA< @@ -300,7 +269,7 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output) } /// Same as [`PHSigner::sign_ph`], but signs from an [`MLDSAPrivateKeyExpanded`]. @@ -310,8 +279,7 @@ impl< ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { let mut out = [0u8; SIG_LEN]; - _ = Self::sign_ph_with_expanded_key_out(sk, ph, ctx, &mut out); - + Self::sign_ph_with_expanded_key_out(sk, ph, ctx, &mut out)?; Ok(out) } /// Same as [`PHSigner::sign_ph_out`], but signs from an [`MLDSAPrivateKeyExpanded`]. @@ -331,12 +299,10 @@ impl< /// (modified to take an externally-computed ph instead of M', thus combining Algorithm 4 with Algorithm 7). /// /// Security note: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode. However, the user must be aware - /// that is their responsibility to ensure that their nonce `rnd` is unique per signature. - /// If otherwise, some privacy properties may be lost; for example it becomes easy to tell if a signer - /// has signed the same message twice or two different messages, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// `rnd` comes from the caller. FIPS 204 Section 3.4 permits the deterministic variant, `rnd` + /// all zeros, and the hedged variant draws it fresh. Deterministic signing does not leak the + /// key, but a repeated `rnd` loses some privacy: it becomes easy to tell whether a signer has + /// signed the same message twice, or whether two signatures on one message share a signer. /// /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help /// prevent accidental nonce reuse, this function moves `rnd`. @@ -354,13 +320,7 @@ impl< /// Algorithm 7 ML-DSA.Sign_internal(𝑠𝑘, 𝑀′, 𝑟𝑛𝑑) /// (modified to take an externally-computed ph instead of M', thus combining Algorithm 4 with Algorithm 7). /// - /// Performs an ML-DSA signature using the provided external message representative `mu`. - /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both - /// FIPS 204 itself, as well as subsequent FAQ documents. - /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG. - /// - /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help - /// prevent accidental nonce reuse, this function moves `rnd`. + /// As [`Self::sign_ph_deterministic`], writing into `output`; see its security note. /// /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. pub fn sign_ph_deterministic_out( @@ -371,37 +331,10 @@ impl< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result { - let ctx = if ctx.is_some() { ctx.unwrap() } else { &[] }; - - // Algorithm 4 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - output.fill(0); - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mu = { - let mut h = H::new(); - h.do_update(sk.tr()); - - // Algorithm 4 - // 23: 𝑀' ← BytesToBits(IntegerToBytes(1, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ OID ∥ PH𝑀) - // all done together - h.do_update(&[1u8]); - h.do_update(&[ctx.len() as u8]); - h.do_update(ctx); - h.do_update(::OID_DER); - h.do_update(ph); - let mut mu = [0u8; MLDSA_MU_LEN]; - let mut h = h.into_squeezer(); - let bytes_written = h.do_output_out(&mut mu); - debug_assert_eq!(bytes_written, MLDSA_MU_LEN); - - mu - }; + // Algorithm 4, lines 1 and 23, and Algorithm 7, line 6 + let mu = MuBuilder::compute_mu_ph(sk.tr(), ctx, ::OID_DER, ph)?; // 24: 𝜎 ← ML-DSA.Sign_internal(𝑠𝑘, 𝑀', 𝑟𝑛𝑑) let bytes_written = @@ -420,19 +353,15 @@ impl< } fn parse_ctx(ctx: Option<&[u8]>) -> Result<([u8; 255], usize), SignatureError> { - if ctx.is_some() { - // Algorithm 2 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.unwrap().len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - let mut ctx_buf = [0u8; 255]; - ctx_buf[..ctx.unwrap().len()].copy_from_slice(ctx.unwrap()); - Ok((ctx_buf, ctx.unwrap().len())) - } else { - Ok(([0u8; 255], 0)) + let ctx = ctx.unwrap_or(&[]); + // Algorithm 2 + // 1: if |𝑐𝑡𝑥| > 255 then + if ctx.len() > 255 { + return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); } + let mut ctx_buf = [0u8; 255]; + ctx_buf[..ctx.len()].copy_from_slice(ctx); + Ok((ctx_buf, ctx.len())) } /// Alternative initialization of the streaming signer where the user provides their private key @@ -461,7 +390,7 @@ impl< sig: &[u8], ) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::verify_ph_internal(&pk.pk, Some(&pk.A_hat()), &ph_m, ctx, sig) } @@ -478,34 +407,13 @@ impl< } let sig_sized: &[u8; SIG_LEN] = sig[..SIG_LEN].try_into().unwrap(); - let ctx = if ctx.is_some() { ctx.unwrap() } else { &[] }; - - // Algorithm 5 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mu = { - let mut h = H::new(); - h.do_update(&pk.compute_tr()); - - // Algorithm 4 - // 23: 𝑀 ← BytesToBits(IntegerToBytes(1, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ OID ∥ PH𝑀) - // all done together - h.do_update(&[1u8]); - h.do_update(&[ctx.len() as u8]); - h.do_update(ctx); - h.do_update(::OID_DER); - h.do_update(ph); - let mut mu = [0u8; MLDSA_MU_LEN]; - let mut h = h.into_squeezer(); - _ = h.do_output_out(&mut mu); - - mu - }; + // Algorithm 5, line 1, Algorithm 4, line 23, and Algorithm 7, line 6 + let mu = MuBuilder::compute_mu_ph( + &pk.compute_tr(), + ctx, + ::OID_DER, + ph, + )?; match A_hat { Some(A_hat) => MLDSA::::verify_mu( @@ -553,7 +461,7 @@ impl< output.fill(0); let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::sign_ph_out(sk, &ph_m, ctx, output) } @@ -582,52 +490,33 @@ impl< } fn do_sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result { + output.fill(0); let ph: [u8; PH_LEN] = self.hash.do_final().try_into().unwrap(); + let ctx = Some(&self.ctx[..self.ctx_len]); - if self.sk.is_none() && self.seed.is_none() { + if let Some(sk) = self.sk { + return match self.signer_rnd { + None => Self::sign_ph_out(&sk, &ph, ctx, output), + Some(rnd) => Self::sign_ph_deterministic_out(&sk, None, ctx, &ph, rnd, output), + }; + } + let Some(seed) = self.seed else { return Err(SignatureError::GenericError( "do_sign_final_out called on a streaming context with no private key or seed; \ this is a verify-initialized context. Call do_verify_final instead", )); - } - - output.fill(0); - - if self.sk.is_some() { - if self.signer_rnd.is_none() { - Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output) - } else { - Self::sign_ph_deterministic_out( - &self.sk.unwrap(), - None, - Some(&self.ctx[..self.ctx_len]), - &ph, - self.signer_rnd.unwrap(), - output, - ) - } - } else if self.seed.is_some() { - let rnd = if self.signer_rnd.is_some() { - self.signer_rnd.unwrap() - } else { + }; + let rnd = match self.signer_rnd { + Some(rnd) => rnd, + None => { let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; rnd - }; - // At this point it is not necessary to fully reconstruct SK in order to compute tr for mu. - // Therefore, there is no advantage in using MLDSA::sign_from_seed - let (_pk, sk) = Self::keygen_from_seed(&self.seed.unwrap())?; - Self::sign_ph_deterministic_out( - &sk, - None, - Some(&self.ctx[..self.ctx_len]), - &ph, - rnd, - output, - ) - } else { - unreachable!() - } + } + }; + // The seed is expanded here, not at init: the pre-hash does not need tr until now. + let (_pk, sk) = Self::keygen_from_seed(&seed)?; + Self::sign_ph_deterministic_out(&sk, None, ctx, &ph, rnd, output) } } @@ -644,7 +533,7 @@ impl< { fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError> { let mut ph_m = [0u8; PH_LEN]; - _ = ::default().hash_out(msg, &mut ph_m); + _ = P::PreHash::hash_out(msg, &mut ph_m); Self::verify_ph(pk, &ph_m, ctx, sig) } @@ -668,12 +557,12 @@ impl< } fn do_verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { - assert!( - self.pk.is_some(), - "Somehow you managed to construct a streaming verifier without a public key, impressive!" - ); + // A context from do_sign_init has no public key; the same error MLDSA gives. + let pk = self + .pk + .ok_or(SignatureError::GenericError("No public key set on streaming verifier."))?; let ph: [u8; PH_LEN] = self.hash.do_final().try_into().unwrap(); - Self::verify_ph(&self.pk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), sig) + Self::verify_ph(&pk, &ph, Some(&self.ctx[..self.ctx_len]), sig) } } diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 6bf98ec9a..278356f27 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -1,6 +1,6 @@ //! This crate implements the Module Lattice Digital Signature Algorithm (ML-DSA) as per FIPS 204. //! -//! # Usage +//! # Usage Examples //! //! This crate has been designed to serve a wide range of use cases, from people dabbling in //! cryptography for the first time, to cryptographic protocol designers who need access to the internal and advanced @@ -36,9 +36,9 @@ //! let (pk, sk) = MLDSA65::keygen_from_seed(&seed).unwrap(); //! ``` //! -//! See [`MLDSATrait`] and [`MLDSATrait::sign_mu_deterministic_from_seed`] for an API flow that uses a merged -//! keygen-and-sign function to provide improved speed and memory performance compared with making -//! separate calls to [`MLDSATrait::keygen_from_seed`] followed by [`Signer::sign`]. +//! [`MLDSATrait::sign_mu_deterministic_from_seed`] and [`MLDSATrait::sign_init_from_seed`] sign +//! straight from a seed; each expands the key with [`MLDSATrait::keygen_from_seed`] first, so a +//! caller that signs more than once should keep the expanded private key. //! //! ## Generating and Verifying Signatures //! @@ -64,16 +64,16 @@ //! And that's the basic usage! There are lots more bells-and-whistles in the form of exposed algorithm //! parameters, streaming APIs and other goodies that you can find by poking around this documentation. //! -//! # Memory Footprint +//! # Memory Usage //! //! The following table lists the size of the on-disk bytes encoding and the in-memory struct size of the //! standard key objects: //! //! | Key Object | PK size on disk | PK size in memory | SK Size on disk | SK size in memory | //! |------------|-----------------|-------------------|-----------------|-------------------| -//! | ML-DSA-44 | 1312 | 1312 (4128) | 2560 | 12464 | -//! | ML-DSA-65 | 1952 | 1952 (6176) | 4032 | 17584 | -//! | ML-DSA-87 | 2592 | 2592 (8224) | 4896 | 23728 | +//! | ML-DSA-44 | 1312 | 4128 | 2560 | 12464 | +//! | ML-DSA-65 | 1952 | 6176 | 4032 | 17584 | +//! | ML-DSA-87 | 2592 | 8224 | 4896 | 23728 | //! //! The following table lists the size of the on-disk bytes encoding and the in-memory struct size of the //! expanded key objects that pre-expand the public matrix A for faster repeated verify() operations: @@ -85,8 +85,6 @@ //! | ML-DSA-87_expanded | 2592 | 65568 | 4896 | 81072 | //! //! All values are in bytes. The "in memory" sizes are measured by rust's `std::mem::size_of`. -//! Values in parentheses are the usual sizes in our un-optimized implementation in the \[bouncycastle_mldsa] crate. -//! //! //! # 🚨 Security Considerations 🚨 //! @@ -99,11 +97,10 @@ //! ML-DSA and HashML-DSA take several parameters: `seed`, `mu`, `ph`, `ctx`, and `rnd`. //! They fall into two groups with very different failure modes. //! -//! -//! `seed` and `rnd`, however, are secret/entropy inputs and must be handled with care: +//! `seed` and `rnd` are secret or entropy inputs and must be handled with care: //! //! - `seed` *is* the private key, i.e. the entire key is derived from it. It must be generated -//! with a strong cryptographically secure PRNG, it must be kept secret, and it must never reused. +//! with a strong cryptographically secure PRNG, it must be kept secret, and it must never be reused. //! A low-entropy, predictable, or disclosed seed yields a full key compromise, //! not merely an unverifiable signature. //! @@ -119,6 +116,14 @@ //! verifier won't accept (a correctness/interoperability failure). //! One caveat: `ctx` can still be security-relevant at the protocol level (domain separation, replay and //! cross-protocol binding), so choosing it incorrectly can weaken those properties. +//! +//! ## What is zeroized +//! +//! FIPS 204 Section 3.6.3 asks that "any potentially sensitive intermediate data is destroyed as +//! soon as it is no longer needed". Here the private key's `K`, `s1_hat` and `s2_hat` are `Secret` +//! and the seed is `KeyMaterial`, both wiped on drop, and the signing randomizer `rho''` is zeroized +//! by hand when signing finishes. The per-signature vectors `y`, `w`, `cs1`, `cs2`, `ct0` and `r0` +//! are plain stack values that are not wiped when their scope ends. #![no_std] #![forbid(unsafe_code)] @@ -149,10 +154,9 @@ mod matrix; pub mod mldsa; mod mldsa_keys; mod params; -mod polynomial; /*** Exported types ***/ -pub use hash_mldsa::{HashMLDSA44_with_SHA256, HashMLDSA65_with_SHA256, HashMLDSA87_with_SHA256}; +pub use hash_mldsa::HashMLDSA44_with_SHA256; pub use hash_mldsa::{HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA512}; pub use mldsa::MuBuilder; pub use mldsa::{MLDSA, MLDSA44, MLDSA65, MLDSA87, MLDSATrait}; @@ -181,9 +185,7 @@ pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; pub use mldsa::SUSPENDED_MU_BUILDER_STATE_LEN; pub use hash_mldsa::HASH_ML_DSA_44_with_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA256_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_with_SHA256_NAME; pub use hash_mldsa::HASH_ML_DSA_44_with_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_65_WITH_SHA512_NAME; -pub use hash_mldsa::HASH_ML_DSA_87_WITH_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_65_with_SHA512_NAME; +pub use hash_mldsa::HASH_ML_DSA_87_with_SHA512_NAME; diff --git a/crypto/mldsa/src/matrix.rs b/crypto/mldsa/src/matrix.rs index 31ac9856f..8f89caadf 100644 --- a/crypto/mldsa/src/matrix.rs +++ b/crypto/mldsa/src/matrix.rs @@ -1,11 +1,10 @@ -//! These are somewhat unnecessary wrappers around simple arrays, but they are helpful to me in clearly -//! keeping the types and sizes obvious. +//! Thin wrappers around fixed-size arrays of polynomials, so that the vector and matrix types and +//! their dimensions are explicit at every use. -use crate::aux_functions::multiply_ntt; use crate::mldsa::H; use crate::params::MLDSAParams; -use crate::polynomial::Polynomial; use bouncycastle_core::traits::Hash; +use bouncycastle_mldsa_common::polynomial::{Polynomial, multiply_ntt}; use bouncycastle_utils::secret::ZeroizablePrimitive; use core::ops::{Index, IndexMut}; @@ -223,7 +222,7 @@ impl VectorTrait for Vector { fn scalar_vector_ntt(&self, w: &Polynomial) -> Self { let mut s_hat = Vector::::new(); for i in 0..LEN { - s_hat[i] = multiply_ntt(&self[i], &w); + s_hat[i] = multiply_ntt(&self[i], w); } s_hat diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index be78cb936..06c104943 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -272,7 +272,7 @@ //! let sig = MLDSA65::sign_with_expanded_key(&sk_expanded, msg, None).unwrap(); //! //! // The pre-expand the public key uses more memory, but has performance -//! // improvements if doing multiple encapsulations for the same key. +//! // improvements if doing multiple verifications for the same key. //! let pk_expanded = MLDSA65PublicKeyExpanded::from(&pk); //! match MLDSA65::verify_with_expanded_key(&pk_expanded, msg, None, &sig) { //! Ok(()) => println!("Signature is valid!"), @@ -281,24 +281,14 @@ //! } //! ``` //! -//! # sign_from_seed +//! # Signing from a seed //! -//! This mode is intended for users with extreme performance or resource-limitation requirements. -//! -//! A very careful analysis of the ML-DSA signing algorithm will show that -//! the entire ML-DSA private key does not need to be in memory at the same time. -//! In fact, it is possible to merge the keygen() and sign() functions -//! -//! The codebase contains [`MLDSA::sign_mu_deterministic_from_seed`] which implements such an algorithm. -//! It has a significantly lower peak-memory-footprint than the regular signing API (although there's -//! always room for more optimization), and according to our benchmarks it is only around 25% slower -//! than signing with a fully-expanded private key -- which is still faster than performing a full -//! keygen followed by a regular sign since there are intermediate values common to keygen and sign -//! that the merged function is able to only compute once. -//! -//! Since this is intended for embedded systems specialists, the functions are not wrapped in -//! the beginner-friendly APIs. It is implied that a user that needs this functionality also knows how -//! to use it and what they are doing +//! [`MLDSATrait::sign_mu_deterministic_from_seed`] and [`MLDSATrait::sign_init_from_seed`] take +//! the 32-byte seed in place of a private key. Each expands the key with +//! [`MLDSATrait::keygen_from_seed`] and then signs with it; nothing is cached between calls, so +//! a caller that signs more than once should expand the key once and keep it. Signing from the +//! seed costs a key generation on top of the signature, about 40% more than signing with an +//! expanded key on the benches. //! //! Example usage: //! @@ -327,9 +317,7 @@ //! // Assume this was computed somewhere else, then //! // the party that computed it would have had to know pk //! let mu: [u8; 64] = MuBuilder::compute_mu(&tr, msg, None).unwrap(); -//! let rnd: [u8; 32] = [0u8; 32]; // with this API, the user is responsible for their own nonce -//! // because in the cases where this level of memory optimization -//! // is needed, our RNG probably won't work anyway. +//! let rnd: [u8; 32] = [0u8; 32]; // with this API, the caller supplies the nonce //! //! let mut sig = [0u8; MLDSA44_SIG_LEN]; //! let bytes_written = MLDSA44::sign_mu_deterministic_from_seed_out(&seed, &mu, rnd, &mut sig) @@ -343,12 +331,6 @@ //! } //! ``` //! -//! While this is currently only supported when operating from a seed-based private key, something analogous -//! could be done that merges the sk_decode() and sign() routines when working with the standardized -//! private key encoding (which is often called the "semi-expanded format" since the in-memory representation -//! is still larger). -//! Contact us if you need such a thing implemented. -//! //! //! # Pre-expanding the public key for repeated use //! @@ -476,25 +458,25 @@ //! ``` use crate::aux_functions::{ - expand_mask, expandA, expandS, make_hint_vecs, power_2_round_vec, sample_in_ball, sig_decode, - sig_encode, use_hint_vecs, + expand_mask, expandA, expandS, make_hint_vecs, power_2_round_vec, sig_decode, sig_encode, + use_hint_vecs, }; use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mldsa_keys::{MLDSAPrivateKeyInternalTrait, MLDSAPrivateKeyTrait}; use crate::mldsa_keys::{MLDSAPublicKeyInternalTrait, MLDSAPublicKeyTrait}; -use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams, MLDSAParamsBase}; use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; +use bouncycastle_core::errors::{RNGError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, RNG, SignatureVerifier, Signer, Suspendable, XOF, XOFSqueezer, + Algorithm, AlgorithmOID, Hash, RNG, SignatureVerifier, Signer, XOF, XOFSqueezer, }; +use bouncycastle_mldsa_common::aux_functions::sample_in_ball; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::marker::PhantomData; @@ -506,30 +488,13 @@ use bouncycastle_core::traits::{PHSignatureVerifier, PHSigner}; /*** Constants ***/ -/// -pub const ML_DSA_44_NAME: &str = "ML-DSA-44"; -/// -pub const ML_DSA_65_NAME: &str = "ML-DSA-65"; -/// -pub const ML_DSA_87_NAME: &str = "ML-DSA-87"; - -/*** From FIPS 204 Table 1 and Table 2 ***/ - -// Constants that are the same for all parameter sets -pub(crate) const N: usize = 256; -pub(crate) const q: i32 = 8380417; -pub(crate) const q_inv: i32 = 58728449; // q ^ (-1) mod 2 ^32 -pub(crate) const d: i32 = 13; -/// Length of the \[u8] holding an ML-DSA seed value. -pub const MLDSA_SEED_LEN: usize = 32; -/// Length of the \[u8] holding an ML-DSA signing random value. -pub const MLDSA_RND_LEN: usize = 32; -/// Length of the \[u8] holding an ML-DSA tr value (which is the SHAKE256 hash of the public key). -pub const MLDSA_TR_LEN: usize = 64; -/// Length of the \[u8] holding an ML-DSA mu value. -pub const MLDSA_MU_LEN: usize = 64; -pub(crate) const POLY_T0PACKED_LEN: usize = 416; -pub(crate) const POLY_T1PACKED_LEN: usize = 320; +// The constants, names and XOF aliases FIPS 204 fixes for every parameter set are shared with +// `bouncycastle-mldsa-lowmemory` and live in `bouncycastle-mldsa-common`. +pub(crate) use bouncycastle_mldsa_common::{H, N, POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; +#[doc(inline)] +pub use bouncycastle_mldsa_common::{MLDSA_MU_LEN, MLDSA_RND_LEN, MLDSA_SEED_LEN, MLDSA_TR_LEN}; /*** Re-exporting length constants that a caller will need instead of the entire Params objects which contains a bunch of internal algorithm detail ***/ @@ -554,10 +519,6 @@ pub const MLDSA87_SK_LEN: usize = MLDSA87Params::SK_LEN; /// Length of the \[u8] holding an ML-DSA-87 signature value. pub const MLDSA87_SIG_LEN: usize = MLDSA87Params::SIG_LEN; -// Typedefs just to make the algorithms look more like the FIPS 204 sample code. -pub(crate) type H = SHAKE256; -pub(crate) type G = SHAKE128; - /*** Pub Types ***/ /// The ML-DSA-44 algorithm. @@ -643,9 +604,6 @@ pub struct MLDSA< /// only used in streaming sign operations sk: Option, - /// only used in streaming sign operations instead of sk - seed: Option>, - /// only used in streaming verify operations pk: Option, } @@ -779,7 +737,7 @@ impl< // 5: 𝐀_hat ← ExpandA(𝜌) // It does an optimization where the user can pre-expand A_hat within the - // public key object for faster repeated encapsulations against this public key. + // public key object for faster repeated verifications against this public key. // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀 ′, 64) // skip: mu has already been provided @@ -829,7 +787,7 @@ impl< let w = { // scope for y_hat // 12: 𝐰 ← NTT−1(𝐀_hat * NTT(𝐲)) - let mut y_hat = y.clone(); + let mut y_hat = y; y_hat.ntt(); let mut w = A_hat.matrix_vector_ntt(&y_hat); // Bound-keeping step before NTT⁻¹, not in FIPS 204: see [`Polynomial::reduce32`]. @@ -967,7 +925,7 @@ impl< // 2: (𝑐_tilde, 𝐳, 𝐡) ← sigDecode(𝜎) // ▷ signer’s commitment hash c_tilde, response 𝐳, and hint 𝐡 // 3: if 𝐡 = ⊥ then return false - let (c_tilde, z, h) = sig_decode::(&sig) + let (c_tilde, z, h) = sig_decode::(sig) .map_err(|_| SignatureError::SignatureVerificationFailed)?; // 13 (first half) return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] @@ -978,7 +936,7 @@ impl< // 5: 𝐀 ← ExpandA(𝜌) // ▷ 𝐀 is generated and stored in NTT representation as 𝐀 // The code offers an optimization where the user can pre-expand A_hat within the - // public key object for faster repeated encapsulations against this public key. + // public key object for faster repeated verifications against this public key. // 6: 𝑡𝑟 ← H(𝑝𝑘, 64) // 7: 𝜇 ← (H(BytesToBits(𝑡𝑟)||𝑀 ′, 64)) @@ -1004,7 +962,7 @@ impl< // weird nested scoping is to reduce peak stack memory usage let w1p = { let Az = { - let mut z_hat = z.clone(); + let mut z_hat = z; z_hat.ntt(); A_hat.matrix_vector_ntt(&z_hat) }; @@ -1104,14 +1062,14 @@ impl< msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; 64], SignatureError> { - MuBuilder::compute_mu(&sk.tr(), msg, ctx) + MuBuilder::compute_mu(sk.tr(), msg, ctx) } fn sign_with_expanded_key( sk: &MLDSAPrivateKeyExpanded, msg: &[u8], ctx: Option<&[u8]>, ) -> Result<[u8; SIG_LEN], SignatureError> { - let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; + let mu = MuBuilder::compute_mu(sk.tr(), msg, ctx)?; Self::sign_mu(&sk.sk, Some(&sk.A_hat), &mu) } @@ -1123,7 +1081,7 @@ impl< ) -> Result { out.fill(0); - let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; + let mu = MuBuilder::compute_mu(sk.tr(), msg, ctx)?; Self::sign_mu_out(&sk.sk, Some(&sk.A_hat), &mu, out) } @@ -1205,16 +1163,10 @@ impl< Self::sign_mu_deterministic_from_seed_out(seed, mu, rnd, &mut out)?; Ok(out) } - /// External-μ deterministic signing directly from a 32-byte seed (a mash-up of - /// KeyGen Alg 6 and Sign Alg 7). Because μ is supplied by the caller, this never - /// derives the public key: it skips pkEncode and tr = H(pk), never materializes - /// the PK/SK structs, and keeps peak live memory low via scoped temporaries (see below). - /// This is a middle ground between keygen_from_seed()+sign_mu() and - /// the fully streamed low-memory implementation. - // TODO: benchmark peak memory + runtime against - // keygen_from_seed() + sign_mu_deterministic() to confirm the separate path earns being kept. - // Note: this path intentionally avoids the public key entirely - // (no pkEncode / tr = H(pk)) since μ is supplied externally. + /// External-mu deterministic signing from a 32-byte seed: the key is expanded with + /// [`Self::keygen_from_seed`] and the signature made from it with the same code path as + /// [`Self::sign_mu_deterministic_out`]. Nothing is cached, so a caller that signs more than + /// once should expand the key once and keep it. fn sign_mu_deterministic_from_seed_out( seed: &KeyMaterial<32>, mu: &[u8; 64], @@ -1222,283 +1174,8 @@ impl< output: &mut [u8; SIG_LEN], ) -> Result { output.fill(0); - - // This has been kept as clean as possible for correspondence with the FIPS, - // but things have been moved around so that unnamed scopes can be used to limit how many - // stack variables are alive at the same time. - - // 1: (𝜌, 𝐾, 𝑡𝑟, 𝐬1, 𝐬2, 𝐭0) ← skDecode(𝑠𝑘) - // to avoid having all of it in memory at the same time, - // we're gonna derive what we need as we need it. - - if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom) - || seed.key_len() != 32 - { - return Err(SignatureError::KeyGenError( - "Seed must be 32 bytes and KeyType::Seed or KeyType::BytesFullEntropy.", - )); - } - - if seed.security_strength() < P::MAX_SECURITY_STRENGTH { - return Err(SignatureError::KeyGenError( - "Seed SecurityStrength must match algorithm security strength: 128-bit (ML-DSA-44), 192-bit (ML-DSA-65), or 256-bit (ML-DSA-87).", - )); - } - - // Alg 7; 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀 ′, 64) - // skip: mu has already been provided - - let (rho, mut rho_p_p, s1, s2) = { - // scope for h - // derive sk.K - // Alg 6; 1: (rho, rho_prime, K) <- H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) - // ▷ expand seed - let (rho, rho_prime, K) = { - let mut h = H::default(); - h.do_update(seed.ref_to_bytes()); - h.do_update(&(P::k as u8).to_le_bytes()); - h.do_update(&(P::l as u8).to_le_bytes()); - let mut rho = [0u8; 32]; - let mut h = h.into_squeezer(); - let bytes_written = h.do_output_out(&mut rho); - debug_assert_eq!(bytes_written, 32); - let mut rho_prime = [0u8; 64]; - let bytes_written = h.do_output_out(&mut rho_prime); - debug_assert_eq!(bytes_written, 64); - let mut K: [u8; 32] = [0u8; 32]; - let bytes_written = h.do_output_out(&mut K); - debug_assert_eq!(bytes_written, 32); - - (rho, rho_prime, K) - }; - - // Alg 7; 7: 𝜌″ ← H(𝐾||𝑟𝑛𝑑||𝜇, 64) - let rho_p_p = { - let mut h = H::new(); - h.do_update(&K); - h.do_update(&rnd); - h.do_update(mu); - let mut rho_p_p = [0u8; 64]; - let mut h = h.into_squeezer(); - h.do_output_out(&mut rho_p_p); - - rho_p_p - }; - - // 4: (𝐬1, 𝐬2) ← ExpandS(𝜌′) - let (s1, s2) = expandS::

(&rho_prime); - - (rho, rho_p_p, s1, s2) - }; - - // Alg 7; 5: 𝐀_hat ← ExpandA(𝜌) - // Note on memory optimization: - // A_hat consumes a large bit of memory and technically could move inside the loop -- - // -- or even more aggressively, could be derived and multiplied by y_hat row-by-row -- - // But in my unit tests, it can be observed that the loop typically execute 1 - 3 times, sometimes as many - // as 20 or even 80 times. So moving expandA() inside the loop would be a pretty drastic speed-for-memory tradeoff - // whose generality falls out of the scope of this implementation. - // It is left as an optimization that can be made by users that require further reduction of memory usage - let A_hat = expandA::

(&rho); - - // Alg 7; 8: 𝜅 ← 0 - // ▷ initialize counter 𝜅 - let mut kappa: u16 = 0; - - // Alg 7; 9: (𝐳, 𝐡) ← ⊥ - // handled in the loop - - // Alg 7; 10: while (𝐳, 𝐡) = ⊥ do - // ▷ rejection sampling loop - - // these need to be outside the loop because they form the encoded signature value - let mut sig_val_c_tilde = ::ZEROED; - let mut sig_val_z: P::VecL; - let mut sig_val_h: P::VecK; - loop { - // FIPS 204 s. 6.2 allows: - // "Implementations may limit the number of iterations in this loop to not exceed a finite maximum value." - // mutants note: there is no test for this because we don't know of a KAT that will exceed this limit. - if kappa > 1000 * P::k as u16 { - return Err(SignatureError::GenericError( - "Rejection sampling loop exceeded max iterations, try again with a different signing nonce.", - )); - } - - // Alg 7; 11: 𝐲 ∈ 𝑅^ℓ ← ExpandMask(𝜌″, 𝜅) - let mut y = expand_mask::

(&rho_p_p, kappa); - - let w = { - // scope for y_hat - // Alg 7; 12: 𝐰 ← NTT−1(𝐀_hat * NTT(𝐲)) - let mut y_hat = y.clone(); - y_hat.ntt(); - let mut w = A_hat.matrix_vector_ntt(&y_hat); - // Bound-keeping step before NTT⁻¹, not in FIPS 204: see [`Polynomial::reduce32`]. - w.reduce32(); - w.inv_ntt(); - w.conditional_add_q(); - w - }; - - // Alg 7; 13: 𝐰1 ← HighBits(𝐰) - // ▷ signer’s commitment - let w1 = w.high_bits::

(); - - { - // scope for h - // 15: 𝑐_tilde ← H(𝜇||w1Encode(𝐰1), 𝜆/4) - // ▷ commitment hash - let mut hash = H::new(); - hash.do_update(mu); - w1.w1_encode_and_hash::

(&mut hash); - let mut hash = hash.into_squeezer(); - hash.do_output_out(sig_val_c_tilde.as_mut()); - } - - // Alg 7; 16: 𝑐 ∈ 𝑅𝑞 ← SampleInBall(c_tilde) - // ▷ verifier’s challenge - let c_hat = { - // scope for c - let mut c = sample_in_ball::

(&sig_val_c_tilde); - - // 17: 𝑐_hat ← NTT(𝑐) - c.ntt(); - c - }; - - let t_hat: P::VecK; - sig_val_z = { - // scope for s1_hat, cs1 - // Alg 7; 2: 𝐬1̂_hat ← NTT(𝐬1) - let mut s1_hat = s1.clone(); - s1_hat.ntt(); - - y = { - // scope for cs1 - // Alg 7; 18: ⟨⟨𝑐𝐬1⟩⟩ ← NTT−1(𝑐_hat * 𝐬1_hat) - // Note: <<.>> in FIPS 204 means that this value will be used again later, - // so it is better to keep it. - let mut cs1 = s1_hat.scalar_vector_ntt(&c_hat); - cs1.inv_ntt(); - - // Alg 7; 20: 𝐳 ← 𝐲 + ⟨⟨𝑐𝐬1⟩⟩ - y.add_vector_ntt(&cs1); - y - }; - - // also, while we have s1_hat in memory, compute t_hat - // Alg 6; 5: 𝐭 ← NTT−1(𝐀 ∘ NTT(𝐬1)) + 𝐬2 - // ▷ compute 𝐭 = 𝐀𝐬1 + 𝐬2 - t_hat = A_hat.matrix_vector_ntt(&s1_hat); - - y - }; - - // Alg 7; 23 (first half): if ||𝐳||∞ ≥ 𝛾1 − 𝛽 or ||𝐫0||∞ ≥ 𝛾2 − 𝛽 then (z, h) ← ⊥ - // ▷ validity checks - // This is done out-of-order on purpose for performance reasons: - // rejection sampling check is done before any extra heavy computation - if sig_val_z.check_norm(P::gamma1_minus_beta) { - kappa += P::l as u16; - continue; - }; - - let t0: P::VecK; - let mut r0: P::VecK = { - // scope for s2_hat and cs2 - // 3: 𝐬2̂_hat ← NTT(𝐬2) - let mut s2_hat = s2.clone(); - s2_hat.ntt(); - - // 19: ⟨⟨𝑐𝐬2⟩⟩ ← NTT−1(𝑐_hat * 𝐬2̂_hat) - let mut cs2 = s2_hat.scalar_vector_ntt(&c_hat); - cs2.inv_ntt(); - - // 21: 𝐫0 ← LowBits(𝐰 − ⟨⟨𝑐𝐬2⟩⟩) - let r0 = w.sub_vector(&cs2).low_bits::

(); - - // while s2_hat is in scope, derive t0 - let mut t = t_hat; - // Bound-keeping step before NTT⁻¹, not in FIPS 204: see [`Polynomial::reduce32`]. - t.reduce32(); - t.inv_ntt(); - t.add_vector_ntt(&s2); - t.conditional_add_q(); - - // 6: (𝐭1, 𝐭0) ← Power2Round(𝐭) - // ▷ compress 𝐭 - // ▷ PowerTwoRound is applied componentwise (see explanatory text in Section 7.4) - let (_t1tmp, t0tmp) = power_2_round_vec(&t); - t0 = t0tmp; - - r0 - }; - - // Alg 7; 23 (second half): if ||𝐳||∞ ≥ 𝛾1 − 𝛽 or ||𝐫0||∞ ≥ 𝛾2 − 𝛽 then (z, h) ← ⊥ - // ▷ validity checks - if r0.check_norm(P::gamma2_minus_beta) { - // mutants note: mutants thinks this can be replaced with -=, but in practice that makes - // the rejection sampling loop go forever, so is a false positive. - kappa += P::l as u16; - continue; - }; - - let ct0: P::VecK = { - // scope for t0_hat - // 4: 𝐭0̂_hat ← NTT(𝐭0)̂ - let mut t0_hat = t0.clone(); - t0_hat.ntt(); - - // 25: ⟨⟨𝑐𝐭0⟩⟩ ← NTT−1(𝑐_hat * 𝐭0̂_hat ) - let mut ct0 = t0_hat.scalar_vector_ntt(&c_hat); - ct0.inv_ntt(); - ct0 - }; - - // Alg 7; 28 (first half): if ||⟨⟨𝑐𝐭0⟩⟩||∞ ≥ 𝛾2 or the number of 1’s in 𝐡 is greater than 𝜔, then (z, h) ← ⊥ - // out-of-order on purpose for performance reasons: - // might as well do the rejection sampling check before any extra heavy computation - // mutants note: there is currently no unit test that triggers this branch - if ct0.check_norm(P::gamma2) { - kappa += P::l as u16; - continue; - }; - - // Alg 7; 26: 𝐡 ← MakeHint(−⟨⟨𝑐𝐭0⟩⟩, 𝐰 − ⟨⟨𝑐𝐬2⟩⟩ + ⟨⟨𝑐𝐭0⟩⟩) - // ▷ Signer’s hint - r0.add_vector_ntt(&ct0); - r0.conditional_add_q(); - let hint_hamming_weight: i32; - sig_val_h = { - // scope for hint - let (hint, inner_hint_hamming_weight) = make_hint_vecs::

(&r0, &w1); - hint_hamming_weight = inner_hint_hamming_weight; - hint - }; - - // Alg 7; 28 (second half): if ||⟨⟨𝑐𝐭0⟩⟩||∞ ≥ 𝛾2 or the number of 1’s in 𝐡 is greater than 𝜔, then (z, h) ← ⊥ - // mutants note: there is currently no unit test that triggers this branch - if hint_hamming_weight > P::omega { - kappa += P::l as u16; - continue; - }; - - break; - } - - // zeroize rho_p_p before returning it to the OS - rho_p_p.fill(0u8); - - // sig_encode does not necessarily write to all bytes of the output, - // The following is done for safety - output.fill(0u8); - - // Alg 7; 33: 𝜎 ← sigEncode(𝑐, 𝐳̃ mod±𝑞, 𝐡) - let bytes_written = - sig_encode::(&sig_val_c_tilde, &sig_val_z, &sig_val_h, output); - - Ok(bytes_written) + let (_pk, sk) = Self::keygen_internal(seed)?; + Self::sign_internal(&sk, &sk.A_hat(), mu, rnd, output) } fn set_signer_rnd(&mut self, rnd: [u8; 32]) { self.signer_rnd = Some(rnd); @@ -1510,10 +1187,9 @@ impl< let (_pk, sk) = Self::keygen_from_seed(seed)?; Ok(Self { _phantom: PhantomData, - mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, + mu_builder: MuBuilder::do_init(sk.tr(), ctx)?, signer_rnd: None, - sk: None, - seed: Some(seed.clone()), + sk: Some(sk), pk: None, }) } @@ -1567,7 +1243,7 @@ pub trait MLDSATrait< fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { // Source the seed from the provided RNG if rng.security_strength() < P::MAX_SECURITY_STRENGTH { - return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?; + return Err(RNGError::SecurityStrengthInsufficientForAlgorithm.into()); } let mut seed = KeyMaterial256::new(); rng.fill_keymaterial_out(&mut seed)?; @@ -1708,13 +1384,11 @@ pub trait MLDSATrait< /// key object -- although the more ergonomic way to use this functionality is via the /// [`MLDSAPublicKeyExpanded`] object. /// - /// Security note about deterministic mode: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that - /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature. - /// If not, some privacy properties may be lost; for example it becomes easy to tell if a signer - /// has signed the same message twice or two different messagase, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// Security note: + /// `rnd` comes from the caller. FIPS 204 Section 3.4 permits the deterministic variant, `rnd` + /// all zeros, and the hedged variant draws it fresh. Deterministic signing does not leak the + /// key, but a repeated `rnd` loses some privacy: it becomes easy to tell whether a signer has + /// signed the same message twice, or whether two signatures on one message share a signer. /// /// Since `rnd` should be either a per-signature nonce, or a fixed value, therefore, to help /// prevent accidental nonce reuse, this function moves `rnd`. @@ -1724,27 +1398,7 @@ pub trait MLDSATrait< mu: &[u8; 64], rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError>; - /// Algorithm 7 ML-DSA.Sign_internal(𝑠𝑘, 𝑀′, 𝑟𝑛𝑑) - /// (modified to take an externally-computed mu instead of M') - /// - /// Performs an ML-DSA signature using the provided external message representative `mu`. - /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both - /// FIPS 204 itself, as well as subsequent FAQ documents. - /// This mode exposes deterministic signing (called "hedged mode" in FIPS 204) using an internal RNG. - /// - /// This mode exposes the signing nonce `rnd` either for users who wish to source the signing - /// nonce from a source other than the library's default internal RNG, or who wish to use the - /// "deterministic mode" defined in FIPS 204 by providing `rnd = [0u8; 32]`. - /// In order to help prevent against accidental nonce reuse, this function moves `rnd` instead - /// of taking it by reference. - /// - /// Security note about deterministic mode: - /// This mode exposes deterministic signing (called "hedged mode" and allowed by FIPS 204). - /// The ML-DSA algorithm is considered safe to use in deterministic mode, but be aware that - /// the responsibility is on the user to ensure that the nonce `rnd` is unique for each signature. - /// If not, some privacy properties may be lost; for example, it becomes easy to tell if a signer - /// has signed the same message twice or two different messages, or to tell if the same message - /// has been signed by the same signer twice or two different signers. + /// As [`MLDSATrait::sign_mu_deterministic`], writing into `output`; see its security note. /// /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer. fn sign_mu_deterministic_out( @@ -1754,17 +1408,15 @@ pub trait MLDSATrait< rnd: [u8; 32], output: &mut [u8; SIG_LEN], ) -> Result; - /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak - /// memory usage by never having the full secret key in memory at the same time, - /// and by deriving intermediate values piece-wise as needed. + /// External-mu deterministic signing from a 32-byte seed: [`Self::keygen_from_seed`] + /// followed by [`Self::sign_mu_deterministic`]. A caller that signs more than once should + /// expand the key once and keep it. fn sign_mu_deterministic_from_seed( seed: &KeyMaterial<32>, mu: &[u8; 64], rnd: [u8; 32], ) -> Result<[u8; SIG_LEN], SignatureError>; - /// This contains a heavily-optimized combined keygen() and sign() which greatly reduces peak - /// memory usage by never having the full secret key in memory at the same time, - /// and by deriving intermediate values piece-wise as needed. + /// As [`Self::sign_mu_deterministic_from_seed`], writing into `output`. fn sign_mu_deterministic_from_seed_out( seed: &KeyMaterial<32>, mu: &[u8; 64], @@ -1774,8 +1426,8 @@ pub trait MLDSATrait< /// To be used for deterministic signing in conjunction with the [`MLDSA44::do_sign_init`], [`MLDSA44::do_sign_update`], and [`MLDSA44::do_sign_final`] flow. /// Can be set anywhere after [`MLDSA44::do_sign_init`] and before [`MLDSA44::do_sign_final`]. fn set_signer_rnd(&mut self, rnd: [u8; 32]); - /// Alternative initialization of the streaming signer where the user has their private key - /// as a seed and they want to delay its expansion as late as possible for memory-usage reasons. + /// Initialises the streaming signer from a 32-byte seed: the key is expanded here with + /// [`Self::keygen_from_seed`] and held by the signer. fn sign_init_from_seed( seed: &KeyMaterial<32>, ctx: Option<&[u8]>, @@ -1823,7 +1475,7 @@ impl< ) -> Result { output.fill(0); - let mu = MuBuilder::compute_mu(&sk.tr(), msg, ctx)?; + let mu = MuBuilder::compute_mu(sk.tr(), msg, ctx)?; let bytes_written = Self::sign_mu_out(sk, None, &mu, output)?; Ok(bytes_written) @@ -1832,10 +1484,9 @@ impl< fn do_sign_init(sk: &SK, ctx: Option<&[u8]>) -> Result { Ok(Self { _phantom: PhantomData, - mu_builder: MuBuilder::do_init(&sk.tr(), ctx)?, + mu_builder: MuBuilder::do_init(sk.tr(), ctx)?, signer_rnd: None, sk: Some(sk.clone()), - seed: None, pk: None, }) } @@ -1851,40 +1502,19 @@ impl< } fn do_sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result { + output.fill(0); let mu = self.mu_builder.do_final(); - if self.sk.is_none() && self.seed.is_none() { + let Some(sk) = self.sk else { return Err(SignatureError::GenericError( - "do_sign_final_out called on a streaming context with no private key or seed; \ + "do_sign_final_out called on a streaming context with no private key; \ this is a verify-initialized context. Call do_verify_final instead", )); - } - - output.fill(0); + }; - if self.sk.is_some() { - if self.signer_rnd.is_none() { - Self::sign_mu_out(&self.sk.unwrap(), None, &mu, output) - } else { - Self::sign_mu_deterministic_out( - &self.sk.unwrap(), - None, - &mu, - self.signer_rnd.unwrap(), - output, - ) - } - } else if self.seed.is_some() { - let rnd = if self.signer_rnd.is_some() { - self.signer_rnd.unwrap() - } else { - let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN]; - HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?; - rnd - }; - Self::sign_mu_deterministic_from_seed_out(&self.seed.unwrap(), &mu, rnd, output) - } else { - unreachable!() + match self.signer_rnd { + None => Self::sign_mu_out(&sk, None, &mu, output), + Some(rnd) => Self::sign_mu_deterministic_out(&sk, None, &mu, rnd, output), } } } @@ -1912,7 +1542,6 @@ impl< mu_builder: MuBuilder::do_init(&pk.compute_tr(), ctx)?, signer_rnd: None, sk: None, - seed: None, pk: Some(pk.clone()), }) } @@ -1935,100 +1564,7 @@ impl< } } -/// Implements parts of Algorithm 2 and Line 6 of Algorithm 7 of FIPS 204. -/// Provides a stateful version of [`MLDSATrait::compute_mu_from_pk`] and [`MLDSATrait::compute_mu_from_tr`] -/// that supports streaming -/// large to-be-signed messages. -/// -/// Note: this struct is only exposed for "pure" ML-DSA and not for HashML-DSA because HashML-DSA -/// does not benefit from allowing external construction of the message representative mu. -/// The same behaviour can be obtained by computing the pre-hash `ph` with the appropriate hash function -/// and providing that to HashMLDSA via [`PHSigner::sign_ph`]. -#[derive(Clone)] -pub struct MuBuilder { - h: H, -} - -impl MuBuilder { - /// Algorithm 7 - /// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀′, 64) - pub fn compute_mu( - tr: &[u8; 64], - msg: &[u8], - ctx: Option<&[u8]>, - ) -> Result<[u8; 64], SignatureError> { - let mut mu_builder = MuBuilder::do_init(&tr, ctx)?; - mu_builder.do_update(msg); - let mu = mu_builder.do_final(); - - Ok(mu) - } - - /// This function requires the public key hash `tr`, which can be computed from the public key - /// using [`MLDSAPublicKeyTrait::compute_tr`]. - pub fn do_init(tr: &[u8; 64], ctx: Option<&[u8]>) -> Result { - let ctx = match ctx { - Some(ctx) => ctx, - None => &[], - }; - - // Algorithm 2 - // 1: if |𝑐𝑡𝑥| > 255 then - if ctx.len() > 255 { - return Err(SignatureError::LengthError("ctx value is longer than 255 bytes")); - } - - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64) - let mut mb = Self { h: H::new() }; - mb.h.do_update(tr); - - // Algorithm 2 - // 10: 𝑀′ ← BytesToBits(IntegerToBytes(0, 1) ∥ IntegerToBytes(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥) ∥ 𝑀 - // all done together - mb.h.do_update(&[0u8]); - mb.h.do_update(&[ctx.len() as u8]); - mb.h.do_update(ctx); - - // now ready to absorb M - Ok(mb) - } - - /// Stream a chunk of the message. - pub fn do_update(&mut self, msg_chunk: &[u8]) { - self.h.do_update(msg_chunk); - } - - /// Finalize and return the mu value. - pub fn do_final(self) -> [u8; 64] { - // Completion of - // Algorithm 7 - // 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀 ′, 64) - let mut mu = [0u8; 64]; - self.h.into_squeezer().do_output_out(&mut mu); - - mu - } -} - -/// The length, in bytes, of a serialized state of a [`MuBuilder`] object. -pub const SUSPENDED_MU_BUILDER_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; - -/// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [`Suspendable`]. -/// Serialization of the state of an in-progress ML-DSA instance is really just serialization -/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm -/// has a pausable state. -// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, -// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl Suspendable for MuBuilder { - fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { - self.h.suspend() - } - - fn from_suspended( - serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { - Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) - } -} +// The message representative builder is shared with the other ML-DSA crate and lives in +// `bouncycastle-mldsa-common`. +#[doc(inline)] +pub use bouncycastle_mldsa_common::mu_builder::{MuBuilder, SUSPENDED_MU_BUILDER_STATE_LEN}; diff --git a/crypto/mldsa/src/mldsa_keys.rs b/crypto/mldsa/src/mldsa_keys.rs index e89d857c7..48ad2c76d 100644 --- a/crypto/mldsa/src/mldsa_keys.rs +++ b/crypto/mldsa/src/mldsa_keys.rs @@ -1,14 +1,15 @@ -use crate::aux_functions::{ - bit_pack_eta, bit_pack_t0, bit_unpack_eta, bit_unpack_t0, expandA, power_2_round_vec, - simple_bit_pack_t1, simple_bit_unpack_t1, -}; +use crate::aux_functions::{expandA, power_2_round_vec}; use crate::matrix::{MatrixTrait, VectorTrait}; use crate::mldsa::H; use crate::mldsa::{POLY_T0PACKED_LEN, POLY_T1PACKED_LEN}; -use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams}; +use crate::params::{MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParams, MLDSAParamsBase}; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{SignaturePrivateKey, SignaturePublicKey, XOF}; +use bouncycastle_mldsa_common::aux_functions::{ + bit_pack_eta, bit_pack_t0, bit_unpack_eta, bit_unpack_t0, simple_bit_pack_t1, + simple_bit_unpack_t1, +}; use bouncycastle_utils::secret::Secret; use core::fmt; use core::fmt::{Debug, Display, Formatter}; @@ -17,7 +18,7 @@ use core::fmt::{Debug, Display, Formatter}; #[allow(unused_imports)] use crate::mldsa::MLDSATrait; #[allow(unused_imports)] -use crate::polynomial::Polynomial; +use bouncycastle_mldsa_common::polynomial::Polynomial; /* Pub Types */ @@ -39,10 +40,10 @@ pub type MLDSA87PrivateKey = /* Pre-expanded keys for repeated operations */ -/// ML-DSA-44 Public Key with a pre-expanded public matrix A for repeated encaps operations. +/// ML-DSA-44 Public Key with a pre-expanded public matrix A for repeated verify operations. pub type MLDSA44PublicKeyExpanded = MLDSAPublicKeyExpanded; -/// ML-DSA-44 Private Key with a pre-expanded public matrix A for repeated decaps operations. +/// ML-DSA-44 Private Key with a pre-expanded public matrix A for repeated sign operations. pub type MLDSA44PrivateKeyExpanded = MLDSAPrivateKeyExpanded< MLDSA44Params, MLDSA44PublicKey, @@ -50,10 +51,10 @@ pub type MLDSA44PrivateKeyExpanded = MLDSAPrivateKeyExpanded< { MLDSA44Params::SK_LEN }, { MLDSA44Params::PK_LEN }, >; -/// ML-DSA-65 Public Key with a pre-expanded public matrix A for repeated encaps operations. +/// ML-DSA-65 Public Key with a pre-expanded public matrix A for repeated verify operations. pub type MLDSA65PublicKeyExpanded = MLDSAPublicKeyExpanded; -/// ML-DSA-65 Private Key with a pre-expanded public matrix A for repeated decaps operations. +/// ML-DSA-65 Private Key with a pre-expanded public matrix A for repeated sign operations. pub type MLDSA65PrivateKeyExpanded = MLDSAPrivateKeyExpanded< MLDSA65Params, MLDSA65PublicKey, @@ -61,10 +62,10 @@ pub type MLDSA65PrivateKeyExpanded = MLDSAPrivateKeyExpanded< { MLDSA65Params::SK_LEN }, { MLDSA65Params::PK_LEN }, >; -/// ML-DSA-87 Public Key with a pre-expanded public matrix A for repeated encaps operations. +/// ML-DSA-87 Public Key with a pre-expanded public matrix A for repeated verify operations. pub type MLDSA87PublicKeyExpanded = MLDSAPublicKeyExpanded; -/// ML-DSA-87 Private Key with a pre-expanded public matrix A for repeated decaps operations. +/// ML-DSA-87 Private Key with a pre-expanded public matrix A for repeated sign operations. pub type MLDSA87PrivateKeyExpanded = MLDSAPrivateKeyExpanded< MLDSA87Params, MLDSA87PublicKey, @@ -108,7 +109,7 @@ impl MLDSAPublicKey { debug_assert_eq!(pk_chunks.len(), P::k); debug_assert_eq!(last_chunk.len(), 0); - for (pk_chunk, t1_i) in pk_chunks.into_iter().zip(self.t1.elems()) { + for (pk_chunk, t1_i) in pk_chunks.iter_mut().zip(self.t1.elems()) { pk_chunk.copy_from_slice(&simple_bit_pack_t1(t1_i)); } @@ -134,7 +135,7 @@ pub trait MLDSAPublicKeyTrait: /// This is exposed as a public API for a few reasons: /// 1. `tr` is required for some external-prehashing schemes such as the so-called "external mu" signing mode. /// 2. `tr` is the canonical fingerprint of an ML-DSA public key, so would be an appropriate value - /// to use, for example, to build a public key lookup or deny-listing table. + /// to use, for example, to build a public key lookup or deny-listing table. fn compute_tr(&self) -> [u8; 64]; } @@ -152,7 +153,6 @@ pub(crate) trait MLDSAPublicKeyInternalTrait MLDSAPublicKeyTrait for MLDSAPublicKey { - // todo: block a t1 of all zeros? Maybe add to consistency_check() ? fn pk_decode(pk: &[u8; PK_LEN]) -> Self { let rho = pk[0..32].try_into().unwrap(); let mut t1 = P::VecK::new(); @@ -360,8 +360,8 @@ impl< const PK_LEN: usize, > From<&PK> for MLDSAPublicKeyExpanded { - /// Fully expands the intermediate values needed for performing multiple encaps operations - /// against the same public key, which causes the MLKEMPublicKey struct to take up + /// Expands the public matrix A once, for repeated verifications under the same public key; the + /// expanded object is correspondingly larger (see the crate's Memory Usage table). fn from(pk: &PK) -> Self { let A_hat = pk.A_hat(); @@ -550,10 +550,7 @@ impl MLDSAPrivateKeyTrait for MLDSAPrivateKey { fn seed(&self) -> Option<&KeyMaterial<32>> { - match self.seed { - Some(_) => self.seed.as_ref(), - None => None, - } + self.seed.as_ref() } fn tr(&self) -> &[u8; 64] { @@ -879,8 +876,8 @@ impl< const PK_LEN: usize, > From<&SK> for MLDSAPrivateKeyExpanded { - /// Fully expands the intermediate values needed for performing multiple encaps operations - /// against the same public key, which causes the MLKEMPublicKey struct to take up + /// Expands the public matrix A once, for repeated signatures under the same private key; the + /// expanded object is correspondingly larger (see the crate's Memory Usage table). fn from(sk: &SK) -> Self { let A_hat = as MLDSAPublicKeyTrait>::A_hat(&sk.derive_pk()); diff --git a/crypto/mldsa/src/params.rs b/crypto/mldsa/src/params.rs index 1c0da9646..a8070330d 100644 --- a/crypto/mldsa/src/params.rs +++ b/crypto/mldsa/src/params.rs @@ -1,120 +1,32 @@ -//! The three ML-DSA parameter sets of FIPS 204, Section 4, and the six HashML-DSA pairings built on -//! them, each as a sealed trait with one type per set. +//! This crate's view of the three ML-DSA parameter sets of FIPS 204, Section 4, and the four +//! HashML-DSA pairings built on them, each as a sealed trait with one type per set. //! -//! # Derived parameters -//! -//! FIPS 204, Table 1 assigns eight independent values per set (𝜏, 𝜆, 𝛾1, 𝛾2, (𝑘, ℓ), 𝜂, 𝜔) plus the -//! three sizes of Table 2. Everything else this implementation needs is a function of those, so it -//! is written once as a defaulted associated const rather than three times as a hand-computed -//! number. `params::tests` checks every derivation against the values tabulated in FIPS 204. +//! The values of Table 1 and Table 2, the algorithm metadata and the derived constants live on +//! [`MLDSAParamsBase`] in `bouncycastle-mldsa-common`, shared with `bouncycastle-mldsa-lowmemory`; +//! [`MLDSAParams`] extends it with the vector and matrix types this implementation materializes. use crate::hash_mldsa::{ HASH_ML_DSA_44_with_SHA256_NAME, HASH_ML_DSA_44_with_SHA512_NAME, - HASH_ML_DSA_65_WITH_SHA256_NAME, HASH_ML_DSA_65_WITH_SHA512_NAME, - HASH_ML_DSA_87_WITH_SHA512_NAME, HASH_ML_DSA_87_with_SHA256_NAME, + HASH_ML_DSA_65_with_SHA512_NAME, HASH_ML_DSA_87_with_SHA512_NAME, }; use crate::matrix::{Matrix, MatrixTrait, Vector, VectorTrait}; -use crate::mldsa::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, q}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, HashAlgParams}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, HashOneShot}; +use bouncycastle_mldsa_common::params::weaker_of; use bouncycastle_sha2::{SHA256, SHA512}; -use bouncycastle_utils::secret::ZeroizablePrimitive; - -/// `bitlen 𝑥`, the length of the binary expansion of 𝑥 (FIPS 204, Section 2.3). -/// -/// `bitlen 0` is 0; every use below has a positive argument. -pub(crate) const fn bitlen(x: u32) -> usize { - if x == 0 { 0 } else { x.ilog2() as usize + 1 } -} - -/// A fixed-size byte buffer whose length depends on the parameter set. -/// -/// [`ZeroizablePrimitive`] rather than [`Default`] supplies the all-zero value, because `Default` -/// for arrays stops at 32 elements and every buffer here is longer than that. -trait ByteBuffer: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]> {} -impl ByteBuffer for [u8; N] {} -/// A crate-private (aka "sealed") trait that prevents a new ML-DSA parameter set from being defined -/// outside this crate. -trait MLDSAParamsInternalTrait {} +pub use bouncycastle_mldsa_common::params::{ + MLDSA44Params, MLDSA65Params, MLDSA87Params, MLDSAParamsBase, +}; -/// One ML-DSA parameter set: the values of FIPS 204, Table 1 and Table 2, and the types whose size -/// they determine. +/// One ML-DSA parameter set as this crate uses it: everything on [`MLDSAParamsBase`], plus the +/// vector and matrix types whose dimensions the set fixes. /// -/// Sealed via a private supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] +/// Sealed through its supertrait, so [`MLDSA44Params`], [`MLDSA65Params`] and [`MLDSA87Params`] /// are the only implementations. -pub trait MLDSAParams: MLDSAParamsInternalTrait { - /* FIPS 204, Table 1: the values assigned by each parameter set. */ - - /// 𝜏, the number of ±1's in the polynomial 𝑐. - const tau: i32; - /// 𝜆, the collision strength of 𝑐̃, in bits. - const lambda: i32; - /// 𝛾1, the coefficient range of 𝐲. Always a power of two. - const gamma1: i32; - /// 𝛾2, the low-order rounding range. - const gamma2: i32; - /// 𝑘, the number of rows of 𝐀. - const k: usize; - /// ℓ, the number of columns of 𝐀. - const l: usize; - /// 𝜂, the private key range. - const eta: usize; - /// 𝜔, the maximum number of 1's in the hint 𝐡. - const omega: i32; - - /* FIPS 204, Table 2: sizes in bytes of keys and signatures. */ - - /// The length of an encoded public key. - const PK_LEN: usize; - /// The length of an encoded private key. - const SK_LEN: usize; - /// The length of a signature. - const SIG_LEN: usize; - - /* Algorithm meta-data */ - - /// The algorithm name, as reported by `Algorithm::ALG_NAME`. - const ALG_NAME: &'static str; - /// The strength claimed for this parameter set, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. - const MAX_SECURITY_STRENGTH: SecurityStrength; - /// The OID in component form, as reported by `AlgorithmOID::OID`. - const OID: &'static [u32]; - /// The DER encoding of [`MLDSAParams::OID`], as reported by `AlgorithmOID::OID_DER`. - const OID_DER: &'static [u8]; - - /* Derived. Never written out per parameter set -- see the module docs. */ - - /// 𝛽, which FIPS 204, Table 1 defines as "𝛽 = 𝜏 ⋅ 𝜂". - const beta: i32 = Self::tau * Self::eta as i32; - - /// The length of the commitment hash 𝑐̃, which FIPS 204, Algorithm 26 (sigEncode) gives as - /// 𝑐̃ ∈ 𝔹^(𝜆/4). - const C_TILDE_LEN: usize = Self::lambda as usize / 4; - - /// The packed length of one coordinate of 𝐳: FIPS 204, Algorithm 26 (sigEncode) writes each of - /// the ℓ coordinates as 𝔹^(32⋅(1+bitlen (𝛾1−1))). - /// - /// This is also the number of bytes ExpandMask squeezes per coordinate: FIPS 204, - /// Algorithm 34, line 1 sets 𝑐 ← 1 + bitlen (𝛾1 − 1) and line 4 squeezes 32𝑐 bytes. - const POLY_Z_PACKED_LEN: usize = 32 * (1 + bitlen(Self::gamma1 as u32 - 1)); - - /// The packed length of one coordinate of 𝐬1 or 𝐬2: FIPS 204, Algorithm 24 (skEncode), line 3 - /// packs each with BitPack(𝐬1[𝑖], 𝜂, 𝜂), and Algorithm 17 (BitPack) outputs - /// 𝔹^(32⋅bitlen (𝑎+𝑏)), so 32⋅bitlen (2𝜂). - const POLY_ETA_PACKED_LEN: usize = 32 * bitlen(2 * Self::eta as u32); - - /// The packed length of one coordinate of 𝐰1: FIPS 204, Algorithm 28 (w1Encode) outputs - /// 𝔹^(32𝑘⋅bitlen ((𝑞−1)/(2𝛾2)−1)) for all 𝑘 coordinates together. - const POLY_W1_PACKED_LEN: usize = 32 * bitlen(((q - 1) / (2 * Self::gamma2)) as u32 - 1); - - /// 𝛾1 − 𝛽, the rejection bound on ‖𝐳‖∞ (FIPS 204, Algorithm 7, line 23). - const gamma1_minus_beta: i32 = Self::gamma1 - Self::beta; - - /// 𝛾2 − 𝛽, the rejection bound on ‖𝐫0‖∞ (FIPS 204, Algorithm 7, line 23). - const gamma2_minus_beta: i32 = Self::gamma2 - Self::beta; - - /* Types whose size depends on the parameter set. */ +pub trait MLDSAParams: MLDSAParamsBase { + /// The length of an encoded private key, which this crate stores in its FIPS 204 form. + const SK_LEN: usize = Self::FULL_SK_LEN; /// A vector of 𝑘 polynomials, i.e. an element of 𝑅^𝑘. type VecK: VectorTrait; @@ -122,148 +34,24 @@ pub trait MLDSAParams: MLDSAParamsInternalTrait { type VecL: VectorTrait; /// The 𝑘 × ℓ public matrix 𝐀̂. type MatrixA: MatrixTrait; - - /// The commitment hash 𝑐̃, of [`MLDSAParams::C_TILDE_LEN`] bytes. - type SigCTilde: ByteBuffer; - /// One packed coordinate of 𝐳, of [`MLDSAParams::POLY_Z_PACKED_LEN`] bytes. - /// - /// ExpandMask squeezes into a buffer of this same length; see - /// [`MLDSAParams::POLY_Z_PACKED_LEN`]. - type PolyZPacked: ByteBuffer; - /// One packed coordinate of 𝐰1, of [`MLDSAParams::POLY_W1_PACKED_LEN`] bytes. - type PolyW1Packed: ByteBuffer; } -/// The ML-DSA-44 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA44Params; -/// The ML-DSA-65 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA65Params; -/// The ML-DSA-87 parameter set (FIPS 204, Table 1). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MLDSA87Params; - -impl MLDSAParamsInternalTrait for MLDSA44Params {} -impl MLDSAParamsInternalTrait for MLDSA65Params {} -impl MLDSAParamsInternalTrait for MLDSA87Params {} - impl MLDSAParams for MLDSA44Params { - const tau: i32 = 39; - const lambda: i32 = 128; - const gamma1: i32 = 1 << 17; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 88; - const k: usize = 4; - const l: usize = 4; - const eta: usize = 2; - const omega: i32 = 80; - - const PK_LEN: usize = 1312; - const SK_LEN: usize = 2560; - const SIG_LEN: usize = 2420; - - const ALG_NAME: &'static str = ML_DSA_44_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; - type VecK = Vector<4>; type VecL = Vector<4>; type MatrixA = Matrix<4, 4>; - - type SigCTilde = [u8; 32]; // 𝜆/4 = 128/4 - type PolyZPacked = [u8; 576]; // 32 * (1 + bitlen(2^17 - 1)) = 32 * 18 - type PolyW1Packed = [u8; 192]; // 32 * bitlen(44 - 1) = 32 * 6 } impl MLDSAParams for MLDSA65Params { - const tau: i32 = 49; - const lambda: i32 = 192; - const gamma1: i32 = 1 << 19; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 32; - const k: usize = 6; - const l: usize = 5; - const eta: usize = 4; - const omega: i32 = 55; - - const PK_LEN: usize = 1952; - const SK_LEN: usize = 4032; - const SIG_LEN: usize = 3309; - - const ALG_NAME: &'static str = ML_DSA_65_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; - type VecK = Vector<6>; type VecL = Vector<5>; type MatrixA = Matrix<6, 5>; - - type SigCTilde = [u8; 48]; // 𝜆/4 = 192/4 - type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 - type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 } impl MLDSAParams for MLDSA87Params { - const tau: i32 = 60; - const lambda: i32 = 256; - const gamma1: i32 = 1 << 19; - // mutants note: because of the bitshifting, the "- 1" ends up not mattering. - const gamma2: i32 = (q - 1) / 32; - const k: usize = 8; - const l: usize = 7; - const eta: usize = 2; - const omega: i32 = 75; - - const PK_LEN: usize = 2592; - const SK_LEN: usize = 4896; - const SIG_LEN: usize = 4627; - - const ALG_NAME: &'static str = ML_DSA_87_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; - /// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; - type VecK = Vector<8>; type VecL = Vector<7>; type MatrixA = Matrix<8, 7>; - - type SigCTilde = [u8; 64]; // 𝜆/4 = 256/4 - type PolyZPacked = [u8; 640]; // 32 * (1 + bitlen(2^19 - 1)) = 32 * 20 - type PolyW1Packed = [u8; 128]; // 32 * bitlen(16 - 1) = 32 * 4 -} - -/// The two distinct values 𝛾1 takes across the three parameter sets (FIPS 204, Table 1). -/// -/// The bit-packing routines have one layout per distinct 𝛾1, so they dispatch on these rather than -/// on the parameter set. ML-DSA-65 and ML-DSA-87 share the second value. -pub(crate) const GAMMA1_2_POW_17: i32 = MLDSA44Params::gamma1; -/// See [`GAMMA1_2_POW_17`]. -pub(crate) const GAMMA1_2_POW_19: i32 = MLDSA65Params::gamma1; - -/// The two distinct values 𝛾2 takes across the three parameter sets (FIPS 204, Table 1). -/// -/// As with 𝛾1, the routines that depend on 𝛾2 have one form per distinct value rather than one per -/// parameter set. ML-DSA-65 and ML-DSA-87 share the second value. -pub(crate) const GAMMA2_Q_MINUS_1_OVER_88: i32 = MLDSA44Params::gamma2; -/// See [`GAMMA2_Q_MINUS_1_OVER_88`]. -pub(crate) const GAMMA2_Q_MINUS_1_OVER_32: i32 = MLDSA65Params::gamma2; - -/// The weaker of two security strengths. -/// -/// [`SecurityStrength`]'s discriminants are assigned in increasing order of strength, so comparing -/// them as integers orders them. A `const fn` because the strength of a HashML-DSA pairing is a -/// defaulted associated const. -const fn weaker_of(a: SecurityStrength, b: SecurityStrength) -> SecurityStrength { - if (a as u8) <= (b as u8) { a } else { b } } /// A crate-private (aka "sealed") trait that prevents a new HashML-DSA pairing from being defined @@ -277,12 +65,12 @@ trait HashMLDSAParamsInternalTrait {} /// here, so [`crate::hash_mldsa::HashMLDSA`] takes one type rather than a parameter set plus a /// hash function plus a digest length. /// -/// Sealed via a private supertrait, so the six types below are the only implementations. +/// Sealed via a private supertrait, so the four types below are the only implementations. pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { /// The ML-DSA parameter set underneath. type MLDSA: MLDSAParams; /// PH, the pre-hash function. - type PreHash: Hash + HashAlgParams + AlgorithmOID + Default; + type PreHash: HashOneShot + HashAlgParams + AlgorithmOID; /// The algorithm name, as reported by `Algorithm::ALG_NAME`. /// @@ -298,9 +86,9 @@ pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { /// The strength claimed for the pairing, as reported by `Algorithm::MAX_SECURITY_STRENGTH`. /// /// A HashML-DSA signature is no stronger than either of its two components, so this is the - /// weaker of the two. That is what caps, for example, HashML-DSA-87_with_SHA256 at 128 bits. + /// weaker of the two; in every pairing here the ML-DSA parameter set is the binding one. const MAX_SECURITY_STRENGTH: SecurityStrength = weaker_of( - ::MAX_SECURITY_STRENGTH, + ::MAX_SECURITY_STRENGTH, ::MAX_SECURITY_STRENGTH, ); } @@ -309,14 +97,6 @@ pub trait HashMLDSAParams: HashMLDSAParamsInternalTrait { #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HashMLDSA44_with_SHA256Params; -/// The HashML-DSA-65_with_SHA256 pairing. -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct HashMLDSA65_with_SHA256Params; -/// The HashML-DSA-87_with_SHA256 pairing. -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct HashMLDSA87_with_SHA256Params; /// The HashML-DSA-44_with_SHA512 pairing. #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -331,8 +111,6 @@ pub struct HashMLDSA65_with_SHA512Params; pub struct HashMLDSA87_with_SHA512Params; impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA256Params {} -impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA256Params {} -impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA256Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA44_with_SHA512Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA65_with_SHA512Params {} impl HashMLDSAParamsInternalTrait for HashMLDSA87_with_SHA512Params {} @@ -342,16 +120,6 @@ impl HashMLDSAParams for HashMLDSA44_with_SHA256Params { type PreHash = SHA256; const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA256_NAME; } -impl HashMLDSAParams for HashMLDSA65_with_SHA256Params { - type MLDSA = MLDSA65Params; - type PreHash = SHA256; - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA256_NAME; -} -impl HashMLDSAParams for HashMLDSA87_with_SHA256Params { - type MLDSA = MLDSA87Params; - type PreHash = SHA256; - const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA256_NAME; -} impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { type MLDSA = MLDSA44Params; type PreHash = SHA512; @@ -360,152 +128,36 @@ impl HashMLDSAParams for HashMLDSA44_with_SHA512Params { impl HashMLDSAParams for HashMLDSA65_with_SHA512Params { type MLDSA = MLDSA65Params; type PreHash = SHA512; - const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; + const ALG_NAME: &'static str = HASH_ML_DSA_65_with_SHA512_NAME; } impl HashMLDSAParams for HashMLDSA87_with_SHA512Params { type MLDSA = MLDSA87Params; type PreHash = SHA512; - const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; + const ALG_NAME: &'static str = HASH_ML_DSA_87_with_SHA512_NAME; } #[cfg(test)] mod tests { use super::*; - use crate::mldsa::d; - - /// FIPS 204, Table 1, transcribed column by column: the eight values each parameter set - /// assigns. `(tau, lambda, gamma1, gamma2, k, l, eta, omega)`. - const TABLE_1: [(i32, i32, i32, i32, usize, usize, usize, i32); 3] = [ - (39, 128, 131072, (q - 1) / 88, 4, 4, 2, 80), - (49, 192, 524288, (q - 1) / 32, 6, 5, 4, 55), - (60, 256, 524288, (q - 1) / 32, 8, 7, 2, 75), - ]; - - /// FIPS 204, Table 2, transcribed row by row: `(private key, public key, signature)` in bytes. - const TABLE_2: [(usize, usize, usize); 3] = - [(2560, 1312, 2420), (4032, 1952, 3309), (4896, 2592, 4627)]; - /// FIPS 204, Table 1 also tabulates 𝛽, which it labels "𝛽 = 𝜏 ⋅ 𝜂". - const TABLE_1_BETA: [i32; 3] = [78, 196, 120]; - - fn check_table_1(i: usize) { - let (tau, lambda, gamma1, gamma2, k, l, eta, omega) = TABLE_1[i]; - assert_eq!(P::tau, tau, "{}: 𝜏", P::ALG_NAME); - assert_eq!(P::lambda, lambda, "{}: 𝜆", P::ALG_NAME); - assert_eq!(P::gamma1, gamma1, "{}: 𝛾1", P::ALG_NAME); - assert_eq!(P::gamma2, gamma2, "{}: 𝛾2", P::ALG_NAME); - assert_eq!(P::k, k, "{}: 𝑘", P::ALG_NAME); - assert_eq!(P::l, l, "{}: ℓ", P::ALG_NAME); - assert_eq!(P::eta, eta, "{}: 𝜂", P::ALG_NAME); - assert_eq!(P::omega, omega, "{}: 𝜔", P::ALG_NAME); - assert_eq!(P::beta, TABLE_1_BETA[i], "{}: 𝛽 = 𝜏 ⋅ 𝜂", P::ALG_NAME); - } - - fn check_table_2(i: usize) { - let (sk_len, pk_len, sig_len) = TABLE_2[i]; - assert_eq!(P::SK_LEN, sk_len, "{}: private key size", P::ALG_NAME); - assert_eq!(P::PK_LEN, pk_len, "{}: public key size", P::ALG_NAME); - assert_eq!(P::SIG_LEN, sig_len, "{}: signature size", P::ALG_NAME); - } - - /// Each of the three sizes of Table 2 also has a formula in FIPS 204, and the two must agree. - /// Table 2 is what is written down above; this is what re-derives it. - fn check_table_2_formulas() { - // Algorithm 22 (pkEncode): 𝑝𝑘 ∈ 𝔹^(32+32𝑘(bitlen (𝑞−1)−𝑑)). - let pk_len = 32 + 32 * P::k * (bitlen((q - 1) as u32) - d as usize); - assert_eq!(P::PK_LEN, pk_len, "{}: Algorithm 22 output size", P::ALG_NAME); - - // Algorithm 24 (skEncode): 𝑠𝑘 ∈ 𝔹^(32+32+64+32⋅((𝑘+ℓ)⋅bitlen (2𝜂)+𝑑𝑘)). - let sk_len = - 32 + 32 + 64 + 32 * ((P::k + P::l) * bitlen(2 * P::eta as u32) + d as usize * P::k); - assert_eq!(P::SK_LEN, sk_len, "{}: Algorithm 24 output size", P::ALG_NAME); - - // Algorithm 26 (sigEncode): 𝜎 ∈ 𝔹^(𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘). - let sig_len = P::lambda as usize / 4 - + P::l * 32 * (1 + bitlen(P::gamma1 as u32 - 1)) - + P::omega as usize - + P::k; - assert_eq!(P::SIG_LEN, sig_len, "{}: Algorithm 26 output size", P::ALG_NAME); - } - - /// The associated types must be exactly as long as the consts that describe them; they are - /// written out by hand per parameter set, so this guards against a typo in one of them. - fn check_associated_type_sizes() { - assert_eq!( - size_of::(), - P::C_TILDE_LEN, - "{}: SigCTilde vs C_TILDE_LEN", - P::ALG_NAME - ); - assert_eq!( - size_of::(), - P::POLY_Z_PACKED_LEN, - "{}: PolyZPacked vs POLY_Z_PACKED_LEN", - P::ALG_NAME - ); - assert_eq!( - size_of::(), - P::POLY_W1_PACKED_LEN, - "{}: PolyW1Packed vs POLY_W1_PACKED_LEN", - P::ALG_NAME - ); + /// The vector types are written out by hand per parameter set, so this guards against a typo + /// in one of them. The byte-buffer types are checked the same way in `bouncycastle-mldsa-common`. + fn check_vector_lengths() { assert_eq!(::LEN, P::k, "{}: VecK vs 𝑘", P::ALG_NAME); assert_eq!(::LEN, P::l, "{}: VecL vs ℓ", P::ALG_NAME); } #[test] - fn test_parameter_sets_match_fips204_table_1() { - check_table_1::(0); - check_table_1::(1); - check_table_1::(2); - } - - #[test] - fn test_sizes_match_fips204_table_2() { - check_table_2::(0); - check_table_2::(1); - check_table_2::(2); - } - - #[test] - fn test_table_2_sizes_agree_with_the_encoding_formulas() { - check_table_2_formulas::(); - check_table_2_formulas::(); - check_table_2_formulas::(); - } - - #[test] - fn test_associated_types_are_the_length_their_consts_claim() { - check_associated_type_sizes::(); - check_associated_type_sizes::(); - check_associated_type_sizes::(); - } - - #[test] - fn test_bitlen_matches_its_definition() { - // FIPS 204 Section 2.3 defines bitlen 𝑥 as the length of the binary expansion of 𝑥. - assert_eq!(bitlen(0), 0); - assert_eq!(bitlen(1), 1); - assert_eq!(bitlen(2), 2); - assert_eq!(bitlen(3), 2); - assert_eq!(bitlen(4), 3); - // The two arguments the derivations above actually use, plus bitlen(𝑞 − 1) = 23. - assert_eq!(bitlen((1 << 17) - 1), 17); - assert_eq!(bitlen((1 << 19) - 1), 19); - assert_eq!(bitlen((q - 1) as u32), 23); + fn test_vector_types_are_the_length_their_consts_claim() { + check_vector_lengths::(); + check_vector_lengths::(); + check_vector_lengths::(); } #[test] - fn test_gamma_dispatch_constants_cover_every_parameter_set() { - // The packing routines dispatch on these; a parameter set whose 𝛾 is neither value would - // fall through to a panic at runtime rather than fail to compile, so pin them here. - for gamma1 in [MLDSA44Params::gamma1, MLDSA65Params::gamma1, MLDSA87Params::gamma1] { - assert!(gamma1 == GAMMA1_2_POW_17 || gamma1 == GAMMA1_2_POW_19); - } - for gamma2 in [MLDSA44Params::gamma2, MLDSA65Params::gamma2, MLDSA87Params::gamma2] { - assert!(gamma2 == GAMMA2_Q_MINUS_1_OVER_88 || gamma2 == GAMMA2_Q_MINUS_1_OVER_32); - } - assert_ne!(GAMMA1_2_POW_17, GAMMA1_2_POW_19); - assert_ne!(GAMMA2_Q_MINUS_1_OVER_88, GAMMA2_Q_MINUS_1_OVER_32); + fn test_sk_len_is_the_fips_encoding() { + assert_eq!(MLDSA44Params::SK_LEN, MLDSA44Params::FULL_SK_LEN); + assert_eq!(MLDSA65Params::SK_LEN, MLDSA65Params::FULL_SK_LEN); + assert_eq!(MLDSA87Params::SK_LEN, MLDSA87Params::FULL_SK_LEN); } } diff --git a/crypto/mldsa/src/polynomial.rs b/crypto/mldsa/src/polynomial.rs deleted file mode 100644 index d67e4842a..000000000 --- a/crypto/mldsa/src/polynomial.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! Represents a polynomial over the ML-DSA ring. - -use crate::aux_functions::{ - ZETAS, conditional_add_q, high_bits, low_bits, make_hint, montgomery_reduce, reduce32, -}; -use crate::mldsa::{N, d, q}; -use crate::params::{GAMMA2_Q_MINUS_1_OVER_32, GAMMA2_Q_MINUS_1_OVER_88, MLDSAParams}; -use bouncycastle_utils::secret::ZeroizablePrimitive; -use core::ops::{Index, IndexMut}; - -/// A polynomial over the ML-DSA ring. -/// -/// # 🚨 Security Considerations 🚨 -/// Polynomials themselves are not inherently secret since sometimes they are part of public keys -/// and sometimes private keys. -/// It is the responsibility of the caller to wrap sensitive instances in `Secret`. -/// -/// Public only because it appears in [`crate::VectorTrait`]'s signatures; its fields and -/// operations are crate-private, so from outside it is an opaque handle. -#[derive(Clone, Copy)] -pub struct Polynomial { - pub(crate) coeffs: [i32; N], -} - -/// Convenience function to avoid ".0" all over the place. -impl Index for Polynomial { - type Output = i32; - - fn index(&self, index: usize) -> &Self::Output { - &self.coeffs[index] - } -} -/// Convenience function to avoid ".0" all over the place. -impl IndexMut for Polynomial { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.coeffs[index] - } -} - -impl Polynomial { - /// Create a new polynomial with all coefficients set to zero. - pub(crate) const fn new() -> Self { - Self { coeffs: [0i32; N] } - } - - pub(crate) fn conditional_add_q(&mut self) { - for x in self.coeffs.iter_mut() { - *x = conditional_add_q(*x); - } - } - - pub(crate) fn reduce(&mut self) { - for i in 0..N { - self[i] = montgomery_reduce(self[i] as i64); - } - } - - /// Reduces every coefficient to |𝑐| < 𝑞 without changing its residue. See [`reduce32`] for when - /// this is required; note that [`Self::reduce`] is a *Montgomery* reduction and is not a substitute. - pub(crate) fn reduce32(&mut self) { - for x in self.coeffs.iter_mut() { - *x = reduce32(*x); - } - } - - /// Algorithm 44 AddNTT(𝑎, 𝑏)̂ - /// Computes the sum a + 𝑏 of two elements 𝑎, 𝑏 ∈ 𝑇𝑞. - /// Note: result could be up to 2q. - pub(crate) fn add_ntt(&mut self, w: &Self) { - for i in 0..N { - self[i] += w[i]; - } - } - - pub(crate) fn sub(&mut self, w: &Self) { - for i in 0..N { - self[i] -= w[i]; - } - } - - pub(crate) fn high_bits(&self) -> Self { - let mut w = Self::new(); - for i in 0..N { - w[i] = high_bits::

(self[i]); - } - - w - } - - pub(crate) fn low_bits(&self) -> Self { - let mut w = Self::new(); - for i in 0..N { - w[i] = low_bits::

(self[i]); - } - - w - } - - /// Tests whether any coefficient has absolute value at least `bound`. - /// - /// `bound` is a runtime argument rather than a const generic because every call site passes a - /// value derived from the parameter set (𝛾1 − 𝛽, 𝛾2 − 𝛽, or 𝛾2), and an associated const of a - /// type parameter cannot be used as a const generic argument. It is still a constant after - /// monomorphization, so this costs nothing. - pub(crate) fn check_norm(&self, bound: i32) -> bool { - // It is acceptable that this function is not constant-time (returns true early) - // The reason being because it is used in a rejection loop. - // That is, the early quit here leads to rejection, dropping the secret values and - // continuing to the top of the rejection loop with generating new secret values, - // or failing the signature validation. - // So the i32 that we just checked in a non-constant-time manner is about to get thrown away. - - // Note: this formulation of the check_norm function usually requires this bounds check - // if bound > (q - 1) / 8 { - // return true; - // } - // but since every caller passes a parameter-set constant, a debug_assert is performed - // instead to make sure the value is what we expect. - debug_assert!(bound <= (q - 1) / 8); - - let mut t: i32; - for x in self.coeffs.iter() { - t = *x >> 31; - t = *x - (t & (2 * *x)); - - if t >= bound { - return true; - } - } - false - } - - /// Multiplies every coefficient by 2^𝑑. - /// - /// 𝑑 is 13 for all three parameter sets (FIPS 204, Table 1), so it is read from the global - /// constant rather than being passed in. - pub(crate) fn shift_left_d(&mut self) { - for x in self.coeffs.iter_mut() { - *x <<= d; - } - } - - /// Creates the hint vector, and also returns its hamming weight (i.e. the number of 1's). - pub(crate) fn make_hint(&self, r: &Self) -> (Self, i32) { - let mut out = Polynomial::new(); - let mut count = 0i32; - for i in 0..N { - let x = make_hint::

(self[i], r[i]); - out[i] = x; - - // mutants note: this chains up to hint_hamming_weight > OMEGA and there is no test KAT that triggers this branch - count += x; - } - - (out, count) - } - - /// SimpleBitPack(𝐰1[𝑖], (𝑞 − 1)/(2𝛾2) − 1), the per-coordinate body of - /// FIPS 204, Algorithm 28 (w1Encode), line 3. - pub(crate) fn w1_encode(&self) -> P::PolyW1Packed { - let mut out = ::ZEROED; - let r = out.as_mut(); - - match P::gamma2 { - // ML-DSA-44: (𝑞 − 1)/(2𝛾2) − 1 = 43, so four 6-bit coefficients pack into three bytes. - GAMMA2_Q_MINUS_1_OVER_88 => { - for i in 0..N / 4 { - r[3 * i] = ((self[4 * i]) as u8) | ((self[4 * i + 1] << 6) as u8); - r[3 * i + 1] = ((self[4 * i + 1] >> 2) as u8) | ((self[4 * i + 2] << 4) as u8); - r[3 * i + 2] = ((self[4 * i + 2] >> 4) as u8) | ((self[4 * i + 3] << 2) as u8); - } - } - // ML-DSA-65 and ML-DSA-87 share this 𝛾2: (𝑞 − 1)/(2𝛾2) − 1 = 15, so two 4-bit - // coefficients pack into one byte. - GAMMA2_Q_MINUS_1_OVER_32 => { - for i in 0..N / 2 { - r[i] = ((self[2 * i]) | (self[2 * i + 1] << 4)) as u8; - } - } - _ => { - unreachable!() - } - } - - out - } - - /// Algorithm 41 NTT(𝑤) - /// Computes the NTT. - /// Input: Polynomial 𝑤(𝑋) = Σ_{j=0}^{255} 𝑤𝑗𝑋𝑗 ∈ 𝑅𝑞. - /// Output: 𝑤_hat = (𝑤_hat\[0], ..., 𝑤_hat\[255]) ∈ 𝑇𝑞. - /// - /// Note: by convention, variables holding the output of the NTT function should be named "_hat" - /// to indicate that they are in the NTT domain (sometimes called the frequency domain), not the natural domain. - /// Usage of the rust type system to enforce this is arguably unnecessary, since that's what the NIST - /// test vectors are for. - /// - /// Lazy reduction: the butterfly omits an explicit reduction modulo `q` - /// This is safe only because ‖input‖∞ ≤ q-1 (i.e. intermediates stay below ~5q - /// (well within i32) and the input of montgomery_reduce input stays below q·2^{31}) and - /// the final result is reduced downstream. - pub(crate) fn ntt(&mut self) { - let mut m: usize = 0; - let mut len: usize = 128; - - while len >= 1 { - let mut start: usize = 0; - while start < N { - m += 1; - let z: i32 = ZETAS[m]; - - for j in start..start + len { - let t = montgomery_reduce(z as i64 * self[j + len] as i64); - // '% q' not strictly needed cause it gets reduced at some point later. - // Removing it gave +5% in benchmarking - self[j + len] = self[j] - t; - self[j] = self[j] + t; // '% q' not strictly needed - } - start = start + 2 * len; - } - len >>= 1; - } - } - - /// Algorithm 42 NTT−1(𝑤_hat) - /// Computes the inverse of the NTT. - /// Input: 𝑤_hat = (𝑤_hat[0], … , 𝑤_hat[255]) ∈ 𝑇𝑞. - /// Output: Polynomial 𝑤(𝑋) = Σ_{j=0}^{255} 𝑤𝑗𝑋𝑗 ∈ 𝑅𝑞 - pub(crate) fn inv_ntt(&mut self) { - // A core input condition on the InverseNTT is that every input - // coefficient must satisfy |𝑤| < 𝑞 for the sums to stay within an i32. - // The reason this is required is because Algorithm 42's butterflies (steps 13-14) - // run 8 levels without reducing, leading to an i32 overflow if the input was out-of-range. - // Callers must pass them through `reduce32` first. - // Note that only a crafted input, such as (Wycheproof mldsa_87_verify tcId 240/241) will trigger this. - debug_assert!( - self.coeffs.iter().all(|c| c.abs() < q), - "inv_ntt input coefficient not reduced below q; call reduce32() first" - ); - - let mut m: usize = N; - let mut len: usize = 1; - - while len < N { - let mut start: usize = 0; - while start < N { - m -= 1; - let z = (-1) * ZETAS[m]; - - // j = start; - // while j < start + len { - for j in start..start + len { - // 𝑡 ← 𝑤𝑗 - let t: i32 = self[j]; - - // 𝑤𝑗 ← (𝑡 + 𝑤𝑗+𝑙𝑒𝑛) mod 𝑞 - self[j] = t + self[j + len]; - - // 𝑤𝑗+𝑙𝑒𝑛 ← (𝑡 − 𝑤𝑗+𝑙𝑒𝑛) mod 𝑞 - self[j + len] = t - self[j + len]; - - // 𝑤𝑗+𝑙𝑒𝑛 ← (𝑧 ⋅ 𝑤𝑗+𝑙𝑒𝑛) mod 𝑞 - self[j + len] = montgomery_reduce(z as i64 * self[j + len] as i64); - } - start = start + 2 * len; - // could be optimized to save the multiply-by-two since j finishes as `start + len`. - // That said 2* is just << 1, which is basically free. - } - len <<= 1; - } - - // Final 1/256 normalization, in Montgomery form to match the montgomery_reduce - // bookkeeping used by every butterfly above (each contributes a 2^-32 factor). - // Note: f != 256^-1 mod q = 8347681. That value is only correct - // applied as a plain multiply (FIPS 204 Alg 42: w_j <- f * w_j mod q). - // Here we apply f via montgomery_reduce(f * w_j), so the constant is the - // Montgomery-domain form: f = mont^2 / 256 mod q = 41978, mont = 2^32 mod q. - // Do NOT substitute 8347681, as it is invalid through montgomery_reduce. - const f: i64 = 41978; - for j in 0..N { - // equiv. to the global constant N - self[j] = montgomery_reduce(f * self[j] as i64); - } - } -} diff --git a/crypto/mldsa/tests/hash_mldsa_tests.rs b/crypto/mldsa/tests/hash_mldsa_tests.rs index 006b90172..97fd2521b 100644 --- a/crypto/mldsa/tests/hash_mldsa_tests.rs +++ b/crypto/mldsa/tests/hash_mldsa_tests.rs @@ -4,15 +4,15 @@ mod hash_mldsa_tests { use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Hash, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, + HashOneShot, PHSignatureVerifier, PHSigner, SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::signature::TestFrameworkSignature; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ - HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA256, - HashMLDSA65_with_SHA512, HashMLDSA87_with_SHA256, HashMLDSA87_with_SHA512, MLDSA44, - MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, - MLDSA87PrivateKey, MLDSA87PublicKey, MLDSATrait, + HashMLDSA44_with_SHA256, HashMLDSA44_with_SHA512, HashMLDSA65_with_SHA512, + HashMLDSA87_with_SHA512, MLDSA44, MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65, + MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87, MLDSA87PrivateKey, MLDSA87PublicKey, + MLDSATrait, }; use bouncycastle_mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; use bouncycastle_mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; @@ -28,10 +28,8 @@ mod hash_mldsa_tests { tf.test_signature::(HashMLDSA65_with_SHA512::keygen, false); tf.test_signature::(HashMLDSA87_with_SHA512::keygen, false); - // Test HashML-DSA-SHA256 as a ph signature alg + // Test HashML-DSA-44 with SHA256, the one SHA-256 pairing, as a ph signature alg tf.test_ph_signature::(HashMLDSA44_with_SHA256::keygen, false); - tf.test_ph_signature::(HashMLDSA65_with_SHA256::keygen, false); - tf.test_ph_signature::(HashMLDSA87_with_SHA256::keygen, false); // Test HashML-DSA-SHA512 as a ph signature alg tf.test_ph_signature::(HashMLDSA44_with_SHA512::keygen, false); @@ -137,14 +135,14 @@ mod hash_mldsa_tests { let msg = b"The quick brown fox jumped over the lazy dog"; let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); - let ph: [u8; 32] = SHA256::default().hash(msg)[..32].try_into().unwrap(); + let ph: [u8; 32] = SHA256::hash(msg)[..32].try_into().unwrap(); let sig_val = HashMLDSA44_with_SHA256::sign_ph(&sk, &ph, None).unwrap(); HashMLDSA44_with_SHA256::verify_ph(&pk, &ph, None, &sig_val).unwrap(); HashMLDSA44_with_SHA256::verify(&pk, msg, None, &sig_val).unwrap(); // sign_ph_out let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); - let ph: [u8; 32] = SHA256::default().hash(msg)[..32].try_into().unwrap(); + let ph: [u8; 32] = SHA256::hash(msg)[..32].try_into().unwrap(); let mut sig_val = [0u8; MLDSA44_SIG_LEN]; let bytes_written = HashMLDSA44_with_SHA256::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap(); @@ -178,7 +176,7 @@ mod hash_mldsa_tests { // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, None, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); @@ -190,7 +188,7 @@ mod hash_mldsa_tests { // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA65_with_SHA512::sign_ph_deterministic(&sk, None, None, &ph, rnd).unwrap(); assert_eq!(&sig, expected_sig.as_slice()); @@ -202,7 +200,7 @@ mod hash_mldsa_tests { // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA87_with_SHA512::sign_ph_deterministic(&sk, None, None, &ph, rnd).unwrap(); @@ -216,7 +214,7 @@ mod hash_mldsa_tests { // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA44_with_SHA512::sign_ph_deterministic(&sk, None, Some(ctx), &ph, rnd).unwrap(); @@ -244,7 +242,7 @@ mod hash_mldsa_tests { // BEGIN expected values let (expected_pk, expected_sk) = HashMLDSA44_with_SHA512::keygen_from_seed(&seed).unwrap(); - let expected_ph: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); + let expected_ph: [u8; 64] = SHA512::hash(msg).try_into().unwrap(); let mut expected_sig = [0u8; MLDSA44_SIG_LEN]; let bytes_written = HashMLDSA44_with_SHA512::sign_ph_deterministic_out( &expected_sk, None, ctx, &expected_ph, rnd, &mut expected_sig, @@ -304,13 +302,13 @@ mod hash_mldsa_tests { // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA44_with_SHA512::sign_ph_with_expanded_key(&sk_expanded, &ph, None).unwrap(); HashMLDSA44_with_SHA512::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); // and sign_ph_with_expanded_key_out - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let mut sig = [0u8; MLDSA44_SIG_LEN]; let bytes_written = HashMLDSA44_with_SHA512::sign_ph_with_expanded_key_out( &sk_expanded, &ph, None, &mut sig, @@ -324,14 +322,14 @@ mod hash_mldsa_tests { let (pk, sk) = MLDSA65::keygen().unwrap(); let sk_expanded = MLDSA65PrivateKeyExpanded::from(&sk); - let sig = HashMLDSA65_with_SHA256::sign_with_expanded_key(&sk_expanded, msg, None).unwrap(); + let sig = HashMLDSA65_with_SHA512::sign_with_expanded_key(&sk_expanded, msg, None).unwrap(); let pk_expanded = MLDSA65PublicKeyExpanded::from(&pk); - HashMLDSA65_with_SHA256::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); + HashMLDSA65_with_SHA512::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA65_with_SHA512::sign_ph_with_expanded_key(&sk_expanded, &ph, None).unwrap(); HashMLDSA65_with_SHA512::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); @@ -341,14 +339,14 @@ mod hash_mldsa_tests { let (pk, sk) = MLDSA87::keygen().unwrap(); let sk_expanded = MLDSA87PrivateKeyExpanded::from(&sk); - let sig = HashMLDSA87_with_SHA256::sign_with_expanded_key(&sk_expanded, msg, None).unwrap(); + let sig = HashMLDSA87_with_SHA512::sign_with_expanded_key(&sk_expanded, msg, None).unwrap(); let pk_expanded = MLDSA87PublicKeyExpanded::from(&pk); - HashMLDSA87_with_SHA256::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); + HashMLDSA87_with_SHA512::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); // since I'm not exposing a sign_deterministic() that does the ph computation internally and takes an rnd, // we have to compute the ph manually here - let ph: [u8; 64] = SHA512::new().hash(msg)[..64].try_into().unwrap(); + let ph: [u8; 64] = SHA512::hash(msg)[..64].try_into().unwrap(); let sig = HashMLDSA87_with_SHA512::sign_ph_with_expanded_key(&sk_expanded, &ph, None).unwrap(); HashMLDSA87_with_SHA512::verify_with_expanded_key(&pk_expanded, msg, None, &sig).unwrap(); @@ -375,24 +373,19 @@ mod hash_mldsa_tests { // these per algorithm. Pinned here so a wrong wiring of the blanket impl, or a typo in a // pairing, is a test failure rather than a mislabelled algorithm. assert_eq!(HashMLDSA44_with_SHA256::ALG_NAME, "HashML-DSA-44_with_SHA256"); - assert_eq!(HashMLDSA65_with_SHA256::ALG_NAME, "HashML-DSA-65_with_SHA256"); - assert_eq!(HashMLDSA87_with_SHA256::ALG_NAME, "HashML-DSA-87_with_SHA256"); assert_eq!(HashMLDSA44_with_SHA512::ALG_NAME, "HashML-DSA-44_with_SHA512"); assert_eq!(HashMLDSA65_with_SHA512::ALG_NAME, "HashML-DSA-65_with_SHA512"); assert_eq!(HashMLDSA87_with_SHA512::ALG_NAME, "HashML-DSA-87_with_SHA512"); // The claimed strength is derived as the weaker of the two components, so these pin the - // derivation rather than six hand-written values. SHA-256 caps every pairing it appears in - // at 128 bits; with SHA-512 the ML-DSA parameter set is what binds. + // derivation rather than six hand-written values. SHA-256 and ML-DSA-44 both give 128 bits; with SHA-512 the ML-DSA parameter set is what binds. assert_eq!(HashMLDSA44_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HashMLDSA65_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HashMLDSA87_with_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HashMLDSA44_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HashMLDSA65_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); assert_eq!(HashMLDSA87_with_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // NIST's Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 }, - // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The three SHA-256 pairings carry no OID in + // -65- { sigAlgs 33 }, -87- { sigAlgs 34 }. The SHA-256 pairing carries no OID in // this implementation, which is why `AlgorithmOID` is still written out per alias rather // than derived from the pairing like the name and strength above. assert_eq!(HashMLDSA44_with_SHA512::OID, &[2, 16, 840, 1, 101, 3, 4, 3, 32]); @@ -413,6 +406,34 @@ mod hash_mldsa_tests { } } + /// The expanded-key pre-hash path reports what its `_out` form reports: a 256-byte ctx is + /// refused on both, rather than the one returning an all-zero signature as Ok. + #[test] + fn expanded_key_sign_ph_reports_errors() { + use bouncycastle_mldsa::MLDSA44PrivateKeyExpanded; + let (_pk, sk) = MLDSA44::keygen().unwrap(); + let sk_expanded = MLDSA44PrivateKeyExpanded::from(&sk); + let ph = [0x11u8; 32]; + let ctx = [0u8; 256]; + assert!(HashMLDSA44_with_SHA256::sign_ph(&sk, &ph, Some(&ctx)).is_err()); + assert!( + HashMLDSA44_with_SHA256::sign_ph_with_expanded_key(&sk_expanded, &ph, Some(&ctx)) + .is_err() + ); + } + + /// Finishing a sign-initialised context as a verifier is an error, as it is on MLDSA, not a + /// panic. + #[test] + fn verify_final_on_a_sign_initialised_context_is_an_error() { + let (_pk, sk) = MLDSA44::keygen().unwrap(); + let ctx = HashMLDSA44_with_SHA256::do_sign_init(&sk, None).unwrap(); + assert!(matches!( + ctx.do_verify_final(&[0u8; MLDSA44_SIG_LEN]), + Err(SignatureError::GenericError(_)) + )); + } + #[test] fn prehash_lengths_match_the_hash_functions() { // `PH_LEN` is still a const generic on `HashMLDSA` -- `PHSigner` takes it as one -- but @@ -420,18 +441,18 @@ mod hash_mldsa_tests { // `HashAlgParams::OUTPUT_LEN`. So there is only one value, and nothing in the chain can // disagree with itself. What is left to check is whether that value matches the digest // the hash actually produces, which is what this test does. - assert_eq!(SHA256::new().hash(b"").len(), 32); - assert_eq!(SHA512::new().hash(b"").len(), 64); + assert_eq!(SHA256::hash(b"").len(), 32); + assert_eq!(SHA512::hash(b"").len(), 64); // A signature is produced from a `ph` of exactly that length, so a mismatch would fail // here rather than silently truncating. let msg = b"The quick brown fox"; - let ph256: [u8; 32] = SHA256::new().hash(msg).try_into().unwrap(); - let ph512: [u8; 64] = SHA512::new().hash(msg).try_into().unwrap(); + let ph256: [u8; 32] = SHA256::hash(msg).try_into().unwrap(); + let ph512: [u8; 64] = SHA512::hash(msg).try_into().unwrap(); - let (pk, sk) = HashMLDSA65_with_SHA256::keygen().unwrap(); - let sig = HashMLDSA65_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); - HashMLDSA65_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); + let (pk, sk) = HashMLDSA44_with_SHA256::keygen().unwrap(); + let sig = HashMLDSA44_with_SHA256::sign_ph(&sk, &ph256, None).unwrap(); + HashMLDSA44_with_SHA256::verify_ph(&pk, &ph256, None, &sig).unwrap(); let (pk, sk) = HashMLDSA65_with_SHA512::keygen().unwrap(); let sig = HashMLDSA65_with_SHA512::sign_ph(&sk, &ph512, None).unwrap(); diff --git a/crypto/mldsa/tests/mldsa_bc-test-data.rs b/crypto/mldsa/tests/mldsa_bc-test-data.rs index 90ae1da73..20d791e94 100644 --- a/crypto/mldsa/tests/mldsa_bc-test-data.rs +++ b/crypto/mldsa/tests/mldsa_bc-test-data.rs @@ -12,7 +12,7 @@ use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Hash, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, XOF, XOFSqueezer, + Hash, HashOneShot, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, XOF, XOFSqueezer, }; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_hex as hex; @@ -270,7 +270,7 @@ impl SigGenTestCase { // None, // sk.tr(), // ).unwrap(); - let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + let mut mb = BustedMuBuilder::do_init(sk.tr()).unwrap(); mb.do_update(&hex::decode(&self.message).unwrap()); let mu = mb.do_final(); @@ -295,7 +295,7 @@ impl SigGenTestCase { // None, // sk.tr(), // ).unwrap(); - let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + let mut mb = BustedMuBuilder::do_init(sk.tr()).unwrap(); mb.do_update(&hex::decode(&self.message).unwrap()); let mu = mb.do_final(); @@ -312,7 +312,7 @@ impl SigGenTestCase { // None, // sk.tr(), // ).unwrap(); - let mut mb = BustedMuBuilder::do_init(&sk.tr()).unwrap(); + let mut mb = BustedMuBuilder::do_init(sk.tr()).unwrap(); mb.do_update(&hex::decode(&self.message).unwrap()); let mu = mb.do_final(); @@ -673,8 +673,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // It only tests SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -692,14 +691,16 @@ impl MldsaRspTestCase { HashMLDSA44_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) - .expect(&format!( - "paramSet: {}, is_hash: {}, count: {}", - parameter_set, IS_HASH_MLDSA, self.count - )); + .unwrap_or_else(|_| { + panic!( + "paramSet: {}, is_hash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + ) + }); } else { // note: The code only exposes a sign_mu_deterministic(), but not sign_deterministic() // so mu needs to be computed manually @@ -750,8 +751,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // it only tests SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -769,14 +769,16 @@ impl MldsaRspTestCase { HashMLDSA65_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) - .expect(&format!( - "paramSet: {}, isHash: {}, count: {}", - parameter_set, IS_HASH_MLDSA, self.count - )); + .unwrap_or_else(|_| { + panic!( + "paramSet: {}, isHash: {}, count: {}", + parameter_set, IS_HASH_MLDSA, self.count + ) + }); } else { // note: The code only exposes a sign_mu_deterministic(), but not sign_deterministic() // so mu needs to be computed manually @@ -821,8 +823,7 @@ impl MldsaRspTestCase { if IS_HASH_MLDSA { // it only tests SHA512 - let ph: [u8; 64] = SHA512::new() - .hash(&hex::decode(&self.msg).unwrap()) + let ph: [u8; 64] = SHA512::hash(&hex::decode(&self.msg).unwrap()) .as_slice() .try_into() .unwrap(); @@ -840,7 +841,7 @@ impl MldsaRspTestCase { HashMLDSA87_with_SHA512::verify( &pk, - &*hex::decode(&self.msg).unwrap(), + &hex::decode(&self.msg).unwrap(), Some(&*hex::decode(&self.context).unwrap()), &sig, ) @@ -881,7 +882,7 @@ impl BustedMuBuilder { /// Algorithm 7 /// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀′, 64) pub fn compute_mu(msg: &[u8], tr: &[u8; 64]) -> Result<[u8; 64], SignatureError> { - let mut mu_builder = Self::do_init(&tr)?; + let mut mu_builder = Self::do_init(tr)?; mu_builder.do_update(msg); let mu = mu_builder.do_final(); diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index d9350bc92..2d4028751 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -1,3 +1,52 @@ +struct Kat { + _parameter_set: &'static str, + deterministic: bool, + sk: &'static str, + message: &'static str, + ctx: &'static str, + signature: &'static str, +} + +// generated by hand against bc-java +// This is almost tgId=1, tcId=1 from +// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// and the signature value is run against bc-java +const MLDSA44_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-44", + deterministic: true, + sk: "1B8664E94372A180CAD8C3D93F57FC16F452EF524D2DACEF0A50C0DDED23FBC052EA7330489B17CA2FE4234FB0F9CCA63B6185963852374EE10116AB38AAE2A922693A91188E5F5057AB18485D98B87EB21CCFDF5E9F4B5C5787F5043988EF6790A47D70515D93C48C28A855111D73F245898C877BA4E6A646057579A111F59C1BC629D82652D388901B2961C2C270A3B68054329299362691889122320418918820132D03C28D8C144200B825E0A88198A210031328D4884D1126690AC38D1B1510089051C8142D02B390C8440564040800290CA4480EC4424D1A960409A58010472D42346911A4401481884830411B122589808981222E63164D11C165DB064C0A430412374AD19271400846409271001748C9124894064952C06D1C316AC0484C19B69103A70950100208460458084C49224EE4484559048D514265D99220E2A41162C231D01620C31800D4A45008390A22A67018B36882049103440642364E09B3484A8448DA382E5CA810E01810182466D446681B489102384D8B18510397259A400A00436992C40C129285C9C05060486450988884386220808D919268233401A0B8405B0662E4C42119B74099B265E2400860462AA0B40D1A3248CC06318304489BC6114A484503A4010CB68D11860111984CA21410CC2666E0A02C94B01183182C904805A4320860B29122B66142886DC9222D24C171A42211C9A08814854023034582A25183B6711BC88DD0480292387220B041238790C8081203C0099436918BA2244A422959042D48260194322860928C00A844D21264A0A6240C9870C4A6042134209CA4099B128A1B3249249651A022281B44640AB36C029969C4C2710B396D0B964509B6209CC87150142A80B620CB28121A93684B382E01350A88160520983181322104426E83A6481014495C304DCB1826000390A4386103314E02B3008A14440AC78111C5704018021426650BC68C53C4708098481B9044C242680C124E2049284A3671C9B08911A77049982403C0300A83654B426D99345110B82C021092999429A0124E98184D50242061908981282661B28108A6651B22450A342C9C984C4B382DC4086292188D41C46CE2C491D902419C1231C2120CA040440CB12D22062DA3901102286C580066801228C9083101872554244902C62CCCB28502168CD8422E24416D1C20924A0660892826D2329092922C03451258B2099B2481183804243390C2B44C5AB485C814610A188503852988409203A580C9C0E30E0791B8E694262282FDD2FA69AD5CA94D015922BB63BC690FD546BC1ACFA0E79B02E7D1C1E92F419DA68C30CB12C736E216FC72C394883FBF40CFF538A43D08CE8A390DC7B78542199A54EEFE8FCD49225596FC97B27CF8BDDB57209E48FB628909CF889D38855CFE04AB04486378D17DC5780E81168B5E1DF462FC2D43860031B59D80AF95AFF0278210B9D96023E8A88FF0E4403D710895C830049AF4B8BCB57C5972D067A579C8C0021E5C870E1EE10629518191E283E796B58700061BA656B9385F54AA6917EEF52E60669B7B4F4FD36063DFB7822CDB4F19D9DA4E2F41E4BAE8C5274E7A9EBDB80BC9555687827A10224D9044DB2E45A89F582A716B4D3B131AF7DBCEA95DB88D84C73281D4CA7961F752FFA520BA3FDCC245331849768CE4D4B1FC330DCE474F31558764395F97FFC847BE9147DC22A3021F666A4327FF7FBEF007683A84C9FD99F5088703784AFFB3D86F216405BB9218DF05BB4CFA100145C9358EDE1A0F4C69AD23F7BA56FE8755F711067D7D43CF9FE0A93D642383DB3A0086CF7FCCA3FC59379D416DACBCEB7B49DFF554748EE36DFBCC07D15270A0388DA88360E6CA476DCFF56DE0B7B359609E58A728EE4B3ABCC296E42CB76034C7E13BD0B5208EE8CDF451F6CCCE87E9A21C3DCB28B8B9805CA7115065757B3C6D2A39B28B8610A3F30092C2E9F43A744372FFDF6B5F69DA6589860C520882FBEC130B28E1788572252A5995EB00B4B008931220B27488FEF5D04BC28C1FFA9DB5D0F1ED49CC4D775EB3DEC26ACD92BBB8A33A195A0C2F5AB7386BEA3AC9A9EE90C762E005DEF59348B0B1A6A373CCCD0BCD4125E0602EAA2BE057603020C2DC849513333C019A22210B55BCDAD54279BFEC5F998C1D14F7590B31B403564ED2E54AD007EDA4F2DE06AB0315E4C3384BE2C5392BB9FF1D41C770A767E4947FF7B609CFEAFF54DC672E866BDB861B81CAD3CC646DC325A5E1CD37C45FD0E29D6C2117CDF4C8B3082249C4F8D3E736A1CC0628D5FCB322711D78B881E7AABF4FE8967C5D584296DE520B818966A9490C82063D0C3C743208B25EB69BAEF6CC65E3CFF0C0C02997F328D2F1D307D421A87EE8A2737C8D5E7F63141DE2E03F5BB1EA20C829C4A00407F2E500BA2877DA65D64B29E42F097164F09F69C9587E4F1A1A88F619B6D19F210C01D35443F21C7C987658DCFF4163C8646F7671D04ACCE322E32513737B35944D2E70D62924F922A35AE86347843CA971FDFE616FF0DF4C5F77C699B14A555E0D41FF39BA7BD156751E73EC64A25E80E93F59217A5AC8B2162CBA9416E54F04447A99F8073D881BDA47B7D8CAEC135C0D09A028DD2A3EA74BAEFDCECA0D08B8FA701B9ED25B59F39BAB7ECA16A9ACD0446827829EBD9393E8DEC95C2395704F8CAED2155CBE604B047F94A650A981BAA93F070C3142011BF4F1561DFFC4D2F2B7144AC4CCC8DB17FBC3811C421961B156DF1FDE2893FE87902385B0FB5538E996FC40CC9B5995297DB905C262E25145A3C35A6B6F2014CC9DDAF4EE05DED72B3D436E4A6711BBA6B802D0CBC4ED44AB923644CF9E4BA4F24EFE1427E44D0A3FA98C1AA92F37E56440BA282AE0A77831650AE866C86BB2AA49EDDA7AF27B13A612F6A185265D93097679A7ED5A3A38B4B40F16CF5B11B99E2F5B529B940A4583D38B46D388167B34458CAF13682B6070D4FC37700E0222B04B9C1CCFE8FA0BB2B193E743E93C38DFB5029561287C782D9A0BAEE67705C1DF3AB2D8A456EB7C3F5E5D117108C3C15C3F4D2D382A6F69A5716621D004E5F510C4226E4B6BF76C36E649DA5D6EAE2690B1ADA69D8570DFADDE392CE5D9AF4AC94085903C52B0CAA61856150DACA070342754BC3C71AF32F8EB6D5CDF10F0B20471971569E7F1490A32E6F4DAF129A6CAE4DBC977C6B0D18102B9F18226ADF28F168B24D307753BA24574808444E488D812E9E97827261801AB2DD2F62EA4F4C552E54735A018D7ACCB666F6FBA8BF9EA8280876591B8E73D6303B2F1E585BB86F4DA1769503E144D3BD503BA460802D9287CB7B2238C2FBF3490DCDE4D35F02A63463A4F488632070345CA7C19604FED48A7102562965BFD87F2776357B3D1B17AE98CEEC0159FBBE604AEE9D92570E526CF6A5806D1BFD3092007D3DE29B9860DA1DE62184B4F576A8E515BAB346E2A2A4DB1338F4490400CA5A3EBE9AC3A8173C1991839F3A62DF7B903F777B0E5608C7396719639C8BBE3CF8CB371A43F7C436796259728ED11C3AA32BA97FB0B9C708785FD00345543EB8CF195A442A42F6AFBBBCF597CA8230B019C283777830E70C19DCF018C7C0561A7F2FE", + message: "BE2E693BBF71C271A5AB6B2AC0CD1EE8BAA143B4D65E131C", + ctx: "", + signature: "2bddcee4a9ac1b9d19bc1531365c5613e48b95a530339c52f5fc0b671ee01b6587fe08b290c191f82eace640fae216cca90f40fa93bb309e5ff53afc5a042050bffeb69d4e0041c34fd334a7b576c6ebae68042b315fe78f84300dc011cdb144252a06f35c9a2b0a275d00552f890361d1e7b7439572097d1f3d5833b98b751412deb3c0df23ee3a30782919444bcbcc9ac25c645180c8d1a9fe693086cba4779d6e31b1e5fd1a0ccaeb055c471ef065a273676d78c6e7feebfc607c5578107ac27765345363af57f77431e306d9407ce365708d813e44d3107c108f8c5f2848913a4099f6a0ce2ebf80c7414236d4edc07fee79efe1ab6fe70217bec958ea48221fb6b87cef6f5762b41a9c698fbb45f5994969bee21e40f20c95f9a18389e8b49d6ecccdae9f568313448b5162c981bc9ff320753aede977b15f7ddc68bba076a07deb68ac36826e70d80752fac0356d507b3e283b350a17b015f76c71314f7c2086e44601c4d9c707c99a36e55716fd34384a218242a69876c8dea9f6ec28c40189ee2b8608040a96a813f38240dc85a511b3cebab1ea893270e730aef2e29406fcc1f6826101f1361168ceb3f1632e8ee505143e0f031e744928c1e41eab923ebbfce5e9f159fd160db737b013759382274a1d9409554ab06eb72b5ce2a710d8b08f163df991c956abc823d21b0a6d9d7ae484d6cdb2e04de7a282ef317f488e20d40e4e67cbf05d1528c0ce261e3a65163ad518661989484bc964da21122a6c95ef7036b3273f93833d068d9a2c7933e19ad2fde8378c286ad57f7e22c8aa4153718f356b46b34ca4a986e6e9ef3ddb206693acf3b08c0aa5118c8efbb6a99a0fc0674f6228e2f6f53e229129d4b6c0fd6455e28587f0f169270d044398e2c377ab6f985f7f2235d68192d031f39251f9f0270451beda2d29b654511b73bce0f30174a606a1100f4fc984959704ba0e2df2bdf642ba3b0241ca9889009ae575540c71747246835018ba8faeb473b39e4ede5a6fbe0165a4db2e90739cb051f3e5c1c0ce159539758569ecc8b67a759a2e786d5b4e96834db0dd460ab1c5c7ac8f3cf19596979108dfbdc6c8798e7342026b7571ba64fa86d68e1d5179af5768a7e22db76f3a319193d04eeea626d03d4be7dd83ed6c46e2149a1e2428e60f6566833726bcc93d0b342ff4245665b54167b013b02165d29fb64055112c5d800e853bf28bf149da2284e49dc1c5f478f490ce1002f16431564534244cd9c1e0bd12ea9209efe572afb7effc0153e5b2066b1af0dd05a2d9da13308c90a0244ad95a33a64d07a75d7596e46cdcfd154e18f7677ef16b979a1f431eb59279c65a3a31c5429ceebfcc869cf538722a82e6e765bd6a3042dc825684ad4163e054c113b47276b894580db17c92f0859c240b6812716e408a557c1d7ac8cfc3abf9b43f759bb2a13c903bba936206496fed37696171717cb8995b837e869052c9c25348698617c70e98966d30f56bb5bf41369c0131de4178637c5684a64992b50f9ab9a16b51752e14ea521d5bd42bacdfcf100085629796d22bcaa6a7830f9b5859d75b290a0db031bd51b77fa7911eb17d46dcf16d45e3b1ce8fb7dc19ba969a724e43ccce9487c143302c79fb208b2ab0ea1060af3809d5397c11cc0a79ed39ab06c0ef7bceb8e094ec0e3ea52937442ccc176a31b07a52d7de84c0599dbe736cecfacbb41cac206ec3dc20603606a14a1db3ccad8037a540f3213ee9337b529f447ed2c2e287760973b8175f70c78423ace6179757c519c14a0d7b81af00646afde573a35e909ef9660623cd3651b63b79be508698888e1d3bc87344067dcc099416616c273d5ea0e41a8723d11c5ec49e0c6019f6576a2e87d0237c3f64d49afbaf09fc818829d5d0fcb4336e08503d570dc3c96fa71fd7b2d4f51d0d1acc515c2c4dad23896273b2616f7d848816c5cc17f7bb2ed5f952a708c38eb13f0fdd8f14ec71893d3f19ccc8aa15fed848583093f4d8246f36db300c9cdd5d63b507641e4d2ba9ec284d17f4a05696be00da371bd5bab0ba56a13451278dbdeed02b4093e482b96269707b09a10dba9ca1dffb4e4185f9e392ebabcea1f9d26c037d9c0dcd71f2bc13faf559d195ef0d8be7baa0cf8aa105a8069356aa2287ec1003e0759f27b02772d4855bf0b7e0b3e97b7f77bd3119669695cc706090991e6a542eb7a28547978ba8a05d9103a90789108fa15898d14982bd0d5d098019700b37939f9f0d66e78af49c6318b886ba2a9101c072bb1e7bdbdb7319eb38e3320b4fbb1fcb28e8e7fac0bd493faf3c0547214465d55ca212dae99dc53addc3378b7e7b93acbdc1c9787149ed0214cd9846852ed7c18b23ab0ae8b7afbb725d44997a38422ff17b1dbeb22e2387094e0bc59496786114d0bb398f36fdfb06c70ff0ce47e9c3eb8c32c22062ccd5306026b606a9e9c628a377f0efd71087c95b3c1ffbdec8a91f311fbba4793d3d3fbf350e6a4a491d74ab7fb0cb66afa0d66177853df464f0175cdee4f97a4e620366aa18c2d04ebab82ff31ec07722fd53e0b4926973dd41d10422747261d8772b18ab55e0bbdcb89e224a5fc2679c2b729aaa4e1a78f95cf68af3562b98f0586d02134464f87dd15b843451a9160c5f4704c994a32259ca623c937431cfe55ee97d736916ac3e7ef831a1b6978539ac6c3304de2f43b3b208f73d071d17fd5cae631f617929468fc59d529deab1c0080a85ded180f9b7029376059c5ca3ba2eab9ee556a74373eadb5983f5990146b04bd255f2380450864e33c478cfe42072eeaabf8032f2c22fbf111407bf2cc41f6cc55596cca62a69303089c3ec231f42a8358d8bce315debce8cd4cea4aa478fba3476b5252e2d64da6b70d6ea0c1b4a99abebbd194e62e25442e7ecffc788710ac6dd1da815a0a5ef8dcef34ebcc90c6f29372875d664c2a06f3485dad8bd00d94837f417412399f9f085d8666fcaf38d38620897e2d06c7399eb28c5db0ffa42a233fdc6732c3e525bd49771aad03348aad068a5e729565c10d343a10c5cd6e530994c9a400354de3af3b39d20cf2b54fc0c9bde4b6f520688694d9b53c3628f1744b61271383d852219d8afae7a284d2f0b042f2fb70778b53d30876b5a031904a241e5a1741ff2e88bf8faa60472ba66111f0560ef127ee86d24f2c502fcff7575696f7f450109776f0f5e1bbd77a48952697fb2f8657516cc061de2bcc6aff9b861356b97e576f78abab1d3acb70d14a84b7858c22b2a0ee17e1231a2da3738018205091b263c42597ea6d3d7d9dae3fa030a13162243494c4e7f8fa1a2d8e2ff11153a41474a63646770717487919da9bac9d2d3e3070f23455d686a7dbcc9de00000000000000000000000000000000000f1f343f", +}; + +// This is almost tgId=1, tcId=1 from +// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// and the signature value is run against bc-java +const MLDSA65_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-65", + deterministic: true, + sk: "33D8A5CFF7177A6669887A82D35456C31B6CEBFFB49417CFA26E640F7627C8E3BCCFABECCC0CEEBB8A462A6AC517F6B2E386A53DDA5F2A3989EEA53F683CA9805DDEB18825025F315380D0F96291E40AA467137E8C4185A7DCFF7CF9DBF08F45A6269B7F051908C306631DBC89B0ABB4BF5F10B8F9EA19DB6CD2F48B3D38A2E0273727477428010566515638554515216834816051282348218870587013566503644716530628602517465223643645550067784460507720286463316005616610732234638424228512408337208702433435754330357376047354326476184045535786465656674247346143438482688422011862806767567383382285676617058574213411688764581637013318547354734887883708557278262077365550557503452028533403703004648070358844785356358487834215416426775703427133133236781744852361285115886431108010634088154458713476356015071573042266372628108262510567072064362420027802680366165810741225150627570138238281785718406177020845270051677362327744053303227123084874366146004450211430723442261333076351523386160421855507808363277637181007415832342403677180301041578630050520713005541135137475245850184535184176362264418761512648068813127662265152618862078120728642374432730824335655607315827755747726454718158412336872107773013207836288672728370814036773300868236421132338731523050867761021330801447015833582481800283005202327303232807671062125030140732562518011765826627027303425254318053742208783518750016352480413316650612045537378587784633728024213374572513681408283411650380721274775044587701825553370467451700784253780360286440535063651480186442171372515287074876560778652208204461450408623670873460346523463316718281475104443784120488786143871820533664788478571233755626275660065133817206422771334835432544521603418861237426884555226364207088858254614842864105445787530634112820241334274151681670875150758752562088507516286202168543883278757311616382386158603525223802243623050026716785136643464150717807020010286210788766086622323683882184134014711655535804602170017248260635707715877378153207506466503844515852826615142321766415534207576152604856724528626218236615782785500717721844726200246215770657843300036636080300477121366482174876202583100006827548665637404614862557465047638367850376644120018331275341261867333225176300656433713063104154640654056058700177307226514863812627620630083163587700278222726588277200452033010612824567283465475153444485577351833474725322473632565743627410676736271787062226870704160185012102245237226471486076876330381131655044207821874411476281520838305307607064412877170140433473823632568180611602503654402574108261453430167362057001838847111780217257070343047758760288121207114845007146540866470261038857352671644847044445317525436823406368660147121262615750227815048422550217376753870138468425251285332817561016386713867700480441820345631826356008267271331738231022801476431676574645400562110810476101554576135401216673230435384264073475757458656363553743522084721217014410764674564505065437358742011335128131124554517633120542562443113607882671836081640552841107365661528565235234042777374871876265852315343012883617874225740620116181008070517427524673552334603425545703157110353804704060361855533235754261451420545323608233551043162051728C01A5F8AB51DCE9E0DC972467D59FAC889DBC380630FFB9173A62CE980F4132E71F1013E08F7F0DE6BE421B108C254D2331330BF33BF3601BF4CFA26F25187C6DCBE228AFB7CC153C348F28B686F10A0A77A984307971665CAB8D2BC757285ABA463F644B7F9F6175FEDE9B9C39C45DCA63A90736C6C244F0D9FA5791DF6FAF4D75241AB9F95F338F7810BC52BED49D83B5276BF523C806498B31DA4CA61F9BAE63155B3FE0FCC79070AD424B4999D04BA89D0A45B7D8E83A473C4875394C9598399D8088EE8DB4A2FF1BCF25D17A804458E6E363D5D84AD6E09EFCF4E676581624B3A20236BFFD108A72C385E554A49F467B3F342867AAAC24ED514072618047EDCF74547D86C7F0590746E9C383D199CB8023CE69FD5D27C30830BA051F9F138919EBC7D94AC281F9AB1B23A7B6485EFC5C1DD287452E13319FAEA290A0DE3D463E57D15EDEF8F0AE24CDAB8515A73792CBB9658359C466889FC93D127D19E07B673983AAAB411D90A8CEC8C30DF74AC3AFE9C021AECDA04E93242D24E26D1485E278F1B447826696E3E8ADCD333056300769ABE8C48C3666ECFCFC260AF3165D7A6DC0D5E237E5F1F57E123CD65ED40B8C1E1D77F77A46ADF204B97B9C51FC306849476E735253392B9CF2EB1D55E4C6A21530BD5859D2B595B64F70A7A064552CACE2CDECE83FE31DF6CF2CBC70E3B3E405ACC833BE32E6C8AD9AA03042A79DDFCA7350B8C18EB97E1CED6E54AE0DFDFF68763929C30F19D9C1DC55168A0D2441B2B6FF247FEBC8305B6B685877AD6805226F3DFFC3CE6D5197F776BC2C1EF5825B8A6FF5E59515280DF3D38283A490A967FC0F3B62EEA117D904A814B794BD44909A7703EF168DC74F79F361AE9DA66D3FCDFB72DFBC6ECA59B6F6257C9DBAEECE76B5A6ED02A763F2A35F9777D2D9D38243F0E4FEEEE740427830BE2CE5EF6E4533BEAAE05C173958DC1CEA90AC53A968298C8B05D25FA4184CD0BFF38E82CDE19AD01D5585758836036793DFA7ADD5016B49C05D17DC1A2E5BA5E17B672A2B084EA397DE3E9EED9B511D82EB3C3422320B21F476369B6C9D5CC4A594E45EFFB0B38CAEC0867FF40B25C3CA55CF1BA808C1F70E1C04C1567B83766CEFAF51DDB4C97184D6382DCABE52D995C0A50CDF2E248762FD375393F46AE05C708FC3BAA9B4C4675CA0BE7C30D6D2DF16565522EE657EA1477F1CC18C5C4193B08EF9564491B878336125681D40ABD9D9944BE878D8248F40DBD488FDFB79122DE024FB1AF31515CD2D5187057CCBE84E6EDCCA9F36D00385783FD22875BA529388AE8808C87DF39A0B1ACC7252CC9814D559E08A57D6BB0ACBAE86F1781187D7A8DC441FDE6588B6B916AC579A4604E590AEC765EF92DAF6E855C0840568F06E5AF727E290E2629AF1AD79AA775DEA7912E92A7D68FE5E6755D69C459090749305C77E1B2E6897FA7571ECAFB234DBDD928C688FB27445BD374872FF74E1FD260A3EBCFFE09AA0B6ED7AAAA378B4CEE183657841EAA0017AA923DEBBE7F254E979D1FA762B6DC3B16CD7B1C22C86E2365993CF055929F921B067F1F038617E208AB5FD4974F7BE884D8795EF5064E22599E1AF1D7EE995DC4C8DD8443EC839806D5C7DF08056E3B3CD73F0F95D27918D39CE99199498D084F82A2F37485F1419935F83491365429C029F91AA5D5700C4D849A3C2C2E476855BECBCD4298C4DA69F3BB6C2DB768280B9393DDE219EEDC6FF661506164E141EB50149147AB83E0B5EDE1AC4128BDC4DD49EBFB9608B843F24A44F639A1296A483923C8A967D0DAE33FCF56A6D24D4BE8BD95780AB94B5B76D865852681CE6E48DF4B71BB9DCF35484BA1A5AE5A5E78A03601559705D2231F1DFD8D2F09D9530A63BA79808F9B04D3C314EA2BF24B0F536F33D257BE9B946D8188B913B5F26B10649BD847BD34A17B4F4B2098992B4F6B7E990E5F64BA157653A9A7DF7CB639F13C1D8375861E660C7A1250A207B5CC229A19A0CB735550EE49A02CB8752AE05E5B2BE2697CE8277BC0CEC8C782E1792EFD599A6395623AC41B61A7D9A5610225B58E92813D6B429EE7DDE86B23F25A3994711181E788B65B9C01D7AEC2FB879ED77659D24793146699696F50D77FE824B5B329E40F356C7B2644BEBB8217FB7FBCBCF211976386E7CD866E8BA3BB8571D500B178689EE7E1EC92729F0B0F77A2E1A7151CFC7DCD8D70E9BDDE9320E2C06EA0C2A287AB3C088B3FA656FA1416CB35BEEAD3FCA15D9FA8D9982E9999CA5C9FB9D3925005A4A81FD6FEC7044B52B05CFFAA84E0F9BC31C1A1EDD82D90F1B71986E7DB60D7455460AC2439FC3F3358CB0906CFD2DA7A31FF984BC30CFA1F06597F7978288535C07102D6CB43E217634A02E547267377764D494F09960F8229F167C5FE29C16918430B9A860C72F5ED97572016D41A695FD194DE673CCE7A1EF98E07F30811E0C087ECC99C441A253E3755054B8AC9A87453DA92ADC1FE76B6F573F4EF75FDFC66CF00EE6245D7DCEB79425FB3BA8F5E29E3295BA1CB9D001DE4D528C6A79443AABAD8F3E14900EF893F847E272DE20A365F4FA5C8669C7B31DF56612CBD8EDFC6E9485CAD0AE958FA4365F1D13DC7A6F08D1FFAF665AB981B75D84AD00B9EFDE1A319BF802F755666DF5060B275B8A5D7F7F45643F96708B95D0D3B120DD0D3E9B7DD8F67B5DBDB7BFF989A674026B5E72EBED35E61461173D9337E713C83B2D851E4738F294FD384256AFCF2A0212FE5058AA3C322D78A5AF6C0E3BCB4CD93DFBE8D5F148240DAD94AC862F3ED722E7EBC65100256BC57A8A5883B505EABE064FD934FAE90CB02177BF1D2AFEB3308CDCC2710A53477E46746A6587F4A0AB7047D73F10ABE309BD1DA49A54C2B1D2F50E6C2401235DEA0EBFF75AD50B4F861CFD772866B1165A5727417DBACA8162513CA76CBE71F6F91DB3A171089FF3083FEE15809173DF9EC51786379DB056C00237C6D2F8CD8116794B691E2F7E61696D36CAD018988AF16DBB7B386391571D755ECE4D05E4BF788C5B0D63E4C9F6D57F1263D47CF98548674FFAFE322E0127635E4C2FBF0F88D7738BC5E243D4B7BADE073992FD8DF8ED54D192C3F329A60865333B8C390DCF638FF4A0FD32AD72E2B32050CFF2E9B3074A8906D37BAAD1E06087100C755869264A68F2E5708E658C178738B11099FB7BEF45B97AD995A4FAD2CC9E0AC090FE57DD5450B688208C4BEC90AC69B4CD21BBB669733F5435383654C561CF9F3F0DEDF40BE3449BF14DB2C91C838F6EA559EB0A3199CFD9AA6D3C97514AE82BA5E03317008B4ED328ACE55C2F2F4426DB8036B3075713639AD16BA50A4A85B1CA9F099D12A236ABC5F34F0919A0A2C3280E4074D0D228747BFE84177B75B62DF8F22D86E1CB61F87A6C91E3D2EB46CECBD5214E3A9E615B96E21A721FAE312F879E2863EBA3F97401D2288FD00559A7C7C9924F6F0FE13F66C32B6B2BFCA445F738A7EC72C4B42CF89B71B39B01669810B0798F965015AC45C57", + message: "F7DB318231EFC5250F7AB18DEBC0340C53", + ctx: "", + signature: "bdbabd40bdcd6a58a93b43f86f0cde1ada82a56638b5fc973b30f73fd12fbb909b4fbb0f408c86713409457b981676265ecde794bd6797e60194fa41c10abfdee2d8312102f19bbd2edea917b4648040c5e95153af4d886a9ad0841c8531851843310775bebdf127b825cd35a7bf9f3d1fd8e1428d3d899a8470ef90bb99525bb44e2b2714028fabfce69e2bcff10c313e46dae196db53635c846dc847d2ea49c4bdf76873793770c1fe1c2949f3a48c26708cb68af7f32ee1126deb11725ba281bcf4fb390e88eeae0c350afebd9fefec4610f846aabb9090f70d63a087a397b1081fbc1b75a4faf287a4361574f929c5272431a71eb0987691906bfc9f37d71611da9fe7a195b2da79e222b189559637fda2cc12b79b30762d773d2f2284ee79a9cb33a158df4583e28bae59f6c5e1094f6aac8b0764ebbdb2225a729e9ec168e8d09243b70e36f1d1b5505cc83c156f886b7e94cba7a73922cef075097aaaf302869c0977a95eab95f1355e17b34813dbe7a034779d8711fe8a3783c851cd663c62c35a38a0fec446816e93ae247e30eb0e97dd2ec18413f2f1cdafce820680b7150464ba9b5d95ee503bb1933df2c971cab1cf6c64fec3d60873db16c6191daae3fe74080c575c57a1dd6c710bae6ba6ae6b5288f3e568c0c1a9257af4ade209837b03e927e980169d7ca4ee21fae3052be8f9b79344a9a175789765c6573132570d99826651d1940b6220712cdebdc1829e6103d0d43a6f43b090d2d41ac34ac24b190774ad39ff00c73a260a72396273f0cee15951a29e58342fbd24e0ae8f2e2cb139b94322ea8ce6a7a07f0a69b318318c3fb1a7eb6682b3f9971062905155ffc16d54ec0ff3f448e21dd16ac6640dcaf3560306948cf77ece4824bc9e5da85d19c37363c81f9f59a4e21d3ec2877046ed5150d5aca269712950f39384c32a724443ad4ed8f24f3f5fb663e6606cfb0cf35ecb9cdc84c91486c1883897c629754e1e60ad7d358138403f8d8c90f46c69825aabda7cf7b5eb9ebc72f141cbbc7dde169744f55c91639ab2bacb67d7151fd778771ea579f1a60d229a33477e95fe1dcc4575a6a9d01f0310a421d59d745f17cff4bbc87975cd0054fcfba61a792d818822fc217e90196d162250b2c33e99bdb451e497145127b6e93bce222097930e6ebcf79c0c24d193cde7c59abba999c5811912166475494f2bdb3dd27815863d89388f58d2773fbbc7f779f8f2ac32e6ba08cb13a8e014ccaebb017998b844330be4deb8834fe406d5d865ce9da6c10c5c15e9beed48cd08a9896c9e67eb27c379bb2ba23da5960ccda966069da8380067f01cd6c1ec1e3f44dea949fb1363b5d3ea500e78728b17acca6a4b63305290bfc0498cf163883ffa4b5d92952b85f827120ed4cc07e1806ea1e14cac54df2f51028b21eec040c30b93cc28a0ce33cdd99f9490a6db0383f7f21e68ed67c645951a6769aa3660986e0ae312e12c7ce9ef992123eb16093c15d8ee15f7519c0ca8548d439c6566a1b919585aeabaa2d196030881a759a7b3d14d4e39574711fe2eb8bf9cf43d3fef9f242f12446adb6c46c339fc6f2e6e667c379f4e6713279f6d62701ed6d8aabf5ef2ea4b4bf430424737228cca7774d51933a706e78dbe47073eb4f5d30c6d26f79541ed6a5903f6de33c925bc244035d3fce56b239b2f133b98273db64e9dc57f1ca3592d32f80724e62f3aa9357f14a47533afc1c3d53b5450884fd8faf1fc524d008916e81656b5814542e43aedcc4a0cfdcee21ef0eeba7348139d4ac86ebd091087ce4f4c240f350309f784b4736d921b60a0e7a8a967fe8a6b4b972f5cb4a069e8580beffb27dc0ddb36076cbb5a667d3470c787a34cde0dfac60943e51f62f8d9706abf32d1af89259499037d8182d5c70a3251cb048c9c9fae6433ed74302b5b436d3d72693749a6a00ff70b1d7fcbb4a8c0d9e1b13a17756ebe4be6c2066e1f6ec0b165c9f9997ca6cab36a1527798e4bee1494e0ea10e8570649b5c298077129b4a69679409ae5d5c67a0a1b69ebc65a366a0b874bcb92f3d4bdbd8e32058e0014a33fefc6c2caab3b1ea52533b4739bd451bb0fc6a02a31ca1c988c281cdc42be7f0ccdc28ffb1411946ee724ecdac90874b598c200d101a64225ee7dbeb9fccb1e546039b624e918d7c3fa493667ac6bb6f4b57f5912fdea9e8ddd0c0328e51434598b31c1204182c26d60ad8fd000a73b6d8d696f9abd120130b340913d67075477de651fe3aa24dc6b6527cdcbb891ba1d18d546a2bd6db4b9fa3a9e467a162e1649e2f00a4ad71b0f83a7c309cc44adc6564565cbb08e2d5ab98cc731ec99bc3517f06fc2ea3ae25ae44b5cb8fad986996da5e17c38528a43025410a650c80e58bf9e276c510052e39105323038261c52b28fea91a1e164e10c06103dde38a88530806d9e6a0c36843a5b1f1590889f1b7a1ade1cd4c1f7206327d0db3922d5f199d26dbd64d548e7c3782c7b8ef0d5e43802302a0c778c0b8a89e4a37ab3d2f3da06c7699fcbc52c581f812e4cbf1b0bb9aa49104fb310dff4bc6fe9b345d0aa4839b58f401ad8451db7faeb51bec94fad5f375db7d1d23025edbb60c84a0f47d8477dde3c86949d355f3e0740ae3267f54c89b57203e4e6dd5f63f4b745acbfdc42592766d17a278a021067ff9b62ecf3a6afa56694675036c7b97eb7eaae091d0d79ed6df22e6cbf418a9bbbccae51c6f4a7a4e987c3640a0869094c4b434d571adff4a59d75c955cd73be82b8d89374be0ce613cc6b586e709208a04c7e0c3055818f86cabb0a1a74fa9301fa90675f828d29498b59c5cec20c9133a8425fa3a85f7116e434a5794a04f7f4785354ad580867e50efb97e408603034a2075a6ddfd3831d5980e92732a2d8cf01aab52289b3c3a7e8d247e0fcd29bb8d0f00b85a544279d08798430b0739da59fcf58cb2c89d764c4686d3b5659bf35bc9222fbf4ca56028b449f75f6a70f6f0fcf6efcb55d327ca36ce32a9dcdc997504b85971ab52c8f4b9927f2cd9a88ef10dcaa1667e3eadba90fe79fa1ff4426298d211998c12981b490c0a725db382edd29679e718bde3f6bda658de0e22b8cef0b0bffd02e77c561ed0b4dbc220824fde92ae7d021deac8b7856810ed50b4b03c0b40984b3a50ab3afb93f55f08e27f20973ef42e80704c3b7c964e336ca578e3d3f45665c6335710d0fd82d0ca0bfae87109fe599ae7eba667d45397d1f4c4076d6a277fd67f4201ee2301624e4f9c9901b66f6372d43ba18843a1f368c157dd135f66e4169262dfe777674da638f5c691e602e1d87188078adda48ebb215160d3d068e9fb17d3fb36f97aada5b55c138d379be4534b374f70592bbcbcf3e5068354dd470a7834828d7214dab2bdc5d8544f274e39fe810d43f7252a48d7699b31d10b824090d45e2b0260a69ba4d614120128b1359ed56229400c21efdb728a151a108d7f43b588f3472ca9bc61b26f85fb10bb3afd0d38d52794a2bfbd945372f5ff1d58f8218cdc3ecf0d3fbdcf1d4786ccecce1f270a2b0ce87c2733a476cb1c7f0a39999afebbb23afb9d8a3d40617da3677f3304c359023292633f08b5ded9264403850a6b93beb18b54cb9f8a53064727e372ab57943fd876a9fca08e2544ad9f48218ffb7f379f1833c864c3b873ebeeee538f64c18462c156adca54fc373b52e77c7cf0444ba54971ebbf1c0e8ea2abcb2914432c630d2a9f2e5746a1b170a6eb0543d8c952452ba9a8852abcb17c24035479e0db4105ddac4e5f237719a4a827d6ca109a8114858ba89b2b2bf9a4cf14dbd1f89eb53f8162077ebe91f146e67a8aa33704577abc5116b05303825122b74d62c78b9463d69a4e9e5fd2fa560c6a05f92656d206b83d440c980de0b595da9b2891c7db73ec75e0be2eb9305278ca2f3d12f9091b063a4e97d611b4c53d28ef877424df2809142a5ecce0a513a7062f398fd74d80e5ee86f60cc7327ac203a8cfc3a9bc74c6c8e14a7dc4767bd51eb4584c1b1e169908c3a2c3c4501b0164078d599ef1f9133644b94df3cd3fe9a2aaabafd38aeda4666406826c26a4768c8f163a9724c15ec609a8e58ad9a14b39768da81010c549123ebc78a3a3045d2701f45e8db95969eab9e1725ee3c035008b1a1fb404e0b485ce0d206a602e6ccfaaf09553c027fb35cdd1648d1dbfe4bf2ac2ed0ba4aad7a0c78ab7f4d2ba9d197683f295f2124b475a4e54ce11da5f2be8e5d3ca9cf9ffeb9323c19fba40e41dab669772ed114ceac093ec20769b251d97bc6b261f8583a4033f66e35f99bd8b44fbbeb46d31dc7015c94e5ff13754ac4c80efa5ca98ec1e52ab8616b68b018609b223005a7c7651251166847f8ae0ab135a93d46c1a9f2c3b5fb7c4aa292d6b43aea753ad75774b603766f5471d7dde8767c1c8c5975c25fc2567e2393ccc9c235bc928a32450b4add0460469655195c4a2b5d0ddd5d4d2effd55c02a812fbcf5d317cbcf249fb5427f2fe20f3bbbcd2ce3ef3fa4cacd528cfd38a84715672a6487118ffab47681aa130e13b9ad9b5327705b13a80dd5c1a788cabaec7d4e04454585b848bb1c1c2db53ab319ba1d3fb4892999dc1f6071518273b5688898fb5e800000000000000000000000000081214191f2a", +}; + +// generated by hand against bc-java +// This is almost tgId=1, tcId=1 from +// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// and the signature value is run against bc-java +const MLDSA87_KAT1: Kat = Kat { + _parameter_set: "ML-DSA-87", + deterministic: true, + sk: "381286DCCAFF85CC31536E8CFA4D6FFF74AA12D0004D0BC826F54D3EF0F632090C06B8D3D562D3EA6C5D9E486AEE773AB7CB8A08382A055C6339BC650523ABA7176BB4169C6294FC52FEC2E43BA13210D3CAF23124DF2D0B4F7BE6327B48DE145071503F0E9EAEF1136A0005F6F5B03FCAD1AF83C10EA38D288934417E86F3D69CB69044064090063248C6895AB0019B080D99189012892CE008495A0609031005C42668C0C64540C80D80164D58388A18A40C0804720AB55020110293B09061348D4AC40182006E089750DBB02823938CA28688421000511066932200D8A2805BB05003255023064D9B184E83448282148103454A540020DA048ECCC24011904D6348801C004244300922A6314B860C0A4985A1184C18888112442D531690E280249486488C300909997119030DC09820E20664618089E4B01041208484349023058601A83123A63103050464226954006C1AA930884052A082402221690449411A83050B278AC4003200B521CC345083A20D60C84019330E00162941A82DCB446592844C024631611608E2842988023121B4844B288921187188C0011103201238280BA6112303490107629C9625C416445236708338929022084AC60C1B00648202324024315B867100104DD1228E58404C1082119AC245DBB204D0084E5BA20D20245159342821B004C8166DD3260419B10100486010A90CD122489914404C288624080E1B436C49A00C59942051108CA2466822A1714A982140366E24A1251C3421E3060A91C40098185223865114C38DD2002D00B03189A8409B940860A0701898488AA4449000621295280C10055292705A260A1C80211913281339421C0146E31030D9848C13202D99462E0C10881384302431712447418132860B948110112C5838898314825110711A982904260C599230102072C23666CAB669E406861C8469843012C49421241820DA12520BC3214B900DDBA004118808DCB465123085C1448E1C11859226128910840B1864E2286998880C90A6011B08495C180261C88DA1184621C109199389244925503625198490922030E28651C49685CA460410456A1B3862632809A202641BB341CB369054262C1C054E1042718348649A4605A2086041A2900A110151366CC49208C0C444A1C080D9408404C0708032851C43608CC4309CA6800A404E63481019B830CAB82944160461348E84184A24181013019188362C93246612C06463961000C3909B9600438685E1188E18C6059CA064844429108445C9000281266AE11804E01004591426E3328023C405E198710C16328896640C33825B2245C9082619236E44224C12126AE4B811C98845100732DA40825BB2316392895C1826218265639820D30022802220DB9624E29440E2042D2432424832295828461A9145D2988D21999010112D5A866D93A844CC42481282288C988422A791249001113270104045E12610A11411A3820D0BB64DE138019A046124C76412496E1BA004A3B08C49026E5C0069214144C8082913C83018940560228A0AC9258286444A102509361018C32111302A1BB5092047614C10511A422699A40041426ED40681DCB48122020A00336D53C284138751D9C851C238325C809101174013366D64A86019962022A38858B02C08078002170ACBA670213805D0222624A640A1A08D181350E0B42CE0225001A6886444011B29890B000092026C48B62508B60060926019953110B311D022215B006C80B008D18205133924DBC049544820C318069C102AC888645A248018C04D0A038010A891A4086461880C0B002A02B98D24220EDB246D21C76DA4126A58026190B2311935858B462102C144C9864998B2641210486110899896850816050B1980E2C6080886610B040D1A1880A4B80104C52564449021109291202863242120066A4CA208190222529491120886933206C49211CA000461162A00000E43022E98928881228E0C456511882102849149C21010920D8B126E10B42014234101A521A2C220813291D2344580324A1C29824906824B2060001060D0322A43208050342D1013010A094ED0C06448262050886013A07000037114948420A30D21055282A8911149125A342CDC880849C8288306650B16501489048C3626A408709C908DA0100AC90060D046718BA208190760CB8085A1008E4A222A01858192844C98B22164C8000199887457577111119A9C0D695E5008224F1F8D3B293D5067D12EA5D9B1ACD120060BE64483AA869B34C317DC13C55FA59FB1939381A21B8E043795F95E4ECB1592217A42B07F3C9731ED0ABFB2CD487CDCF6F956D5DECAA394378DCCB079CE795E96A9BDE857502A1EE1DBB3D6400EFE25F81EA9AA71A2124C05A0E97A271BF8B166385A3AB1E01E235AF62546933E80906B47627F0DD508B22CD0FB34DC2459B21C1437DDBEF93F603CEF19929437CDC85AEA07303F84662E64973F65BA31B0142FA866EBD1CD858AC538E9A598EB1A2A80D463E84125461926FD53DA52C9D53EC1C8EE2E5FC63A7D3259FC69C7A973421A9B4B3BCF4E938450C17ED123D7428C6D17CBC6EDCC4673750B726A36E187A42221BBE039A9EAE4A01528B5CA3074A7448193E8F29C65AB065A551FA7827ADD240C06AAD74692AEE6C44BDD6EAB9F0DC88D3564C3EA09AEA275507B8B085DC4B3BBA5EE7E9A35A3F25FBF2855A76383DC498935BEFC14D32A0EF90EB2F604C0DADFA4B5C7D8FD79BAA318F68B2A529944F87CC4FA80F60C4BC351A95E00EFD0A0D8ECD2F998F30030CF6D9BEEF2F7D63077DBAFC7AD5E7667F8FA671E5B69BC0B0435C80B28ED28D8D884FD60766E030A570282D8110419820D437FBA52C82CC99C94631B5C8AA9B681DEC98957A2E19C6123721B0DD4722D62F7722B3678E5752DD91C4D0EF6A932960EC1312724C56F58F21D0BFCA70340DD4C48ACC5606387663313472E8194B65FCC048A858D6480DD9C2B83841E85205E6C5E9A95776BA29835BBBF8E567200C84E5499CDFBA85355A2067A170BB5897889A73C204CA5A93118E51A4595281A80B952CB35D5589673270142CDE60402FCA4C8D9E437134DA79E7944522FB72D35F3FDFF2C15D4585AF10C3B64A7A40F2418A336F4EE66CCB98AB0D1B082C62802C87A9778B86D9F304847A76420507978FAC7987E8BF667A8E40F15FA1D9B0DD6299458BA196D24E9C6906C26D04708B08F99FDD9D110094D5B36F48213B213C66F5F55F97D4BFB500157E03A074AF090B7609204A71CD1642D11BF34C76400C124D55CF7A80DA98FFCF52AB2B229720357ED071616BD8E03D7276561ADA1A746C4EC37C0B21F964DCE7B1AA303A9CD7CB8108D7E18E32DB22BCB89FB7E6408324ABF03E296572648FD2E335CD4463A64EC5CBDC8AD7953618D8B2E309F04A74302562FDE3CFFE805C4BB4C661EF131D37674AD6F9C5BBF2290727AA67EA3848C48E3C3CA5A35009FB91C0CC383005604C096422407F4337C17D63D885592A5F15F9851F5BF27EB5AB5095B95AC69C2F36A1664B0AE71E4BB131C3A576B41155A2F997BCBCA494584788ACF2F5CCBAE28D09E69F3A39C2CC394B3465358E1F2A23D2E8A3083E3E26E90121553309A1B8E24932CB64FE659EBCF38AF0D1A8819FDA08CF4F2B03D79208061E113F9B89A8B9563E5163C6CBCC9BC2048C15E2AE39CC2B64ABD6DD6CCE5D01ACCD8CC08A4F266491446C1CB9337DAEEFC6777CBEB3291F60CBCC3194706492B7F953009DD5E70245180651DA918647F05A2902F4719F6D97CF3AF5D855FD1A1C76C29910D518771C604AD7B802E264F39BAF16AD772B0EC9DA32FCA34A85FE2E2447CAF2EE925EFF5A442EE9DA327C38EAA22B8E67DE4A7AFC312CBFDEB44856F09015237E59D26244631A8B14AFED134805DEF32FF49058C72C2A7A4B77518EE2B162DAF6B90F1C96AF5225453C5E4D8CD401C6FE1C626D4CA037F380BF288DC428B9680B595E10BDDC9D8A2D2BC85DC316BD9239D557535F66B3802FFEA1095DD4DC2F1A9A1E6190619BC5A2E063EF8C95066F5AF92B3D99A418E26C68F8AB464374FF3A789F07B2C5A99084C7275DDC481BC1BBBE8BD6171DABEC4A5B01F115EBFADAB26C9AC17A4D4EBE37BC294F0ADE593A0DD4AAC258D106457748FDF0059752442E4B96F1294D6D92F6D9657E131D0288EB3AED6C89390E05DC84FA5EC0EAE33F4DA4DF2A7614FF1B4ADBBBC9ACE849366AB82D5902BF7C5C66C095BD92DABB145BCDF8A744FAD2FCF44AD868C349A6DF2F509EE92687123E39DDA96A735AAF57C09F5EEEDFE3CFBC0B9C05B5D51E2F66F8C0E77AB0302AE603DEC8CFCC79485948C012BFDF7D0B9E8FF0D6545EBC781054183785D95173510E65E0E52CEDBB241D8E23C997DA523B974C53ED07A8DA63958768C9520DA7EE6B7093721EDD8633B6192E12D19065F63EB79504B92C7BC31F176DBF4FF21F5DDE5E414FE6555C8D76A2A77B3B3CE1EF545CFF4E776EF9B440506392ECBDED301490678328FFA2D83DE6099B750C3C99A9797A14FA372A57BB2CB72BDBFC640144D1C100899E2E12F2A23147C0B30E4F162184E1E69C1FA770B46D213F52CB4B36864289FCC3DAD1D54216EF9D9FC81B709D6045DCE41E91E5A0C8B5518EEAAE07D2574DB1D53ACA5B2A4A2FCD6237388A86B4C58B4200511103CCCB2420E62789C13F87C82A90751E121788274C028D2106D5FBB0D9D3128618FCD7DC5B4801C01A2410ADA95A7CA902A8F03E2D538D783AFEE2B7DC674C1A71DCAFCDFFCCB00273B942D79A5435DA1CFB2C44C3FB3A4E221AA1B2868EC9F4FA43A522562AAC34BBDA07D337A12748A1B5120D176CD117110D4A7061BA6AEF3EFBAD21E998B78192214380FD84A5D08F9B11282A15BD9226AA54F019213AB6C0BFE8291ADF09B680A16770EAFB2B7042274EEB896DC7B6903113862B1BD5C523787693E65B6C2A85845768A3BC54DD7A02C4ECE6CA3DBAD2D83DC65CA1DBF7C3744EE4E4748507DCE7F913F4332CF5214D8EFC1B4E67C86AEDF09D545C354FFE6B8A4B8F15E765A8F8CB13B24062A17084C52E82964CF6657C808326ABAAFF60545BADEA8A44DAF0A038EDFA6F4A66C57C721A6ED2E8736EF92F4BB8D230DA58DAD187ABF58DFD17FD11A9764D3608D288AA2EF3F1DE94DD7DF07E829D5D07CAB1FFADDDB4DD460D9FF3DAAE1D918AF2AE195B0F95CFD4E1543B4920B20CC808C6473C58933B370C45E47416E973F42FE6FC9C4FB5FA95DDE7D8325A8837648625F722EF95B0D08803FF0299E6A8108122A1C7172FA96ABB263240FC560CF3E1BFFCB2ACA913A9788D5C02EA0EFAD07BEF958A144B15A902AFD13A0AF976044065068C77100066F68982C0B98BB77F814AB624EED9C84531969DB1257C7AEC11DC682434809C7594A02116D6840DD99630629C150CB0CEFDA1E851F35C50CEEE6689B8450E6E9C4F8DAC4F487AE4DFCC60B8641A780BBD724036C21B38BEB1C8ED30EE9AA03DF899678FFFECE5200DD2270A9332B9557E577BB7E5B3378E4512F0BF439CA3BC16E8575DC46B2E497988C7C6F4EF529A730D7D418FF548AA7710F6F3230503D92A8B6228FD19CDC53F1A40165BA1F3EF1FC68BD5717AAF0810AA56AB90604BC0DF466D92403C1B9CCE0B2662D415EA69A1BE094AA5583DFC472480EE0430A93B21BE5B1BD4E27026DF5ACBE51D891B0E49F70DDE93FF43CE43A4F9B82AB951DE14A8A9BF4650FC38D769F4850688103D0D3E4A9BA645DAEC785A571D573ED6ABB22D987C2B52C5102CAB5B14610196B8D71DE69103E59B5C2A682390290B508A693CF87AD0214DC8DF2541FFAC89B38E19D703AC52D1624216FAB0227BC1045A34FD658111F92DB7B97FDF1CDC56FF7CABDA8BC26EF8347A92A2D8A3C56C150C5E3094F3D34D4CEC3C371602907419D8259DA0C74E32F63A9225852B5C9208785E56D0F4DB3349F5DBD06A10397965453D4E70D2E1E7FE56AD98143720BF25FD8204497D0A5EE1F37BD8AD4364D9BAAF2BE2BF519316D1314E0D4801CB90FE52F66A82FCB4708EFDE4FD45F70C38E70B59206C221B42E840B72FE1FD0DE5C7B0B8C0BD1B6561899FC5771764733108B69F81888FDA9E8CF60044F3BDD3289637F6ACBAA5B9B1145DFF8CE247B438320535C514BCBD445146BC00EF8E1DF3CDE218775DBB135FDBE92B7F303B1C344FDDC2E381420544DC9694DBB142704A0F092BA281537176F17B0C1561F0BE320D79677887DEE7C27E01232BAD43D49D9D1FC1A6D8C887443FDD756EE7561C24FFE0FAEEE66DE9A6F05CF687EF4CF456C7DD624DF017E11F66231793FE81C958AF0DD89C0D088B17DF94304659B79E9F9C5196BC1A9F22CB5698C751B829427CFB437B16FE1E3960A70B223E7C46CFD9F1C6195386A170AA07074D7BF9F634CD834939F8520B73D2648D1994CE80FC17E549DFC87CC6164A9CFD77E6E1968D9C9B582FB70736B6163D1E01C4632D594E3A3532E61E1CE87FE5AB5A2E11F98224E95D7136B34D814F138CC5C557690B57DF3EFCE187CE9A01D1CDDE645DD10814E3B3FDD69947A0746C68D9F77B9AFC61474C55CFF7EAB24AA5643E6B451A4C6D84C2E6E3D7015C6F00FB264D55D8B2CFF5F87DB3050E7DA34F7612EE116A088180BD0E1F3AA1BD63B2CC029B3E96C9EFF4C2D1A2C53F25084503639819E5A6E73BC07EFA5212CD9075A6547710A1237AC4991445ECC76D0C11BA557E0BDBD7651A4A6E66E3F9DBCD532C2301BDBDF63307AB425E6AB902164925BC424C5E7B2892DBBED51E0AB5BF33976243D5AB4DB7272EC292D1DBFC494372B51526C9DC1380C3BA563D2CCFA3A20EE7F800ED8C2A7D199D2EA6F1FF7F4B6B4AC51041C974D740FE7E1EA1D7311B81E5ADBB600CDF62BE0", + message: "3F8599C466713387354B419392EFED39A9D76C0B5E45E079B3DB", + ctx: "", + signature: "46233c459671b74a1bcf6310c1809e658c786c1176ea9edf6563e81057edda4c9217469d69170ecb21d1aeee63f42dd65be9d99d764aa020e32a624bcccd988a75acf7cfea3ea81c26d535b931612c0390b94f64152cb0b70d8a05cf4743c8b26e79d83114e6c6afad39b028189fcf05cda112d032029d53d3f182936e4fc4235add79c3810faa03f030bf6abe0c797b6c59cb2b6453aff5c48b4afd43f303a7258df67f848b3615a364fef4c831d90b989ac8ee2eee3b2b4c39a0973869b97b21f9008b2cee621727c26b79d8639cab08f6370e284c705a4aad931c2f64a000d1b6c3d0f731185ff1c69f4b9c68cf121bdf82ec786c64f925a6653027a4fb3dd430caad6ed34e70332c2f6032cef39d5075f4e02f6683041c2e1d802b3cb5c499dd949f6ac61366dce9fa57f7ac7c6285b05389b4afb68487fd47b5608a6267b4af228bdcda2283164b6a2afe67792e6d7ce03765385ba48709ea41a3fcbe5ea91682aef8b121c15d11d590fdb55ac5912fd5d05e76e71d4b78f38c1116882246927fea855fbb318170bca7ffb8240133cc9030f43c9b3651900f332129a51597875739faeb27876d1f6052e986f269e3cdd7b1a5be19d229dabe063b3b143cdbe47109affd2c5b38aca2506b6dec3918bab1e7fcd826c0d6dbc5d7cb64e161d7f05492f8f8783854c3b6a601724f3dfc7a3ba3e846c7ba52f3d0d98643edfbf10c5d2ac36170e12f859a4b696700cbb5ea7caa558e172f9aed0e37ddc2b5c0af5f522aa66a7f2e1a804ae82df5b5c89a391fb6367aa0d21e97622786a86bd791cfe41b0dde8bde9aead1eff312a5036723f63318ee8b59d7c1cb6e4502970abd0cdfe8841475c93d08b8da4d9b0e236731ca6b58ed44cf256e1bcd1548fec385ad330f60e8d5e20d45ac4e0b3f0e3a4faee1d1c3b3ebcfc0ced8a7ea5a064afea0d3f4dd915330fb3c0d06ceeb10137242f632c61966416f317b189e3708f688f3a0a7b24005aab3fa9a91f7eec42eadb2a4af4b2f8ca5a1eb1cde7ecd9401fa36abcec73cb144584c2c666f45be65ec20058466da3b1c5696329019253bac278a9ef2932c67979a2c094816ff639560a023d18d3e821cde160d6feed78a6b33d848b02930f7579ade1b520ba57abebeab9d11af85e31e76cd2a095bf64c57a78813d9379e1ab4e19965266a13199cc523f29a3bffc03569ba65e905d302c2a236223e5fdeaf9277aa090e1aa0ca55af59e03d4897197d8c56d13e6831931922cbb1521ca523863afa0f579f5f06e004444ca77310e64fdf3093cb02ec5da3319b8ba588ccc231a5986fb381a73a5536b52036f3cf412a6864cbf7cd570534d7766b88216067f672d9360d0acee40f225bc87e3d2a4afb698f263a4c22d199370627c366fc4868688bf56863cc5215928142f032937ca73d6a3cad2c0798316434c47074efcf290366aafe65671068923c8bb912171d87ff2513df764331fea0d9ea75bcb8dc9ee9fd697c4d0acf8a1fa63bc9ce1bd38d20f6e560d563f61437be4ac51de1d3f3afb5ddbb6732cd977cb1027bfbf16f703d84298b07d02f5e57cbe6d964f90a7ecc832afa0062540bd8ce6310ba056f80dd05ae87a272a8cc5348f0c170fa74037c6660c146d3b83bb3310da23c02618d37fd34c8f08855cd88a5e7df4e0848093823c6f6222e26688e475177098eaa9d174cc6108ac99526d334f8cdbd74468b25cfbc6ea92e3a9a0b92334a5464ececb9189274f0815c55acd702340b293371dd4b46055b1501c7c31d82ff47d9ecf03ef96285c35a25caaed59207dbcbc0d72e58944a2952b87eec22084994493759d4b5b93d4471cdbf94166d4ca378e629b8aaae3f702795a24efc973b2b58690638eb26dd0ccef0e8cf7ae703a270ff09552939ef7fc123429557babd92048110d20c839a2bc661f7e214f9140a51873efc9289c88e0187ba30fe4879597bb0090c1442dbc43cf014c41a3f38a1aff1597b30355a74a4b94193bb9a88987ba05079179e6332c7dc266b46abb89e62c0faa333aa708a3659cfd51f71e72db798062b30d92ac830904a78832a9e2e6aa88674b14b041cd8e7f9b73c414ec5d64048fbcaf8613e3d56173f7e3937e617d46db9d2d94a7f3f0674fc63a4ee791a074bb61d69c210270d7a6f90dcc78369f5f4131f306b994ad9fd84e3e6634cc54276982620569ee06eeeb5d44ea3271ba630c613a55fc05699c5d60f89901da642c2a31d8ed53cefc16c476cfbc65c884987d0388c34f4546e2c3da3d589c403339b8d5fda22a5b5985426630168ac5584bef01fa7b8a19d3941baed8f2112f3610ce048c57a08aef4b0f44af528e535aaf75759a90eaade5aeafac33d7553654e9a34d34111785fc6dd3787b52c9e16d9a6b242850bf332776439f3ff9f3e38069e035310014667992d19d54f9ee1aa9b7e4cd78a9e73b59e1c388b2585a95fc79aafcb1028b3b26247d607e4d52470fb81e94f4f549f91986d11a5d94cc43f4dd74d19f4598ca370e942c9ac0f8a36a4acdae859018d9847edb03fe32e12b5642bc1155612be0dfecbffb817304d0bf3f01a2da6120ff154ee52d4199c77d0c4563b8dabd7c42378abe597a98423a5a7259a68f3848d0a2281984f5f9bb746a3167ed9386a08187d8f4478ba634076a440be91df95b322f839fe9c52d4294c1a76efee014de3350ce2fbde0406b850f236d06d8de183e7aac4d2c3925006c5fb804dbcff49d845595b6cab5c0ed1d29126fa4e899ab1840c510eaa3177d2b900b80664be3f6c675c0ef86a1f145e18f01c5b0f8d8c93da1560470529a628ecf9b6ac6762a151baa58a684bf29e8dd419bfa85960b5ae40e650584d27b8e01b6f94b1ced660710a46231b502235fc212539beb12cb7566c051f26b8f53b1d19f46936e831cdc7e13f1d3323e047683d6682920ace728d76eb04131e702c94d2a1bcd88a6f71ad654fc92a3183ca5674598e63beab216104f9c94ab5ec6d34c6ef4c5c49e24a7172c707d4a1d9ea9c8733dcf6feb14a9d3f56bb94f872d835b157c08d3ee0828ce88a64127547ecb02fcc8b8c6516ea3e7941c53ce4394ed1a4f7476c7b5301e240eb65fe7ce2355b766605704f059137dbfd3ee4d7b5fca7d821b4450f066ac71b82f4b2536f994a38a85ad9a734c040c9c25dad664c17aa2fdb9214b243ebc92d789853489564c199ba90496c0f96cf05b8cedc0843c7a816c9ef64d5f6abb996b351de248288936510bd691043076315b7e76c373935380d510c730d980d775d2e01fd375a3c30e4c7c92754766c30035ecda2f26cff9d5a8f07d45c9350843d6c5051072b54beee875ab37ea9d1f886cff4ee2ccb048fdaf9b5eb2146e7317fafa68c488c9e6abd2eab02d045e01377461f4f77b9b3a670f1c451bab7760a3ec1acb7274457372e20b5b5c14e9014e60432023bd4c10dda3a96ff9b6ede5bccd133373bc7d251885e8bbd676100fe7c0abd2e94d5d245e4126dd0ecda2c7bc64b3cc13efc0aebebd0544b4f748d7f165c5ed7b1228d650edad0c5ceb6d8fe54ceff8f919e5277e8fd0ded341cc3cf2e6a9c234e8e58c29923855f80145f587c88762c83f8dae79260f5a270e98ec270080b1aae0dfe6998d21433b712f1005e0bd46ed66d5c754cf95b8aa75daf72671c90ae444eb62df1c714c8b58e0c0e65d9383242b718ee5aeff3db90ccd3692495ed33c28ce075266ee4eea9c272b84ba91609a56b922f472bf788989e9f3955095c02054d4fee96a1753018dfe5ba761f7045b628c1d21df924f1a20f1473921befa0ca0851c3997911af113fb28a00eeadca5864cb4be2844a0cd733cea496f6256d8ba5945e9be759297014c94735b275deac1dd2e2b3a0b9ab9dfd9a38720b3bb61da42df49cdd2c4286c9e411f76c1bd17b401d846b7349974bf2c0d68f24fce9838af0fd1fab1a52e336d03fa161daeff1747f38f5a057ab1df3649ccd54a733a5dbd35bd0bdf243e7523acc0fb5b2bfe59f3e90a687aab488a6ba48b7d4e1d47571866ec8159dc369dcfd91021c1117c4aaa1653594088a43ffd45e1a6716be4c367d6d6086eeeb820718d6a994d6f230643a2472aefa653245ea5b4f6f5fb62f9acd701121f0a72694d26ebf6b035c0456eca41dc2756f797cd2fc3586d4c69ffeb1510a6d99178f4e4c6776954e1d09a88a8189d221b24b1dac2da2ea4c9016f3f5d5bcd4bb00ebcefa8a5725c7a2741f39334870537b995e7d720f6e252b2e7b7bf5983847d6bcb6241f7166fe4369b5de386acf4a5b851851d9854e6eeaa8624c9b2a49ad7a5f1421b882a25b7831e295f63af720928fffdd6040402c3eb3dd45d60c281e34514b8b756a19bd2dd7c2ecc6c3f0bf47c29680d6c921cc1344f2a6ee36aae3655736e034e4538e274d0ccde97e089b870b3d94f1ae93313142535c9c7bc5bcb2625833c170298f5ff9d57a024f6f696e9f886ed84e017bd93baa1688719fc4cc615275a2149b2b0ad4d84acdf71463987760241e21834ba97e8572192e852519530b7fc461ab82a802f09e89ff9ed56c55ed7567dc77464d8614242d7048f0355eef3a5a35c9d0fda21867a4bc3b8593a7b8c431cd4301151b1a7af8a07fe9cc86bbb16c142900a44aa38e55b1e7b36da5dce7102b2e9a5fc4c1aaccb698ff9afd1dae6c90f2732f73a9c8d19a39b8332f2d68a44d210ed8eb00656bfb9a9d1f7b0303df62f8f784aa81673fa9757a32a5f56b96f416353e8f236e26a97b0931fc5db3b47e228b3afa26a3f9962ec866c625f6fde06284c40916c61e577adfbb99a6a697edcd34e611bbd6daee8ef701e217b5f009538ed3ee582c2eb2cf9b1cf3265e2ed28880bf8e79a4dfd4187b77ba3e085fa850a58133936f9db8e1fdcdd4f35f8fb47ac0a7a1c0f266f1f367cd773a21c9066ef0b51f65ff1efe0352ed09f1283d08f7ea43d93779bc93a64b717a8e67ef565c6fd7f8d4a33ef6115fe0689618cf9fb03d802060fdd7c8fc288e29ef08f23a6738a5b0daf72ce1aceaa457723a5c207563db06b9fd7d4f388d61b64bbbd9b4325314437584624ef749e9cd799484f8d942c175033768805aed166338456292f49ae088c09aa0c94ef968a92bba8a0e2a4a716ff235d3ca9ccc41fd76cad426d499ec52ef0b06e4985d44782645827c85be77cb558ee4313c91d09d05f34bbfb8f0edca86353ea043a1f18dad3ca02e87506b3dd1184c86912da3f874efdf5ead7e85644ea1fb3f0d5eadecdd55d69f77b91fcc307961216e3c128ccf149b9d67f903cb130faaf74613e53f99fd3bd37522fd983732a42836cb34d94f41e61bb34443472aa5a64cdbe9ee82b22174a14758ecb17e27b606788b57e4872eb4df8d50ca492799300492589a56ff8009d26bb5bfbbd67283274e89493e09d292351c6e84c094439ccbc7e6785d961dc43e64c8d835207af9eec6a4a162f59a4aeffc67b83b4e96bec6d933e7601495f9e171c9fcdfbd3f19b5a08d58aa0704f94a09671e0af89e4fff532b6b3f5805f0d586ec9c5841595448d9c6974d2528ac465cffa32a5d31df74e692231c2721b7a7fbd85366f1fda97d15bac36ec53f467e850cf37a1ac304dfb539a017c99fca69b12d67f62ba635961ddb4dbcc3d5c932d1f67707f133d3d7645486b4671df019b9102675ed0580c275c2f9ddb037790edb35c24fdea3fd380154867bd3594bb14bf3d42f9794049d3c530d7f7bd50145f138b41b1fefd9006bcc454e605aafdf4f1dcf8f51f711d5ac3690fb7925af5b3802e60578f4036e83e0fe093c538422e92551f38429051fe175c0aec40f458204baf21a2f7a47e39d27eb3a30c6006b269356a3ae76f39d7518bbf05c80a1018fdebe2d339082f8fbe70c07b795717c629a0732581cf2540291d09f57e5247930a21c7b6863a4b2c15f4f12f640653fc97074db87a7202f38b12aa68a9a392ab331e1bf8807bc45f6bbbdf22b0e9992c10d314b9a4089ad3df570ff6c1fb722112aaf635dfb465685c2be46cdc4fd62f9fd4b7931c61c38d78c2f345ed5195da517245636744fcf1812b98111ca6f40d7244e4dbd23ebe07f2e8ca3278fee3f2284d353def64074f8ad2e2c7c377837523a70a912ad801fd9f95ed30b399abdf1b2854f485d263242ac30b6f9633448f62ed6da87f1f824d67df5bc16078aa218a17e06e81ae251c34f721bb8fee36d046d9b608f67094cf4f2147b2108794037a311ef3aa14b2b7dc06ac35e792d29ee7fc347d5ab62dcdbe3a7705501ba4b1e77a63c8e92962f0927fe4f1713878ab1a67cfc2776125ec2b3abf2c0b3d68a694c6dbe48347d19442469be8d2ada071ff70ab6c784d9b35490eb6d9d686b18fa00d05354566fbc1b3417de8cdfb6e4dd4002223247b2bbc1d5d80b77808a8e939fca375074dd00384c566a838890bdeb397328304d9d415688e41b3784d10000000000000000000000000000000000000000000000000000000000000911151f2125292d", +}; /// This performs tests using the public interfaces of the crate. #[cfg(test)] mod mldsa_tests { @@ -24,25 +73,6 @@ mod mldsa_tests { }; use bouncycastle_mldsa::{MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait}; - // #[test] - // /// This is not actually a test; just a convenient way to generate KATs for the mldsa_lowmemory crate. - // fn generate_kats_for_low_mem() { - // let seed = KeyMaterial256::from_bytes_as_type( - // &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), - // KeyType::Seed, - // ).unwrap(); - // - // let message = &*hex::decode("BE2E693BBF71C271A5AB6B2AC0CD1EE8BAA143B4D65E131C").unwrap(); - // - // let (pk, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - // let mu = MLDSA44::compute_mu_from_sk(&sk, message, None).unwrap(); - // let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); - // - // println!("signature\n: {}", &hex::encode(sig)); - // - // MLDSA44::verify(&pk, &message, None, &sig).unwrap(); - // } - #[test] fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); @@ -52,20 +82,17 @@ mod mldsa_tests { tf.test_signature::(MLDSA87::keygen, false); } - /// This runs the full bitflipping tests and takes several minutes. - /// It is left commented out, but the user can un-comment it and run it. - // #[test] - // fn test_framework_signature_extensive() { - // - // let tf = TestFrameworkSignature::new(false, true); - // - // tf.test_signature::(true); - // - // tf.test_signature::(true); - // - // tf.test_signature::(true); - // - // } + /// The full bit-flipping suite of the framework, several minutes per parameter set: + /// `cargo test -p bouncycastle-mldsa -- --ignored`. + #[test] + #[ignore] + fn test_framework_signature_extensive() { + let tf = TestFrameworkSignature::new(false, true); + + tf.test_signature::(MLDSA44::keygen, true); + tf.test_signature::(MLDSA65::keygen, true); + tf.test_signature::(MLDSA87::keygen, true); + } #[test] fn rfc9881_keygen() { @@ -111,7 +138,7 @@ mod mldsa_tests { MLDSA44::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); + let mut wrong_sk_bytes = sk_bytes; wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA44::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } @@ -152,7 +179,7 @@ mod mldsa_tests { MLDSA65::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); + let mut wrong_sk_bytes = sk_bytes; wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA65::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } @@ -193,7 +220,7 @@ mod mldsa_tests { MLDSA87::keygen_from_seed_and_encoded(&seed, &sk_bytes).unwrap(); - let mut wrong_sk_bytes = sk_bytes.clone(); + let mut wrong_sk_bytes = sk_bytes; wrong_sk_bytes[4..8].copy_from_slice(&[0u8, 0u8, 0u8, 0u8]); match MLDSA87::keygen_from_seed_and_encoded(&seed, &wrong_sk_bytes) { Err(SignatureError::KeyGenError(_)) => { /* good */ } @@ -417,7 +444,7 @@ mod mldsa_tests { MLDSA65::verify( &sk.derive_pk(), - &*hex::decode(MLDSA65_KAT1.message).unwrap(), + &hex::decode(MLDSA65_KAT1.message).unwrap(), Some(&hex::decode(MLDSA65_KAT1.ctx).unwrap()), &sig, ) @@ -455,7 +482,7 @@ mod mldsa_tests { MLDSA87::verify( &sk.derive_pk(), - &*hex::decode(MLDSA87_KAT1.message).unwrap(), + &hex::decode(MLDSA87_KAT1.message).unwrap(), Some(&hex::decode(MLDSA87_KAT1.ctx).unwrap()), &sig, ) @@ -599,7 +626,7 @@ mod mldsa_tests { let tr: [u8; MLDSA_TR_LEN]; { let (_, sk) = MLDSA44::keygen_from_seed(&seed).unwrap(); - tr = sk.tr().clone(); + tr = *sk.tr(); } // BEGIN expected values @@ -861,7 +888,7 @@ mod mldsa_tests { // println!("{:?}", hex::encode(busted_sig.as_slice())); // END GENERATE BUSTED SIG - match MLDSA44::verify(&pk, msg, None, busted_sig.as_slice().try_into().unwrap()) { + match MLDSA44::verify(&pk, msg, None, busted_sig.as_slice()) { Err(SignatureError::SignatureVerificationFailed) => (), _ => panic!("Expected verification to fail due to busted signature"), } @@ -1111,621 +1138,3 @@ mod mldsa_tests { } } } - -struct Kat { - _parameter_set: &'static str, - deterministic: bool, - sk: &'static str, - message: &'static str, - ctx: &'static str, - signature: &'static str, -} - -// generated by hand against bc-java -// This is almost tgId=1, tcId=1 from -// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, -// and the signature value is run against bc-java -const MLDSA44_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-44", - deterministic: true, - sk: "1B8664E94372A180CAD8C3D93F57FC16F452EF524D2DACEF0A50C0DDED23FBC052EA7330489B17CA2FE4234FB0F9CCA63B6185963852374EE10116AB38AAE2A922693A91188E5F5057AB18485D98B87EB21CCFDF5E9F4B5C5787F5043988EF6790A47D70515D93C48C28A855111D73F245898C877BA4E6A646057579A111F59C1BC629D82652D388901B2961C2C270A3B68054329299362691889122320418918820132D03C28D8C144200B825E0A88198A210031328D4884D1126690AC38D1B1510089051C8142D02B390C8440564040800290CA4480EC4424D1A960409A58010472D42346911A4401481884830411B122589808981222E63164D11C165DB064C0A430412374AD19271400846409271001748C9124894064952C06D1C316AC0484C19B69103A70950100208460458084C49224EE4484559048D514265D99220E2A41162C231D01620C31800D4A45008390A22A67018B36882049103440642364E09B3484A8448DA382E5CA810E01810182466D446681B489102384D8B18510397259A400A00436992C40C129285C9C05060486450988884386220808D919268233401A0B8405B0662E4C42119B74099B265E2400860462AA0B40D1A3248CC06318304489BC6114A484503A4010CB68D11860111984CA21410CC2666E0A02C94B01183182C904805A4320860B29122B66142886DC9222D24C171A42211C9A08814854023034582A25183B6711BC88DD0480292387220B041238790C8081203C0099436918BA2244A422959042D48260194322860928C00A844D21264A0A6240C9870C4A6042134209CA4099B128A1B3249249651A022281B44640AB36C029969C4C2710B396D0B964509B6209CC87150142A80B620CB28121A93684B382E01350A88160520983181322104426E83A6481014495C304DCB1826000390A4386103314E02B3008A14440AC78111C5704018021426650BC68C53C4708098481B9044C242680C124E2049284A3671C9B08911A77049982403C0300A83654B426D99345110B82C021092999429A0124E98184D50242061908981282661B28108A6651B22450A342C9C984C4B382DC4086292188D41C46CE2C491D902419C1231C2120CA040440CB12D22062DA3901102286C580066801228C9083101872554244902C62CCCB28502168CD8422E24416D1C20924A0660892826D2329092922C03451258B2099B2481183804243390C2B44C5AB485C814610A188503852988409203A580C9C0E30E0791B8E694262282FDD2FA69AD5CA94D015922BB63BC690FD546BC1ACFA0E79B02E7D1C1E92F419DA68C30CB12C736E216FC72C394883FBF40CFF538A43D08CE8A390DC7B78542199A54EEFE8FCD49225596FC97B27CF8BDDB57209E48FB628909CF889D38855CFE04AB04486378D17DC5780E81168B5E1DF462FC2D43860031B59D80AF95AFF0278210B9D96023E8A88FF0E4403D710895C830049AF4B8BCB57C5972D067A579C8C0021E5C870E1EE10629518191E283E796B58700061BA656B9385F54AA6917EEF52E60669B7B4F4FD36063DFB7822CDB4F19D9DA4E2F41E4BAE8C5274E7A9EBDB80BC9555687827A10224D9044DB2E45A89F582A716B4D3B131AF7DBCEA95DB88D84C73281D4CA7961F752FFA520BA3FDCC245331849768CE4D4B1FC330DCE474F31558764395F97FFC847BE9147DC22A3021F666A4327FF7FBEF007683A84C9FD99F5088703784AFFB3D86F216405BB9218DF05BB4CFA100145C9358EDE1A0F4C69AD23F7BA56FE8755F711067D7D43CF9FE0A93D642383DB3A0086CF7FCCA3FC59379D416DACBCEB7B49DFF554748EE36DFBCC07D15270A0388DA88360E6CA476DCFF56DE0B7B359609E58A728EE4B3ABCC296E42CB76034C7E13BD0B5208EE8CDF451F6CCCE87E9A21C3DCB28B8B9805CA7115065757B3C6D2A39B28B8610A3F30092C2E9F43A744372FFDF6B5F69DA6589860C520882FBEC130B28E1788572252A5995EB00B4B008931220B27488FEF5D04BC28C1FFA9DB5D0F1ED49CC4D775EB3DEC26ACD92BBB8A33A195A0C2F5AB7386BEA3AC9A9EE90C762E005DEF59348B0B1A6A373CCCD0BCD4125E0602EAA2BE057603020C2DC849513333C019A22210B55BCDAD54279BFEC5F998C1D14F7590B31B403564ED2E54AD007EDA4F2DE06AB0315E4C3384BE2C5392BB9FF1D41C770A767E4947FF7B609CFEAFF54DC672E866BDB861B81CAD3CC646DC325A5E1CD37C45FD0E29D6C2117CDF4C8B3082249C4F8D3E736A1CC0628D5FCB322711D78B881E7AABF4FE8967C5D584296DE520B818966A9490C82063D0C3C743208B25EB69BAEF6CC65E3CFF0C0C02997F328D2F1D307D421A87EE8A2737C8D5E7F63141DE2E03F5BB1EA20C829C4A00407F2E500BA2877DA65D64B29E42F097164F09F69C9587E4F1A1A88F619B6D19F210C01D35443F21C7C987658DCFF4163C8646F7671D04ACCE322E32513737B35944D2E70D62924F922A35AE86347843CA971FDFE616FF0DF4C5F77C699B14A555E0D41FF39BA7BD156751E73EC64A25E80E93F59217A5AC8B2162CBA9416E54F04447A99F8073D881BDA47B7D8CAEC135C0D09A028DD2A3EA74BAEFDCECA0D08B8FA701B9ED25B59F39BAB7ECA16A9ACD0446827829EBD9393E8DEC95C2395704F8CAED2155CBE604B047F94A650A981BAA93F070C3142011BF4F1561DFFC4D2F2B7144AC4CCC8DB17FBC3811C421961B156DF1FDE2893FE87902385B0FB5538E996FC40CC9B5995297DB905C262E25145A3C35A6B6F2014CC9DDAF4EE05DED72B3D436E4A6711BBA6B802D0CBC4ED44AB923644CF9E4BA4F24EFE1427E44D0A3FA98C1AA92F37E56440BA282AE0A77831650AE866C86BB2AA49EDDA7AF27B13A612F6A185265D93097679A7ED5A3A38B4B40F16CF5B11B99E2F5B529B940A4583D38B46D388167B34458CAF13682B6070D4FC37700E0222B04B9C1CCFE8FA0BB2B193E743E93C38DFB5029561287C782D9A0BAEE67705C1DF3AB2D8A456EB7C3F5E5D117108C3C15C3F4D2D382A6F69A5716621D004E5F510C4226E4B6BF76C36E649DA5D6EAE2690B1ADA69D8570DFADDE392CE5D9AF4AC94085903C52B0CAA61856150DACA070342754BC3C71AF32F8EB6D5CDF10F0B20471971569E7F1490A32E6F4DAF129A6CAE4DBC977C6B0D18102B9F18226ADF28F168B24D307753BA24574808444E488D812E9E97827261801AB2DD2F62EA4F4C552E54735A018D7ACCB666F6FBA8BF9EA8280876591B8E73D6303B2F1E585BB86F4DA1769503E144D3BD503BA460802D9287CB7B2238C2FBF3490DCDE4D35F02A63463A4F488632070345CA7C19604FED48A7102562965BFD87F2776357B3D1B17AE98CEEC0159FBBE604AEE9D92570E526CF6A5806D1BFD3092007D3DE29B9860DA1DE62184B4F576A8E515BAB346E2A2A4DB1338F4490400CA5A3EBE9AC3A8173C1991839F3A62DF7B903F777B0E5608C7396719639C8BBE3CF8CB371A43F7C436796259728ED11C3AA32BA97FB0B9C708785FD00345543EB8CF195A442A42F6AFBBBCF597CA8230B019C283777830E70C19DCF018C7C0561A7F2FE", - message: "BE2E693BBF71C271A5AB6B2AC0CD1EE8BAA143B4D65E131C", - ctx: "", - signature: "2bddcee4a9ac1b9d19bc1531365c5613e48b95a530339c52f5fc0b671ee01b6587fe08b290c191f82eace640fae216cca90f40fa93bb309e5ff53afc5a042050bffeb69d4e0041c34fd334a7b576c6ebae68042b315fe78f84300dc011cdb144252a06f35c9a2b0a275d00552f890361d1e7b7439572097d1f3d5833b98b751412deb3c0df23ee3a30782919444bcbcc9ac25c645180c8d1a9fe693086cba4779d6e31b1e5fd1a0ccaeb055c471ef065a273676d78c6e7feebfc607c5578107ac27765345363af57f77431e306d9407ce365708d813e44d3107c108f8c5f2848913a4099f6a0ce2ebf80c7414236d4edc07fee79efe1ab6fe70217bec958ea48221fb6b87cef6f5762b41a9c698fbb45f5994969bee21e40f20c95f9a18389e8b49d6ecccdae9f568313448b5162c981bc9ff320753aede977b15f7ddc68bba076a07deb68ac36826e70d80752fac0356d507b3e283b350a17b015f76c71314f7c2086e44601c4d9c707c99a36e55716fd34384a218242a69876c8dea9f6ec28c40189ee2b8608040a96a813f38240dc85a511b3cebab1ea893270e730aef2e29406fcc1f6826101f1361168ceb3f1632e8ee505143e0f031e744928c1e41eab923ebbfce5e9f159fd160db737b013759382274a1d9409554ab06eb72b5ce2a710d8b08f163df991c956abc823d21b0a6d9d7ae484d6cdb2e04de7a282ef317f488e20d40e4e67cbf05d1528c0ce261e3a65163ad518661989484bc964da21122a6c95ef7036b3273f93833d068d9a2c7933e19ad2fde8378c286ad57f7e22c8aa4153718f356b46b34ca4a986e6e9ef3ddb206693acf3b08c0aa5118c8efbb6a99a0fc0674f6228e2f6f53e229129d4b6c0fd6455e28587f0f169270d044398e2c377ab6f985f7f2235d68192d031f39251f9f0270451beda2d29b654511b73bce0f30174a606a1100f4fc984959704ba0e2df2bdf642ba3b0241ca9889009ae575540c71747246835018ba8faeb473b39e4ede5a6fbe0165a4db2e90739cb051f3e5c1c0ce159539758569ecc8b67a759a2e786d5b4e96834db0dd460ab1c5c7ac8f3cf19596979108dfbdc6c8798e7342026b7571ba64fa86d68e1d5179af5768a7e22db76f3a319193d04eeea626d03d4be7dd83ed6c46e2149a1e2428e60f6566833726bcc93d0b342ff4245665b54167b013b02165d29fb64055112c5d800e853bf28bf149da2284e49dc1c5f478f490ce1002f16431564534244cd9c1e0bd12ea9209efe572afb7effc0153e5b2066b1af0dd05a2d9da13308c90a0244ad95a33a64d07a75d7596e46cdcfd154e18f7677ef16b979a1f431eb59279c65a3a31c5429ceebfcc869cf538722a82e6e765bd6a3042dc825684ad4163e054c113b47276b894580db17c92f0859c240b6812716e408a557c1d7ac8cfc3abf9b43f759bb2a13c903bba936206496fed37696171717cb8995b837e869052c9c25348698617c70e98966d30f56bb5bf41369c0131de4178637c5684a64992b50f9ab9a16b51752e14ea521d5bd42bacdfcf100085629796d22bcaa6a7830f9b5859d75b290a0db031bd51b77fa7911eb17d46dcf16d45e3b1ce8fb7dc19ba969a724e43ccce9487c143302c79fb208b2ab0ea1060af3809d5397c11cc0a79ed39ab06c0ef7bceb8e094ec0e3ea52937442ccc176a31b07a52d7de84c0599dbe736cecfacbb41cac206ec3dc20603606a14a1db3ccad8037a540f3213ee9337b529f447ed2c2e287760973b8175f70c78423ace6179757c519c14a0d7b81af00646afde573a35e909ef9660623cd3651b63b79be508698888e1d3bc87344067dcc099416616c273d5ea0e41a8723d11c5ec49e0c6019f6576a2e87d0237c3f64d49afbaf09fc818829d5d0fcb4336e08503d570dc3c96fa71fd7b2d4f51d0d1acc515c2c4dad23896273b2616f7d848816c5cc17f7bb2ed5f952a708c38eb13f0fdd8f14ec71893d3f19ccc8aa15fed848583093f4d8246f36db300c9cdd5d63b507641e4d2ba9ec284d17f4a05696be00da371bd5bab0ba56a13451278dbdeed02b4093e482b96269707b09a10dba9ca1dffb4e4185f9e392ebabcea1f9d26c037d9c0dcd71f2bc13faf559d195ef0d8be7baa0cf8aa105a8069356aa2287ec1003e0759f27b02772d4855bf0b7e0b3e97b7f77bd3119669695cc706090991e6a542eb7a28547978ba8a05d9103a90789108fa15898d14982bd0d5d098019700b37939f9f0d66e78af49c6318b886ba2a9101c072bb1e7bdbdb7319eb38e3320b4fbb1fcb28e8e7fac0bd493faf3c0547214465d55ca212dae99dc53addc3378b7e7b93acbdc1c9787149ed0214cd9846852ed7c18b23ab0ae8b7afbb725d44997a38422ff17b1dbeb22e2387094e0bc59496786114d0bb398f36fdfb06c70ff0ce47e9c3eb8c32c22062ccd5306026b606a9e9c628a377f0efd71087c95b3c1ffbdec8a91f311fbba4793d3d3fbf350e6a4a491d74ab7fb0cb66afa0d66177853df464f0175cdee4f97a4e620366aa18c2d04ebab82ff31ec07722fd53e0b4926973dd41d10422747261d8772b18ab55e0bbdcb89e224a5fc2679c2b729aaa4e1a78f95cf68af3562b98f0586d02134464f87dd15b843451a9160c5f4704c994a32259ca623c937431cfe55ee97d736916ac3e7ef831a1b6978539ac6c3304de2f43b3b208f73d071d17fd5cae631f617929468fc59d529deab1c0080a85ded180f9b7029376059c5ca3ba2eab9ee556a74373eadb5983f5990146b04bd255f2380450864e33c478cfe42072eeaabf8032f2c22fbf111407bf2cc41f6cc55596cca62a69303089c3ec231f42a8358d8bce315debce8cd4cea4aa478fba3476b5252e2d64da6b70d6ea0c1b4a99abebbd194e62e25442e7ecffc788710ac6dd1da815a0a5ef8dcef34ebcc90c6f29372875d664c2a06f3485dad8bd00d94837f417412399f9f085d8666fcaf38d38620897e2d06c7399eb28c5db0ffa42a233fdc6732c3e525bd49771aad03348aad068a5e729565c10d343a10c5cd6e530994c9a400354de3af3b39d20cf2b54fc0c9bde4b6f520688694d9b53c3628f1744b61271383d852219d8afae7a284d2f0b042f2fb70778b53d30876b5a031904a241e5a1741ff2e88bf8faa60472ba66111f0560ef127ee86d24f2c502fcff7575696f7f450109776f0f5e1bbd77a48952697fb2f8657516cc061de2bcc6aff9b861356b97e576f78abab1d3acb70d14a84b7858c22b2a0ee17e1231a2da3738018205091b263c42597ea6d3d7d9dae3fa030a13162243494c4e7f8fa1a2d8e2ff11153a41474a63646770717487919da9bac9d2d3e3070f23455d686a7dbcc9de00000000000000000000000000000000000f1f343f", -}; - -// This is almost tgId=1, tcId=1 from -// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, -// and the signature value is run against bc-java -const MLDSA65_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-65", - deterministic: true, - sk: "33D8A5CFF7177A6669887A82D35456C31B6CEBFFB49417CFA26E640F7627C8E3BCCFABECCC0CEEBB8A462A6AC517F6B2E386A53DDA5F2A3989EEA53F683CA9805DDEB18825025F315380D0F96291E40AA467137E8C4185A7DCFF7CF9DBF08F45A6269B7F051908C306631DBC89B0ABB4BF5F10B8F9EA19DB6CD2F48B3D38A2E0273727477428010566515638554515216834816051282348218870587013566503644716530628602517465223643645550067784460507720286463316005616610732234638424228512408337208702433435754330357376047354326476184045535786465656674247346143438482688422011862806767567383382285676617058574213411688764581637013318547354734887883708557278262077365550557503452028533403703004648070358844785356358487834215416426775703427133133236781744852361285115886431108010634088154458713476356015071573042266372628108262510567072064362420027802680366165810741225150627570138238281785718406177020845270051677362327744053303227123084874366146004450211430723442261333076351523386160421855507808363277637181007415832342403677180301041578630050520713005541135137475245850184535184176362264418761512648068813127662265152618862078120728642374432730824335655607315827755747726454718158412336872107773013207836288672728370814036773300868236421132338731523050867761021330801447015833582481800283005202327303232807671062125030140732562518011765826627027303425254318053742208783518750016352480413316650612045537378587784633728024213374572513681408283411650380721274775044587701825553370467451700784253780360286440535063651480186442171372515287074876560778652208204461450408623670873460346523463316718281475104443784120488786143871820533664788478571233755626275660065133817206422771334835432544521603418861237426884555226364207088858254614842864105445787530634112820241334274151681670875150758752562088507516286202168543883278757311616382386158603525223802243623050026716785136643464150717807020010286210788766086622323683882184134014711655535804602170017248260635707715877378153207506466503844515852826615142321766415534207576152604856724528626218236615782785500717721844726200246215770657843300036636080300477121366482174876202583100006827548665637404614862557465047638367850376644120018331275341261867333225176300656433713063104154640654056058700177307226514863812627620630083163587700278222726588277200452033010612824567283465475153444485577351833474725322473632565743627410676736271787062226870704160185012102245237226471486076876330381131655044207821874411476281520838305307607064412877170140433473823632568180611602503654402574108261453430167362057001838847111780217257070343047758760288121207114845007146540866470261038857352671644847044445317525436823406368660147121262615750227815048422550217376753870138468425251285332817561016386713867700480441820345631826356008267271331738231022801476431676574645400562110810476101554576135401216673230435384264073475757458656363553743522084721217014410764674564505065437358742011335128131124554517633120542562443113607882671836081640552841107365661528565235234042777374871876265852315343012883617874225740620116181008070517427524673552334603425545703157110353804704060361855533235754261451420545323608233551043162051728C01A5F8AB51DCE9E0DC972467D59FAC889DBC380630FFB9173A62CE980F4132E71F1013E08F7F0DE6BE421B108C254D2331330BF33BF3601BF4CFA26F25187C6DCBE228AFB7CC153C348F28B686F10A0A77A984307971665CAB8D2BC757285ABA463F644B7F9F6175FEDE9B9C39C45DCA63A90736C6C244F0D9FA5791DF6FAF4D75241AB9F95F338F7810BC52BED49D83B5276BF523C806498B31DA4CA61F9BAE63155B3FE0FCC79070AD424B4999D04BA89D0A45B7D8E83A473C4875394C9598399D8088EE8DB4A2FF1BCF25D17A804458E6E363D5D84AD6E09EFCF4E676581624B3A20236BFFD108A72C385E554A49F467B3F342867AAAC24ED514072618047EDCF74547D86C7F0590746E9C383D199CB8023CE69FD5D27C30830BA051F9F138919EBC7D94AC281F9AB1B23A7B6485EFC5C1DD287452E13319FAEA290A0DE3D463E57D15EDEF8F0AE24CDAB8515A73792CBB9658359C466889FC93D127D19E07B673983AAAB411D90A8CEC8C30DF74AC3AFE9C021AECDA04E93242D24E26D1485E278F1B447826696E3E8ADCD333056300769ABE8C48C3666ECFCFC260AF3165D7A6DC0D5E237E5F1F57E123CD65ED40B8C1E1D77F77A46ADF204B97B9C51FC306849476E735253392B9CF2EB1D55E4C6A21530BD5859D2B595B64F70A7A064552CACE2CDECE83FE31DF6CF2CBC70E3B3E405ACC833BE32E6C8AD9AA03042A79DDFCA7350B8C18EB97E1CED6E54AE0DFDFF68763929C30F19D9C1DC55168A0D2441B2B6FF247FEBC8305B6B685877AD6805226F3DFFC3CE6D5197F776BC2C1EF5825B8A6FF5E59515280DF3D38283A490A967FC0F3B62EEA117D904A814B794BD44909A7703EF168DC74F79F361AE9DA66D3FCDFB72DFBC6ECA59B6F6257C9DBAEECE76B5A6ED02A763F2A35F9777D2D9D38243F0E4FEEEE740427830BE2CE5EF6E4533BEAAE05C173958DC1CEA90AC53A968298C8B05D25FA4184CD0BFF38E82CDE19AD01D5585758836036793DFA7ADD5016B49C05D17DC1A2E5BA5E17B672A2B084EA397DE3E9EED9B511D82EB3C3422320B21F476369B6C9D5CC4A594E45EFFB0B38CAEC0867FF40B25C3CA55CF1BA808C1F70E1C04C1567B83766CEFAF51DDB4C97184D6382DCABE52D995C0A50CDF2E248762FD375393F46AE05C708FC3BAA9B4C4675CA0BE7C30D6D2DF16565522EE657EA1477F1CC18C5C4193B08EF9564491B878336125681D40ABD9D9944BE878D8248F40DBD488FDFB79122DE024FB1AF31515CD2D5187057CCBE84E6EDCCA9F36D00385783FD22875BA529388AE8808C87DF39A0B1ACC7252CC9814D559E08A57D6BB0ACBAE86F1781187D7A8DC441FDE6588B6B916AC579A4604E590AEC765EF92DAF6E855C0840568F06E5AF727E290E2629AF1AD79AA775DEA7912E92A7D68FE5E6755D69C459090749305C77E1B2E6897FA7571ECAFB234DBDD928C688FB27445BD374872FF74E1FD260A3EBCFFE09AA0B6ED7AAAA378B4CEE183657841EAA0017AA923DEBBE7F254E979D1FA762B6DC3B16CD7B1C22C86E2365993CF055929F921B067F1F038617E208AB5FD4974F7BE884D8795EF5064E22599E1AF1D7EE995DC4C8DD8443EC839806D5C7DF08056E3B3CD73F0F95D27918D39CE99199498D084F82A2F37485F1419935F83491365429C029F91AA5D5700C4D849A3C2C2E476855BECBCD4298C4DA69F3BB6C2DB768280B9393DDE219EEDC6FF661506164E141EB50149147AB83E0B5EDE1AC4128BDC4DD49EBFB9608B843F24A44F639A1296A483923C8A967D0DAE33FCF56A6D24D4BE8BD95780AB94B5B76D865852681CE6E48DF4B71BB9DCF35484BA1A5AE5A5E78A03601559705D2231F1DFD8D2F09D9530A63BA79808F9B04D3C314EA2BF24B0F536F33D257BE9B946D8188B913B5F26B10649BD847BD34A17B4F4B2098992B4F6B7E990E5F64BA157653A9A7DF7CB639F13C1D8375861E660C7A1250A207B5CC229A19A0CB735550EE49A02CB8752AE05E5B2BE2697CE8277BC0CEC8C782E1792EFD599A6395623AC41B61A7D9A5610225B58E92813D6B429EE7DDE86B23F25A3994711181E788B65B9C01D7AEC2FB879ED77659D24793146699696F50D77FE824B5B329E40F356C7B2644BEBB8217FB7FBCBCF211976386E7CD866E8BA3BB8571D500B178689EE7E1EC92729F0B0F77A2E1A7151CFC7DCD8D70E9BDDE9320E2C06EA0C2A287AB3C088B3FA656FA1416CB35BEEAD3FCA15D9FA8D9982E9999CA5C9FB9D3925005A4A81FD6FEC7044B52B05CFFAA84E0F9BC31C1A1EDD82D90F1B71986E7DB60D7455460AC2439FC3F3358CB0906CFD2DA7A31FF984BC30CFA1F06597F7978288535C07102D6CB43E217634A02E547267377764D494F09960F8229F167C5FE29C16918430B9A860C72F5ED97572016D41A695FD194DE673CCE7A1EF98E07F30811E0C087ECC99C441A253E3755054B8AC9A87453DA92ADC1FE76B6F573F4EF75FDFC66CF00EE6245D7DCEB79425FB3BA8F5E29E3295BA1CB9D001DE4D528C6A79443AABAD8F3E14900EF893F847E272DE20A365F4FA5C8669C7B31DF56612CBD8EDFC6E9485CAD0AE958FA4365F1D13DC7A6F08D1FFAF665AB981B75D84AD00B9EFDE1A319BF802F755666DF5060B275B8A5D7F7F45643F96708B95D0D3B120DD0D3E9B7DD8F67B5DBDB7BFF989A674026B5E72EBED35E61461173D9337E713C83B2D851E4738F294FD384256AFCF2A0212FE5058AA3C322D78A5AF6C0E3BCB4CD93DFBE8D5F148240DAD94AC862F3ED722E7EBC65100256BC57A8A5883B505EABE064FD934FAE90CB02177BF1D2AFEB3308CDCC2710A53477E46746A6587F4A0AB7047D73F10ABE309BD1DA49A54C2B1D2F50E6C2401235DEA0EBFF75AD50B4F861CFD772866B1165A5727417DBACA8162513CA76CBE71F6F91DB3A171089FF3083FEE15809173DF9EC51786379DB056C00237C6D2F8CD8116794B691E2F7E61696D36CAD018988AF16DBB7B386391571D755ECE4D05E4BF788C5B0D63E4C9F6D57F1263D47CF98548674FFAFE322E0127635E4C2FBF0F88D7738BC5E243D4B7BADE073992FD8DF8ED54D192C3F329A60865333B8C390DCF638FF4A0FD32AD72E2B32050CFF2E9B3074A8906D37BAAD1E06087100C755869264A68F2E5708E658C178738B11099FB7BEF45B97AD995A4FAD2CC9E0AC090FE57DD5450B688208C4BEC90AC69B4CD21BBB669733F5435383654C561CF9F3F0DEDF40BE3449BF14DB2C91C838F6EA559EB0A3199CFD9AA6D3C97514AE82BA5E03317008B4ED328ACE55C2F2F4426DB8036B3075713639AD16BA50A4A85B1CA9F099D12A236ABC5F34F0919A0A2C3280E4074D0D228747BFE84177B75B62DF8F22D86E1CB61F87A6C91E3D2EB46CECBD5214E3A9E615B96E21A721FAE312F879E2863EBA3F97401D2288FD00559A7C7C9924F6F0FE13F66C32B6B2BFCA445F738A7EC72C4B42CF89B71B39B01669810B0798F965015AC45C57", - message: "F7DB318231EFC5250F7AB18DEBC0340C53", - ctx: "", - signature: "bdbabd40bdcd6a58a93b43f86f0cde1ada82a56638b5fc973b30f73fd12fbb909b4fbb0f408c86713409457b981676265ecde794bd6797e60194fa41c10abfdee2d8312102f19bbd2edea917b4648040c5e95153af4d886a9ad0841c8531851843310775bebdf127b825cd35a7bf9f3d1fd8e1428d3d899a8470ef90bb99525bb44e2b2714028fabfce69e2bcff10c313e46dae196db53635c846dc847d2ea49c4bdf76873793770c1fe1c2949f3a48c26708cb68af7f32ee1126deb11725ba281bcf4fb390e88eeae0c350afebd9fefec4610f846aabb9090f70d63a087a397b1081fbc1b75a4faf287a4361574f929c5272431a71eb0987691906bfc9f37d71611da9fe7a195b2da79e222b189559637fda2cc12b79b30762d773d2f2284ee79a9cb33a158df4583e28bae59f6c5e1094f6aac8b0764ebbdb2225a729e9ec168e8d09243b70e36f1d1b5505cc83c156f886b7e94cba7a73922cef075097aaaf302869c0977a95eab95f1355e17b34813dbe7a034779d8711fe8a3783c851cd663c62c35a38a0fec446816e93ae247e30eb0e97dd2ec18413f2f1cdafce820680b7150464ba9b5d95ee503bb1933df2c971cab1cf6c64fec3d60873db16c6191daae3fe74080c575c57a1dd6c710bae6ba6ae6b5288f3e568c0c1a9257af4ade209837b03e927e980169d7ca4ee21fae3052be8f9b79344a9a175789765c6573132570d99826651d1940b6220712cdebdc1829e6103d0d43a6f43b090d2d41ac34ac24b190774ad39ff00c73a260a72396273f0cee15951a29e58342fbd24e0ae8f2e2cb139b94322ea8ce6a7a07f0a69b318318c3fb1a7eb6682b3f9971062905155ffc16d54ec0ff3f448e21dd16ac6640dcaf3560306948cf77ece4824bc9e5da85d19c37363c81f9f59a4e21d3ec2877046ed5150d5aca269712950f39384c32a724443ad4ed8f24f3f5fb663e6606cfb0cf35ecb9cdc84c91486c1883897c629754e1e60ad7d358138403f8d8c90f46c69825aabda7cf7b5eb9ebc72f141cbbc7dde169744f55c91639ab2bacb67d7151fd778771ea579f1a60d229a33477e95fe1dcc4575a6a9d01f0310a421d59d745f17cff4bbc87975cd0054fcfba61a792d818822fc217e90196d162250b2c33e99bdb451e497145127b6e93bce222097930e6ebcf79c0c24d193cde7c59abba999c5811912166475494f2bdb3dd27815863d89388f58d2773fbbc7f779f8f2ac32e6ba08cb13a8e014ccaebb017998b844330be4deb8834fe406d5d865ce9da6c10c5c15e9beed48cd08a9896c9e67eb27c379bb2ba23da5960ccda966069da8380067f01cd6c1ec1e3f44dea949fb1363b5d3ea500e78728b17acca6a4b63305290bfc0498cf163883ffa4b5d92952b85f827120ed4cc07e1806ea1e14cac54df2f51028b21eec040c30b93cc28a0ce33cdd99f9490a6db0383f7f21e68ed67c645951a6769aa3660986e0ae312e12c7ce9ef992123eb16093c15d8ee15f7519c0ca8548d439c6566a1b919585aeabaa2d196030881a759a7b3d14d4e39574711fe2eb8bf9cf43d3fef9f242f12446adb6c46c339fc6f2e6e667c379f4e6713279f6d62701ed6d8aabf5ef2ea4b4bf430424737228cca7774d51933a706e78dbe47073eb4f5d30c6d26f79541ed6a5903f6de33c925bc244035d3fce56b239b2f133b98273db64e9dc57f1ca3592d32f80724e62f3aa9357f14a47533afc1c3d53b5450884fd8faf1fc524d008916e81656b5814542e43aedcc4a0cfdcee21ef0eeba7348139d4ac86ebd091087ce4f4c240f350309f784b4736d921b60a0e7a8a967fe8a6b4b972f5cb4a069e8580beffb27dc0ddb36076cbb5a667d3470c787a34cde0dfac60943e51f62f8d9706abf32d1af89259499037d8182d5c70a3251cb048c9c9fae6433ed74302b5b436d3d72693749a6a00ff70b1d7fcbb4a8c0d9e1b13a17756ebe4be6c2066e1f6ec0b165c9f9997ca6cab36a1527798e4bee1494e0ea10e8570649b5c298077129b4a69679409ae5d5c67a0a1b69ebc65a366a0b874bcb92f3d4bdbd8e32058e0014a33fefc6c2caab3b1ea52533b4739bd451bb0fc6a02a31ca1c988c281cdc42be7f0ccdc28ffb1411946ee724ecdac90874b598c200d101a64225ee7dbeb9fccb1e546039b624e918d7c3fa493667ac6bb6f4b57f5912fdea9e8ddd0c0328e51434598b31c1204182c26d60ad8fd000a73b6d8d696f9abd120130b340913d67075477de651fe3aa24dc6b6527cdcbb891ba1d18d546a2bd6db4b9fa3a9e467a162e1649e2f00a4ad71b0f83a7c309cc44adc6564565cbb08e2d5ab98cc731ec99bc3517f06fc2ea3ae25ae44b5cb8fad986996da5e17c38528a43025410a650c80e58bf9e276c510052e39105323038261c52b28fea91a1e164e10c06103dde38a88530806d9e6a0c36843a5b1f1590889f1b7a1ade1cd4c1f7206327d0db3922d5f199d26dbd64d548e7c3782c7b8ef0d5e43802302a0c778c0b8a89e4a37ab3d2f3da06c7699fcbc52c581f812e4cbf1b0bb9aa49104fb310dff4bc6fe9b345d0aa4839b58f401ad8451db7faeb51bec94fad5f375db7d1d23025edbb60c84a0f47d8477dde3c86949d355f3e0740ae3267f54c89b57203e4e6dd5f63f4b745acbfdc42592766d17a278a021067ff9b62ecf3a6afa56694675036c7b97eb7eaae091d0d79ed6df22e6cbf418a9bbbccae51c6f4a7a4e987c3640a0869094c4b434d571adff4a59d75c955cd73be82b8d89374be0ce613cc6b586e709208a04c7e0c3055818f86cabb0a1a74fa9301fa90675f828d29498b59c5cec20c9133a8425fa3a85f7116e434a5794a04f7f4785354ad580867e50efb97e408603034a2075a6ddfd3831d5980e92732a2d8cf01aab52289b3c3a7e8d247e0fcd29bb8d0f00b85a544279d08798430b0739da59fcf58cb2c89d764c4686d3b5659bf35bc9222fbf4ca56028b449f75f6a70f6f0fcf6efcb55d327ca36ce32a9dcdc997504b85971ab52c8f4b9927f2cd9a88ef10dcaa1667e3eadba90fe79fa1ff4426298d211998c12981b490c0a725db382edd29679e718bde3f6bda658de0e22b8cef0b0bffd02e77c561ed0b4dbc220824fde92ae7d021deac8b7856810ed50b4b03c0b40984b3a50ab3afb93f55f08e27f20973ef42e80704c3b7c964e336ca578e3d3f45665c6335710d0fd82d0ca0bfae87109fe599ae7eba667d45397d1f4c4076d6a277fd67f4201ee2301624e4f9c9901b66f6372d43ba18843a1f368c157dd135f66e4169262dfe777674da638f5c691e602e1d87188078adda48ebb215160d3d068e9fb17d3fb36f97aada5b55c138d379be4534b374f70592bbcbcf3e5068354dd470a7834828d7214dab2bdc5d8544f274e39fe810d43f7252a48d7699b31d10b824090d45e2b0260a69ba4d614120128b1359ed56229400c21efdb728a151a108d7f43b588f3472ca9bc61b26f85fb10bb3afd0d38d52794a2bfbd945372f5ff1d58f8218cdc3ecf0d3fbdcf1d4786ccecce1f270a2b0ce87c2733a476cb1c7f0a39999afebbb23afb9d8a3d40617da3677f3304c359023292633f08b5ded9264403850a6b93beb18b54cb9f8a53064727e372ab57943fd876a9fca08e2544ad9f48218ffb7f379f1833c864c3b873ebeeee538f64c18462c156adca54fc373b52e77c7cf0444ba54971ebbf1c0e8ea2abcb2914432c630d2a9f2e5746a1b170a6eb0543d8c952452ba9a8852abcb17c24035479e0db4105ddac4e5f237719a4a827d6ca109a8114858ba89b2b2bf9a4cf14dbd1f89eb53f8162077ebe91f146e67a8aa33704577abc5116b05303825122b74d62c78b9463d69a4e9e5fd2fa560c6a05f92656d206b83d440c980de0b595da9b2891c7db73ec75e0be2eb9305278ca2f3d12f9091b063a4e97d611b4c53d28ef877424df2809142a5ecce0a513a7062f398fd74d80e5ee86f60cc7327ac203a8cfc3a9bc74c6c8e14a7dc4767bd51eb4584c1b1e169908c3a2c3c4501b0164078d599ef1f9133644b94df3cd3fe9a2aaabafd38aeda4666406826c26a4768c8f163a9724c15ec609a8e58ad9a14b39768da81010c549123ebc78a3a3045d2701f45e8db95969eab9e1725ee3c035008b1a1fb404e0b485ce0d206a602e6ccfaaf09553c027fb35cdd1648d1dbfe4bf2ac2ed0ba4aad7a0c78ab7f4d2ba9d197683f295f2124b475a4e54ce11da5f2be8e5d3ca9cf9ffeb9323c19fba40e41dab669772ed114ceac093ec20769b251d97bc6b261f8583a4033f66e35f99bd8b44fbbeb46d31dc7015c94e5ff13754ac4c80efa5ca98ec1e52ab8616b68b018609b223005a7c7651251166847f8ae0ab135a93d46c1a9f2c3b5fb7c4aa292d6b43aea753ad75774b603766f5471d7dde8767c1c8c5975c25fc2567e2393ccc9c235bc928a32450b4add0460469655195c4a2b5d0ddd5d4d2effd55c02a812fbcf5d317cbcf249fb5427f2fe20f3bbbcd2ce3ef3fa4cacd528cfd38a84715672a6487118ffab47681aa130e13b9ad9b5327705b13a80dd5c1a788cabaec7d4e04454585b848bb1c1c2db53ab319ba1d3fb4892999dc1f6071518273b5688898fb5e800000000000000000000000000081214191f2a", -}; - -// generated by hand against bc-java -// This is almost tgId=1, tcId=1 from -// https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, -// and the signature value is run against bc-java -const MLDSA87_KAT1: Kat = Kat { - _parameter_set: "ML-DSA-87", - deterministic: true, - sk: "381286DCCAFF85CC31536E8CFA4D6FFF74AA12D0004D0BC826F54D3EF0F632090C06B8D3D562D3EA6C5D9E486AEE773AB7CB8A08382A055C6339BC650523ABA7176BB4169C6294FC52FEC2E43BA13210D3CAF23124DF2D0B4F7BE6327B48DE145071503F0E9EAEF1136A0005F6F5B03FCAD1AF83C10EA38D288934417E86F3D69CB69044064090063248C6895AB0019B080D99189012892CE008495A0609031005C42668C0C64540C80D80164D58388A18A40C0804720AB55020110293B09061348D4AC40182006E089750DBB02823938CA28688421000511066932200D8A2805BB05003255023064D9B184E83448282148103454A540020DA048ECCC24011904D6348801C004244300922A6314B860C0A4985A1184C18888112442D531690E280249486488C300909997119030DC09820E20664618089E4B01041208484349023058601A83123A63103050464226954006C1AA930884052A082402221690449411A83050B278AC4003200B521CC345083A20D60C84019330E00162941A82DCB446592844C024631611608E2842988023121B4844B288921187188C0011103201238280BA6112303490107629C9625C416445236708338929022084AC60C1B00648202324024315B867100104DD1228E58404C1082119AC245DBB204D0084E5BA20D20245159342821B004C8166DD3260419B10100486010A90CD122489914404C288624080E1B436C49A00C59942051108CA2466822A1714A982140366E24A1251C3421E3060A91C40098185223865114C38DD2002D00B03189A8409B940860A0701898488AA4449000621295280C10055292705A260A1C80211913281339421C0146E31030D9848C13202D99462E0C10881384302431712447418132860B948110112C5838898314825110711A982904260C599230102072C23666CAB669E406861C8469843012C49421241820DA12520BC3214B900DDBA004118808DCB465123085C1448E1C11859226128910840B1864E2286998880C90A6011B08495C180261C88DA1184621C109199389244925503625198490922030E28651C49685CA460410456A1B3862632809A202641BB341CB369054262C1C054E1042718348649A4605A2086041A2900A110151366CC49208C0C444A1C080D9408404C0708032851C43608CC4309CA6800A404E63481019B830CAB82944160461348E84184A24181013019188362C93246612C06463961000C3909B9600438685E1188E18C6059CA064844429108445C9000281266AE11804E01004591426E3328023C405E198710C16328896640C33825B2245C9082619236E44224C12126AE4B811C98845100732DA40825BB2316392895C1826218265639820D30022802220DB9624E29440E2042D2432424832295828461A9145D2988D21999010112D5A866D93A844CC42481282288C988422A791249001113270104045E12610A11411A3820D0BB64DE138019A046124C76412496E1BA004A3B08C49026E5C0069214144C8082913C83018940560228A0AC9258286444A102509361018C32111302A1BB5092047614C10511A422699A40041426ED40681DCB48122020A00336D53C284138751D9C851C238325C809101174013366D64A86019962022A38858B02C08078002170ACBA670213805D0222624A640A1A08D181350E0B42CE0225001A6886444011B29890B000092026C48B62508B60060926019953110B311D022215B006C80B008D18205133924DBC049544820C318069C102AC888645A248018C04D0A038010A891A4086461880C0B002A02B98D24220EDB246D21C76DA4126A58026190B2311935858B462102C144C9864998B2641210486110899896850816050B1980E2C6080886610B040D1A1880A4B80104C52564449021109291202863242120066A4CA208190222529491120886933206C49211CA000461162A00000E43022E98928881228E0C456511882102849149C21010920D8B126E10B42014234101A521A2C220813291D2344580324A1C29824906824B2060001060D0322A43208050342D1013010A094ED0C06448262050886013A07000037114948420A30D21055282A8911149125A342CDC880849C8288306650B16501489048C3626A408709C908DA0100AC90060D046718BA208190760CB8085A1008E4A222A01858192844C98B22164C8000199887457577111119A9C0D695E5008224F1F8D3B293D5067D12EA5D9B1ACD120060BE64483AA869B34C317DC13C55FA59FB1939381A21B8E043795F95E4ECB1592217A42B07F3C9731ED0ABFB2CD487CDCF6F956D5DECAA394378DCCB079CE795E96A9BDE857502A1EE1DBB3D6400EFE25F81EA9AA71A2124C05A0E97A271BF8B166385A3AB1E01E235AF62546933E80906B47627F0DD508B22CD0FB34DC2459B21C1437DDBEF93F603CEF19929437CDC85AEA07303F84662E64973F65BA31B0142FA866EBD1CD858AC538E9A598EB1A2A80D463E84125461926FD53DA52C9D53EC1C8EE2E5FC63A7D3259FC69C7A973421A9B4B3BCF4E938450C17ED123D7428C6D17CBC6EDCC4673750B726A36E187A42221BBE039A9EAE4A01528B5CA3074A7448193E8F29C65AB065A551FA7827ADD240C06AAD74692AEE6C44BDD6EAB9F0DC88D3564C3EA09AEA275507B8B085DC4B3BBA5EE7E9A35A3F25FBF2855A76383DC498935BEFC14D32A0EF90EB2F604C0DADFA4B5C7D8FD79BAA318F68B2A529944F87CC4FA80F60C4BC351A95E00EFD0A0D8ECD2F998F30030CF6D9BEEF2F7D63077DBAFC7AD5E7667F8FA671E5B69BC0B0435C80B28ED28D8D884FD60766E030A570282D8110419820D437FBA52C82CC99C94631B5C8AA9B681DEC98957A2E19C6123721B0DD4722D62F7722B3678E5752DD91C4D0EF6A932960EC1312724C56F58F21D0BFCA70340DD4C48ACC5606387663313472E8194B65FCC048A858D6480DD9C2B83841E85205E6C5E9A95776BA29835BBBF8E567200C84E5499CDFBA85355A2067A170BB5897889A73C204CA5A93118E51A4595281A80B952CB35D5589673270142CDE60402FCA4C8D9E437134DA79E7944522FB72D35F3FDFF2C15D4585AF10C3B64A7A40F2418A336F4EE66CCB98AB0D1B082C62802C87A9778B86D9F304847A76420507978FAC7987E8BF667A8E40F15FA1D9B0DD6299458BA196D24E9C6906C26D04708B08F99FDD9D110094D5B36F48213B213C66F5F55F97D4BFB500157E03A074AF090B7609204A71CD1642D11BF34C76400C124D55CF7A80DA98FFCF52AB2B229720357ED071616BD8E03D7276561ADA1A746C4EC37C0B21F964DCE7B1AA303A9CD7CB8108D7E18E32DB22BCB89FB7E6408324ABF03E296572648FD2E335CD4463A64EC5CBDC8AD7953618D8B2E309F04A74302562FDE3CFFE805C4BB4C661EF131D37674AD6F9C5BBF2290727AA67EA3848C48E3C3CA5A35009FB91C0CC383005604C096422407F4337C17D63D885592A5F15F9851F5BF27EB5AB5095B95AC69C2F36A1664B0AE71E4BB131C3A576B41155A2F997BCBCA494584788ACF2F5CCBAE28D09E69F3A39C2CC394B3465358E1F2A23D2E8A3083E3E26E90121553309A1B8E24932CB64FE659EBCF38AF0D1A8819FDA08CF4F2B03D79208061E113F9B89A8B9563E5163C6CBCC9BC2048C15E2AE39CC2B64ABD6DD6CCE5D01ACCD8CC08A4F266491446C1CB9337DAEEFC6777CBEB3291F60CBCC3194706492B7F953009DD5E70245180651DA918647F05A2902F4719F6D97CF3AF5D855FD1A1C76C29910D518771C604AD7B802E264F39BAF16AD772B0EC9DA32FCA34A85FE2E2447CAF2EE925EFF5A442EE9DA327C38EAA22B8E67DE4A7AFC312CBFDEB44856F09015237E59D26244631A8B14AFED134805DEF32FF49058C72C2A7A4B77518EE2B162DAF6B90F1C96AF5225453C5E4D8CD401C6FE1C626D4CA037F380BF288DC428B9680B595E10BDDC9D8A2D2BC85DC316BD9239D557535F66B3802FFEA1095DD4DC2F1A9A1E6190619BC5A2E063EF8C95066F5AF92B3D99A418E26C68F8AB464374FF3A789F07B2C5A99084C7275DDC481BC1BBBE8BD6171DABEC4A5B01F115EBFADAB26C9AC17A4D4EBE37BC294F0ADE593A0DD4AAC258D106457748FDF0059752442E4B96F1294D6D92F6D9657E131D0288EB3AED6C89390E05DC84FA5EC0EAE33F4DA4DF2A7614FF1B4ADBBBC9ACE849366AB82D5902BF7C5C66C095BD92DABB145BCDF8A744FAD2FCF44AD868C349A6DF2F509EE92687123E39DDA96A735AAF57C09F5EEEDFE3CFBC0B9C05B5D51E2F66F8C0E77AB0302AE603DEC8CFCC79485948C012BFDF7D0B9E8FF0D6545EBC781054183785D95173510E65E0E52CEDBB241D8E23C997DA523B974C53ED07A8DA63958768C9520DA7EE6B7093721EDD8633B6192E12D19065F63EB79504B92C7BC31F176DBF4FF21F5DDE5E414FE6555C8D76A2A77B3B3CE1EF545CFF4E776EF9B440506392ECBDED301490678328FFA2D83DE6099B750C3C99A9797A14FA372A57BB2CB72BDBFC640144D1C100899E2E12F2A23147C0B30E4F162184E1E69C1FA770B46D213F52CB4B36864289FCC3DAD1D54216EF9D9FC81B709D6045DCE41E91E5A0C8B5518EEAAE07D2574DB1D53ACA5B2A4A2FCD6237388A86B4C58B4200511103CCCB2420E62789C13F87C82A90751E121788274C028D2106D5FBB0D9D3128618FCD7DC5B4801C01A2410ADA95A7CA902A8F03E2D538D783AFEE2B7DC674C1A71DCAFCDFFCCB00273B942D79A5435DA1CFB2C44C3FB3A4E221AA1B2868EC9F4FA43A522562AAC34BBDA07D337A12748A1B5120D176CD117110D4A7061BA6AEF3EFBAD21E998B78192214380FD84A5D08F9B11282A15BD9226AA54F019213AB6C0BFE8291ADF09B680A16770EAFB2B7042274EEB896DC7B6903113862B1BD5C523787693E65B6C2A85845768A3BC54DD7A02C4ECE6CA3DBAD2D83DC65CA1DBF7C3744EE4E4748507DCE7F913F4332CF5214D8EFC1B4E67C86AEDF09D545C354FFE6B8A4B8F15E765A8F8CB13B24062A17084C52E82964CF6657C808326ABAAFF60545BADEA8A44DAF0A038EDFA6F4A66C57C721A6ED2E8736EF92F4BB8D230DA58DAD187ABF58DFD17FD11A9764D3608D288AA2EF3F1DE94DD7DF07E829D5D07CAB1FFADDDB4DD460D9FF3DAAE1D918AF2AE195B0F95CFD4E1543B4920B20CC808C6473C58933B370C45E47416E973F42FE6FC9C4FB5FA95DDE7D8325A8837648625F722EF95B0D08803FF0299E6A8108122A1C7172FA96ABB263240FC560CF3E1BFFCB2ACA913A9788D5C02EA0EFAD07BEF958A144B15A902AFD13A0AF976044065068C77100066F68982C0B98BB77F814AB624EED9C84531969DB1257C7AEC11DC682434809C7594A02116D6840DD99630629C150CB0CEFDA1E851F35C50CEEE6689B8450E6E9C4F8DAC4F487AE4DFCC60B8641A780BBD724036C21B38BEB1C8ED30EE9AA03DF899678FFFECE5200DD2270A9332B9557E577BB7E5B3378E4512F0BF439CA3BC16E8575DC46B2E497988C7C6F4EF529A730D7D418FF548AA7710F6F3230503D92A8B6228FD19CDC53F1A40165BA1F3EF1FC68BD5717AAF0810AA56AB90604BC0DF466D92403C1B9CCE0B2662D415EA69A1BE094AA5583DFC472480EE0430A93B21BE5B1BD4E27026DF5ACBE51D891B0E49F70DDE93FF43CE43A4F9B82AB951DE14A8A9BF4650FC38D769F4850688103D0D3E4A9BA645DAEC785A571D573ED6ABB22D987C2B52C5102CAB5B14610196B8D71DE69103E59B5C2A682390290B508A693CF87AD0214DC8DF2541FFAC89B38E19D703AC52D1624216FAB0227BC1045A34FD658111F92DB7B97FDF1CDC56FF7CABDA8BC26EF8347A92A2D8A3C56C150C5E3094F3D34D4CEC3C371602907419D8259DA0C74E32F63A9225852B5C9208785E56D0F4DB3349F5DBD06A10397965453D4E70D2E1E7FE56AD98143720BF25FD8204497D0A5EE1F37BD8AD4364D9BAAF2BE2BF519316D1314E0D4801CB90FE52F66A82FCB4708EFDE4FD45F70C38E70B59206C221B42E840B72FE1FD0DE5C7B0B8C0BD1B6561899FC5771764733108B69F81888FDA9E8CF60044F3BDD3289637F6ACBAA5B9B1145DFF8CE247B438320535C514BCBD445146BC00EF8E1DF3CDE218775DBB135FDBE92B7F303B1C344FDDC2E381420544DC9694DBB142704A0F092BA281537176F17B0C1561F0BE320D79677887DEE7C27E01232BAD43D49D9D1FC1A6D8C887443FDD756EE7561C24FFE0FAEEE66DE9A6F05CF687EF4CF456C7DD624DF017E11F66231793FE81C958AF0DD89C0D088B17DF94304659B79E9F9C5196BC1A9F22CB5698C751B829427CFB437B16FE1E3960A70B223E7C46CFD9F1C6195386A170AA07074D7BF9F634CD834939F8520B73D2648D1994CE80FC17E549DFC87CC6164A9CFD77E6E1968D9C9B582FB70736B6163D1E01C4632D594E3A3532E61E1CE87FE5AB5A2E11F98224E95D7136B34D814F138CC5C557690B57DF3EFCE187CE9A01D1CDDE645DD10814E3B3FDD69947A0746C68D9F77B9AFC61474C55CFF7EAB24AA5643E6B451A4C6D84C2E6E3D7015C6F00FB264D55D8B2CFF5F87DB3050E7DA34F7612EE116A088180BD0E1F3AA1BD63B2CC029B3E96C9EFF4C2D1A2C53F25084503639819E5A6E73BC07EFA5212CD9075A6547710A1237AC4991445ECC76D0C11BA557E0BDBD7651A4A6E66E3F9DBCD532C2301BDBDF63307AB425E6AB902164925BC424C5E7B2892DBBED51E0AB5BF33976243D5AB4DB7272EC292D1DBFC494372B51526C9DC1380C3BA563D2CCFA3A20EE7F800ED8C2A7D199D2EA6F1FF7F4B6B4AC51041C974D740FE7E1EA1D7311B81E5ADBB600CDF62BE0", - message: "3F8599C466713387354B419392EFED39A9D76C0B5E45E079B3DB", - ctx: "", - signature: "46233c459671b74a1bcf6310c1809e658c786c1176ea9edf6563e81057edda4c9217469d69170ecb21d1aeee63f42dd65be9d99d764aa020e32a624bcccd988a75acf7cfea3ea81c26d535b931612c0390b94f64152cb0b70d8a05cf4743c8b26e79d83114e6c6afad39b028189fcf05cda112d032029d53d3f182936e4fc4235add79c3810faa03f030bf6abe0c797b6c59cb2b6453aff5c48b4afd43f303a7258df67f848b3615a364fef4c831d90b989ac8ee2eee3b2b4c39a0973869b97b21f9008b2cee621727c26b79d8639cab08f6370e284c705a4aad931c2f64a000d1b6c3d0f731185ff1c69f4b9c68cf121bdf82ec786c64f925a6653027a4fb3dd430caad6ed34e70332c2f6032cef39d5075f4e02f6683041c2e1d802b3cb5c499dd949f6ac61366dce9fa57f7ac7c6285b05389b4afb68487fd47b5608a6267b4af228bdcda2283164b6a2afe67792e6d7ce03765385ba48709ea41a3fcbe5ea91682aef8b121c15d11d590fdb55ac5912fd5d05e76e71d4b78f38c1116882246927fea855fbb318170bca7ffb8240133cc9030f43c9b3651900f332129a51597875739faeb27876d1f6052e986f269e3cdd7b1a5be19d229dabe063b3b143cdbe47109affd2c5b38aca2506b6dec3918bab1e7fcd826c0d6dbc5d7cb64e161d7f05492f8f8783854c3b6a601724f3dfc7a3ba3e846c7ba52f3d0d98643edfbf10c5d2ac36170e12f859a4b696700cbb5ea7caa558e172f9aed0e37ddc2b5c0af5f522aa66a7f2e1a804ae82df5b5c89a391fb6367aa0d21e97622786a86bd791cfe41b0dde8bde9aead1eff312a5036723f63318ee8b59d7c1cb6e4502970abd0cdfe8841475c93d08b8da4d9b0e236731ca6b58ed44cf256e1bcd1548fec385ad330f60e8d5e20d45ac4e0b3f0e3a4faee1d1c3b3ebcfc0ced8a7ea5a064afea0d3f4dd915330fb3c0d06ceeb10137242f632c61966416f317b189e3708f688f3a0a7b24005aab3fa9a91f7eec42eadb2a4af4b2f8ca5a1eb1cde7ecd9401fa36abcec73cb144584c2c666f45be65ec20058466da3b1c5696329019253bac278a9ef2932c67979a2c094816ff639560a023d18d3e821cde160d6feed78a6b33d848b02930f7579ade1b520ba57abebeab9d11af85e31e76cd2a095bf64c57a78813d9379e1ab4e19965266a13199cc523f29a3bffc03569ba65e905d302c2a236223e5fdeaf9277aa090e1aa0ca55af59e03d4897197d8c56d13e6831931922cbb1521ca523863afa0f579f5f06e004444ca77310e64fdf3093cb02ec5da3319b8ba588ccc231a5986fb381a73a5536b52036f3cf412a6864cbf7cd570534d7766b88216067f672d9360d0acee40f225bc87e3d2a4afb698f263a4c22d199370627c366fc4868688bf56863cc5215928142f032937ca73d6a3cad2c0798316434c47074efcf290366aafe65671068923c8bb912171d87ff2513df764331fea0d9ea75bcb8dc9ee9fd697c4d0acf8a1fa63bc9ce1bd38d20f6e560d563f61437be4ac51de1d3f3afb5ddbb6732cd977cb1027bfbf16f703d84298b07d02f5e57cbe6d964f90a7ecc832afa0062540bd8ce6310ba056f80dd05ae87a272a8cc5348f0c170fa74037c6660c146d3b83bb3310da23c02618d37fd34c8f08855cd88a5e7df4e0848093823c6f6222e26688e475177098eaa9d174cc6108ac99526d334f8cdbd74468b25cfbc6ea92e3a9a0b92334a5464ececb9189274f0815c55acd702340b293371dd4b46055b1501c7c31d82ff47d9ecf03ef96285c35a25caaed59207dbcbc0d72e58944a2952b87eec22084994493759d4b5b93d4471cdbf94166d4ca378e629b8aaae3f702795a24efc973b2b58690638eb26dd0ccef0e8cf7ae703a270ff09552939ef7fc123429557babd92048110d20c839a2bc661f7e214f9140a51873efc9289c88e0187ba30fe4879597bb0090c1442dbc43cf014c41a3f38a1aff1597b30355a74a4b94193bb9a88987ba05079179e6332c7dc266b46abb89e62c0faa333aa708a3659cfd51f71e72db798062b30d92ac830904a78832a9e2e6aa88674b14b041cd8e7f9b73c414ec5d64048fbcaf8613e3d56173f7e3937e617d46db9d2d94a7f3f0674fc63a4ee791a074bb61d69c210270d7a6f90dcc78369f5f4131f306b994ad9fd84e3e6634cc54276982620569ee06eeeb5d44ea3271ba630c613a55fc05699c5d60f89901da642c2a31d8ed53cefc16c476cfbc65c884987d0388c34f4546e2c3da3d589c403339b8d5fda22a5b5985426630168ac5584bef01fa7b8a19d3941baed8f2112f3610ce048c57a08aef4b0f44af528e535aaf75759a90eaade5aeafac33d7553654e9a34d34111785fc6dd3787b52c9e16d9a6b242850bf332776439f3ff9f3e38069e035310014667992d19d54f9ee1aa9b7e4cd78a9e73b59e1c388b2585a95fc79aafcb1028b3b26247d607e4d52470fb81e94f4f549f91986d11a5d94cc43f4dd74d19f4598ca370e942c9ac0f8a36a4acdae859018d9847edb03fe32e12b5642bc1155612be0dfecbffb817304d0bf3f01a2da6120ff154ee52d4199c77d0c4563b8dabd7c42378abe597a98423a5a7259a68f3848d0a2281984f5f9bb746a3167ed9386a08187d8f4478ba634076a440be91df95b322f839fe9c52d4294c1a76efee014de3350ce2fbde0406b850f236d06d8de183e7aac4d2c3925006c5fb804dbcff49d845595b6cab5c0ed1d29126fa4e899ab1840c510eaa3177d2b900b80664be3f6c675c0ef86a1f145e18f01c5b0f8d8c93da1560470529a628ecf9b6ac6762a151baa58a684bf29e8dd419bfa85960b5ae40e650584d27b8e01b6f94b1ced660710a46231b502235fc212539beb12cb7566c051f26b8f53b1d19f46936e831cdc7e13f1d3323e047683d6682920ace728d76eb04131e702c94d2a1bcd88a6f71ad654fc92a3183ca5674598e63beab216104f9c94ab5ec6d34c6ef4c5c49e24a7172c707d4a1d9ea9c8733dcf6feb14a9d3f56bb94f872d835b157c08d3ee0828ce88a64127547ecb02fcc8b8c6516ea3e7941c53ce4394ed1a4f7476c7b5301e240eb65fe7ce2355b766605704f059137dbfd3ee4d7b5fca7d821b4450f066ac71b82f4b2536f994a38a85ad9a734c040c9c25dad664c17aa2fdb9214b243ebc92d789853489564c199ba90496c0f96cf05b8cedc0843c7a816c9ef64d5f6abb996b351de248288936510bd691043076315b7e76c373935380d510c730d980d775d2e01fd375a3c30e4c7c92754766c30035ecda2f26cff9d5a8f07d45c9350843d6c5051072b54beee875ab37ea9d1f886cff4ee2ccb048fdaf9b5eb2146e7317fafa68c488c9e6abd2eab02d045e01377461f4f77b9b3a670f1c451bab7760a3ec1acb7274457372e20b5b5c14e9014e60432023bd4c10dda3a96ff9b6ede5bccd133373bc7d251885e8bbd676100fe7c0abd2e94d5d245e4126dd0ecda2c7bc64b3cc13efc0aebebd0544b4f748d7f165c5ed7b1228d650edad0c5ceb6d8fe54ceff8f919e5277e8fd0ded341cc3cf2e6a9c234e8e58c29923855f80145f587c88762c83f8dae79260f5a270e98ec270080b1aae0dfe6998d21433b712f1005e0bd46ed66d5c754cf95b8aa75daf72671c90ae444eb62df1c714c8b58e0c0e65d9383242b718ee5aeff3db90ccd3692495ed33c28ce075266ee4eea9c272b84ba91609a56b922f472bf788989e9f3955095c02054d4fee96a1753018dfe5ba761f7045b628c1d21df924f1a20f1473921befa0ca0851c3997911af113fb28a00eeadca5864cb4be2844a0cd733cea496f6256d8ba5945e9be759297014c94735b275deac1dd2e2b3a0b9ab9dfd9a38720b3bb61da42df49cdd2c4286c9e411f76c1bd17b401d846b7349974bf2c0d68f24fce9838af0fd1fab1a52e336d03fa161daeff1747f38f5a057ab1df3649ccd54a733a5dbd35bd0bdf243e7523acc0fb5b2bfe59f3e90a687aab488a6ba48b7d4e1d47571866ec8159dc369dcfd91021c1117c4aaa1653594088a43ffd45e1a6716be4c367d6d6086eeeb820718d6a994d6f230643a2472aefa653245ea5b4f6f5fb62f9acd701121f0a72694d26ebf6b035c0456eca41dc2756f797cd2fc3586d4c69ffeb1510a6d99178f4e4c6776954e1d09a88a8189d221b24b1dac2da2ea4c9016f3f5d5bcd4bb00ebcefa8a5725c7a2741f39334870537b995e7d720f6e252b2e7b7bf5983847d6bcb6241f7166fe4369b5de386acf4a5b851851d9854e6eeaa8624c9b2a49ad7a5f1421b882a25b7831e295f63af720928fffdd6040402c3eb3dd45d60c281e34514b8b756a19bd2dd7c2ecc6c3f0bf47c29680d6c921cc1344f2a6ee36aae3655736e034e4538e274d0ccde97e089b870b3d94f1ae93313142535c9c7bc5bcb2625833c170298f5ff9d57a024f6f696e9f886ed84e017bd93baa1688719fc4cc615275a2149b2b0ad4d84acdf71463987760241e21834ba97e8572192e852519530b7fc461ab82a802f09e89ff9ed56c55ed7567dc77464d8614242d7048f0355eef3a5a35c9d0fda21867a4bc3b8593a7b8c431cd4301151b1a7af8a07fe9cc86bbb16c142900a44aa38e55b1e7b36da5dce7102b2e9a5fc4c1aaccb698ff9afd1dae6c90f2732f73a9c8d19a39b8332f2d68a44d210ed8eb00656bfb9a9d1f7b0303df62f8f784aa81673fa9757a32a5f56b96f416353e8f236e26a97b0931fc5db3b47e228b3afa26a3f9962ec866c625f6fde06284c40916c61e577adfbb99a6a697edcd34e611bbd6daee8ef701e217b5f009538ed3ee582c2eb2cf9b1cf3265e2ed28880bf8e79a4dfd4187b77ba3e085fa850a58133936f9db8e1fdcdd4f35f8fb47ac0a7a1c0f266f1f367cd773a21c9066ef0b51f65ff1efe0352ed09f1283d08f7ea43d93779bc93a64b717a8e67ef565c6fd7f8d4a33ef6115fe0689618cf9fb03d802060fdd7c8fc288e29ef08f23a6738a5b0daf72ce1aceaa457723a5c207563db06b9fd7d4f388d61b64bbbd9b4325314437584624ef749e9cd799484f8d942c175033768805aed166338456292f49ae088c09aa0c94ef968a92bba8a0e2a4a716ff235d3ca9ccc41fd76cad426d499ec52ef0b06e4985d44782645827c85be77cb558ee4313c91d09d05f34bbfb8f0edca86353ea043a1f18dad3ca02e87506b3dd1184c86912da3f874efdf5ead7e85644ea1fb3f0d5eadecdd55d69f77b91fcc307961216e3c128ccf149b9d67f903cb130faaf74613e53f99fd3bd37522fd983732a42836cb34d94f41e61bb34443472aa5a64cdbe9ee82b22174a14758ecb17e27b606788b57e4872eb4df8d50ca492799300492589a56ff8009d26bb5bfbbd67283274e89493e09d292351c6e84c094439ccbc7e6785d961dc43e64c8d835207af9eec6a4a162f59a4aeffc67b83b4e96bec6d933e7601495f9e171c9fcdfbd3f19b5a08d58aa0704f94a09671e0af89e4fff532b6b3f5805f0d586ec9c5841595448d9c6974d2528ac465cffa32a5d31df74e692231c2721b7a7fbd85366f1fda97d15bac36ec53f467e850cf37a1ac304dfb539a017c99fca69b12d67f62ba635961ddb4dbcc3d5c932d1f67707f133d3d7645486b4671df019b9102675ed0580c275c2f9ddb037790edb35c24fdea3fd380154867bd3594bb14bf3d42f9794049d3c530d7f7bd50145f138b41b1fefd9006bcc454e605aafdf4f1dcf8f51f711d5ac3690fb7925af5b3802e60578f4036e83e0fe093c538422e92551f38429051fe175c0aec40f458204baf21a2f7a47e39d27eb3a30c6006b269356a3ae76f39d7518bbf05c80a1018fdebe2d339082f8fbe70c07b795717c629a0732581cf2540291d09f57e5247930a21c7b6863a4b2c15f4f12f640653fc97074db87a7202f38b12aa68a9a392ab331e1bf8807bc45f6bbbdf22b0e9992c10d314b9a4089ad3df570ff6c1fb722112aaf635dfb465685c2be46cdc4fd62f9fd4b7931c61c38d78c2f345ed5195da517245636744fcf1812b98111ca6f40d7244e4dbd23ebe07f2e8ca3278fee3f2284d353def64074f8ad2e2c7c377837523a70a912ad801fd9f95ed30b399abdf1b2854f485d263242ac30b6f9633448f62ed6da87f1f824d67df5bc16078aa218a17e06e81ae251c34f721bb8fee36d046d9b608f67094cf4f2147b2108794037a311ef3aa14b2b7dc06ac35e792d29ee7fc347d5ab62dcdbe3a7705501ba4b1e77a63c8e92962f0927fe4f1713878ab1a67cfc2776125ec2b3abf2c0b3d68a694c6dbe48347d19442469be8d2ada071ff70ab6c784d9b35490eb6d9d686b18fa00d05354566fbc1b3417de8cdfb6e4dd4002223247b2bbc1d5d80b77808a8e939fca375074dd00384c566a838890bdeb397328304d9d415688e41b3784d10000000000000000000000000000000000000000000000000000000000000911151f2125292d", -}; - -// struct NistRng { -// _seed: String, -// } -// -// impl NistRng { -// pub(crate) fn new(seed: &str) -> Self { -// Self { _seed: seed.to_string() } -// } -// } -// -// -// #[test] -// fn generate_key_pair() { -// let random = TestRng::new(); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), new_ml_dsa_44()); -// assert!(kpg.generate_key_pair().is_ok()); -// } -// -// #[test] -// fn test_key_encoding() { -// let random = NistRng::new( -// "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1", -// ); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), new_ml_dsa_44()); -// let result = kpg.generate_key_pair(); -// assert!(result.is_ok(), "ML-DSA-44 key generation failed"); -// let kp = result.unwrap(); -// -// let expected_pub_key = hex::decode("1C0EE1111B08003F28E65E8B3BDEB037CF8F221DFCDAF5950EDB38D506D85BEF6177E3DE0D4F1EF5847735947B56D08E841DB2444FA2B729ADEB1417CA7ADF42A1490C5A097F002760C1FC419BE8325AAD0197C52CED80D3DF18E7774265B289912CECA1BE3A90D8A4FDE65C84C610864E47DEECAE3EEA4430B9909559408D11A6ABDB7DB9336DF7F96EAB4864A6579791265FA56C348CB7D2DDC90E133A95C3F6B13601429F5408BD999AA479C1018159550EC55A113C493BE648F4E036DD4F8C809E036B4FBB918C2C484AD8E1747AE05585AB433FDF461AF03C25A773700721AA05F7379FE7F5ED96175D4021076E7F52B60308EFF5D42BA6E093B3D0815EB3496646E49230A9B35C8D41900C2BB8D3B446A23127F7E096D85A1C794AD4C89277904FC6BFEC57B1CDD80DF9955030FDCA741AFBDAC827B13CCD5403588AF4644003C2265DFA4D419DBCCD2064892386518BE9D51C16498275EBECF5CDC7A820F2C29314AC4A6F08B2252AD3CFB199AA42FE0B4FB571975C1020D949E194EE1EAD937BFB550BB3BA8E357A029C29F077554602E1CA2F2289CB9169941C3AAFDB8E58C7F2AC77291FB4147C65F6B031D3EBA42F2ACFD9448A5BC22B476E07CCCEDA2306C554EC9B7AB655F1D7318C2B7E67D5F69BEDF56000FDA98986B5AB1B3A22D8DFD6681697B23A55C96E8710F3F98C044FB15F606313EE56C0F1F5CA0F512E08484FCB358E6E528FFA89F8A866CCFF3C0C5813147EC59AF0470C4AAD0141D34F101DA2E5E1BD52D0D4C9B13B3E3D87D1586105796754E7978CA1C68A7D85DF112B7AB921B359A9F03CBD27A7EAC87A9A80B0B26B4C9657ED85AD7FA2616AB345EB8226F69FC0F48183FF574BCD767B5676413ADB12EA2150A0E97683EE54243C25B7EA8A718606F86993D8D0DACE834ED341EEB724FE3D5FF0BC8B8A7B8104BA269D34133A4CF8300A2D688496B59B6FCBC61AE96062EA1D8E5B410C5671F424417ED693329CD983001FFCD10023D598859FB7AD5FD263547117100690C6CE7438956E6CC57F1B5DE53BB0DC72CE9B6DEAA85789599A70F0051F1A0E25E86D888B00DF36BDBC93EF7217C45ACE11C0790D70E9953E5B417BA2FD9A4CAF82F1FCE6F45F53E215B8355EF61D891DF1C794231C162DD24164B534A9D48467CDC323624C2F95D4402FF9D66AB1191A8124144AFA35D4E31DC86CAA797C31F68B85854CD959C4FAC5EC53B3B56D374B888A9E979A6576B6345EC8522C9606990281BF3EF7C5945D10FD21A2A1D2E5404C5CF21220641391B98BCF825398305B56E58B611FE5253203E3DF0D22466A73B3F0FBE43B9A62928091898B8A0E5B269DB586B0E4DDEF50D682A12D2C1BE824149AA254C6381BB412D77C3F9AA902B688C81715A59C839558556D35ED4FC83B4AB18181F40F73DCD76860D8D8BF94520237C2AC0E463BA09E3C9782380DC07FE4FCBA340CC2003439FD2314610638070D6C9EEA0A70BAE83B5D5D3C5D3FDE26DD01606C8C520158E7E5104020F248CEAA666457C10AEBF068F8A3BD5CE7B52C6AF0ABD5944AF1AD4752C9113976083C03B6C34E1D47ED69644CAD782C2F7D05F8A148961D965FA2E1723A8DDEBC22A90CD783DD1F4DB38FB9AE5A6714B3D946781643D317B7DD79381CF789A9588BB3E193B92A0B60D6B07D047F6984B0609EC57543C394CA8D5E5BCC2A731A79618BD1E2E0DA8704AF98F20F5F8F5452DDF646B95B341DD7F0D2CC1FA15BD9895CD5B65AA1CB94B5E2E788FDA9825B656639193D98328154A4F2C35495A38B6EA0D2FFAAA35DF92C203C7F31CBBCA7BD03C3C2302190CECD161FD49237E4F839E3F3").expect("pk couldn't be read"); -// let expected_priv_key = hex::decode("1C0EE1111B08003F28E65E8B3BDEB037CF8F221DFCDAF5950EDB38D506D85BEF394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB187D0AD11522110931494BF2CAEAE36979711BC585B32F08C78496F379D604D5321C8C62B59EDC23AE1FC7742135918E01B02E411630E26E675400D5AD2C776FCC0A6711A966C11312AD9A821D8086542A600A4B42C1940720242628106210A43852331709308108B188C022492C1B28412C4218B042181C8610248059C9201C0348819326C582046891868A2C28D82346A1C094200A28CE3A6491C112CC24812E0902191985062C084622451CA062C64240E1BB3312496854B4606DB2668C38268441046C9B6211404811445502442084422710B92459AA0811A91709C241003957004C504C82692D29200C0B260C0A26809190AA2300E188969E0008DD84862DA14712018051907440412409B1240118010D142819928508B1091022464A0206D1246211C838C1B4769010690CC062481846920982C24120521B15041360298446ED1A63111056AD3A840CAA84C62B00003134A53344614194004C54CE306695AB08961168ECB10808B168ED990640B94602483851AB30454262251B8251C424A0B814842C4445A102023808409B7254CC64814854D19380E601651D8326A0A918908C170E0964D18468C01328D91C4054A0061230868A2104210A8611306218A248E620689C9B24508278451200D980466DC42054424852426282221612016090BA62C0A1144E0928158480D422210A006098B246E81288CC0248090308D8436404CA68450042494B68DA2926D18B344A00085E3B805140504A4C290842281C3262D0B2066CC903198382810166CC13445C0102224C688034632D840901C20680415289A188144988D9C206E9C302CC1B820614221080310A0C28C58128553204C0330814CA48D44C08D51404C1CA72C440865A03840DA20808106858C260DE2A88C9C4411594228C42604441426A1426408C0851101869B483199B20C80464459A88C0042089882900AB54562244812960544124600C88813A061E1284D0AB9914B962099B84400314E98128500B60183A00D14150E1881101901224A06681A498DE1A28411C63121262591A06D030524A1B6089444724334125BB42041B650D0888D0B074D1C94644C208E8B8808E0300944200549864D03134E19C9840937611A43684A80900204311C1742184080C8308EE1A241C33404A3282251247188D6FEF46712CA182872AB2919678AFF9D94E743E063A39E0C35CAF72A7F2EDA28E65858520D5D8467DE747CF340653B52C268F55413F5ADDC7D49011EC33EDD537423A84288869337AEA0781A124269071451722DB3BB8F2CE5B1552F83D2AF07F25613918A9F4E6F1257603888E589308CA5F95F07143D23BAAE17520B36B6E0E94FAF6845EB2131AEC383E63BC8644EE5F1ACCBA82F9211E57AFCBF509C1131A37466BC91B357DCBBBC14CCC319C4CC6AC75FCDC82C6596D07770C8277AD370B192A0B4E05F812E0E265D2912AA29F03FC9F72DFA69C9B1291A3FC583642B235F6991A954788347F60A0328C48ECEE51BA02DFF323ABD911667CB14549B618F1C5D250CAC9E35E071601992FBEC0BAE6F74213081404744D12F2A0E04BDB265E0924CADA40D1FA1F38ACA4606BFD4575712B8260A456FDDEEEFE7CA259BCDA97B9B939A5FD2889C9B49FB7D4E3553DEA61B3339BD0E6B16BF3BB227103BF9202E72DC502E28F7CE1559A4631F372520324E4EBA07545F78BF4D94B0E5B8BF51B8F176533D5CFEA5232F283A47605FA65DDB17C891C251011C4E98EEB6EB00CB65BA31C8F025C87A9FE02DBC10C5D83A065EBA5D7B2A19D5A1CB2C160AE166E867F2AF8C7D49D63FB83A614957FC0A3B5A5C74990E9A2B02120C7E6DE37E155FB472F50F0A45E47CF5F9D7A4C82982C9DC86AE877C3FD1885943E439FB003C7A9A42F71B4FF6F0A28B140CBDBA6E71B13AC31B23DE9EAB7837E15A69F833EB7B56A71D8BC2CAF1F2A31C345BD5F46EE013A7C689372337191DAA800C0AC6C46C9FF688B1A01347F257C474AA3D97C1D63A8C00E0A37B681673F57C1C9C8FCCD46F174C74A29D84CEB71F7E6B2F8CD2B089ED43F7C96DAE81A223418C20B16F1DF3D1A978AE28F6DF35EC559D04D20EC74B224AEA31A289B015B069E9CBBBF7CF6DE94CFB2A96E4AE3462C96003CDDA87DB561AF2CE3C0BA1D90413FDCE3CCF4390C02C1CB9F654F4820EC33015457D4A629FBF39419CAB7642D6885E103FCE0D4206CCE7C12C6FC44FA33AD0864C3371A7CBE820E3B371B656A38F2E7FF18FE4A50C8AB3F85D783FB57835CED8490B84EE0D99AF0D64C483CEB6366FF54F8AC8A40DB1AFA573A4FB326C74F0236ECEF3DA7120665CCE05DD654B5071723A8348E7CD7793513819B61CB64E1328E8B22E7664BD6B41B5710D19EA8809D4450850E907DFC4D0B75F588CECE962E9E0937CE1402446A4D2891A46E6617FB29D4FCD712606F7819ECA60F7E0D5B19E7FFB57C73C16FFEEB90038410CB9FCBB5E9D51EB3EB6297E9FF6AB7088FE2D9B237BC24CF7F8290118A5E0E00A0B903FB6375C848176CD0A8C8875CC59199CDA11A87A78F65CC404330B087571FD0633E27129FDAB5A8A1F793E52412B0083FD5C74DB3CF60C2543CE7C91B2800E40203F8D99FE5FDE5B108E7EDC80EBB9BB34986EC5C5A8F580E75752907FF0F294C866C2CF1F362E840B6881BD43219201781C63B0039A95BCFB4A0FECE569DF00523CE9C084B022B3B022242E28419796ACF0A0C995F948DBFFFD30D77ED105A3C9943C406B305BC81A6A248A291548F2A67F438D966A57D53F4B7BE15354E581BE16F7AD64D164E85787DF5849C810AFC28D06482F441B5FDE3DB2ED36DD25AA6664D4D43FFA32EDA25689C9F4A5D514FC66231C5401520922524438EF1DC78D693C9718DEBBD243312674C899F18910E389C8EBE505824BCC42CD4A9ACE193768220219011F3B1F335427BFF9E8BDED5C08711A09C2B71CB964C56A8393BFD2B56E9B6B2F513E682587DC1B8ED196066326871025628036700063176D345DE384E182D6C417A32AB11095EF59BB4D171B9CF81D17AC42664DED933CCB722C69857FFC53C8E7F2474B0CB2DFF2DDC8A5C601C84A701981199BCCF74112A6EC062C4FEB601A028AF01032ADB6BD15D4C2B9550AA850AD62CCC3A3665D5212B12E0FD5C5326A1E5EB1F10D557D94605E8E3F356E08FF7FD884ED3C4205463594C9AF2F39E4B1274695234B54EECED93F460EDF1A13C2CB4B17D322F6F79FE16F0357C1C4739863E796791F8647FABF730AB00E0DA509706D94571740F61F7BAF366D2774C9B5B8C61DD6BE9819A6028B264BB2E4AEA54B56D4ECAB5B528CE0C0C0CCDB73023352CB00445BAB6F7467B4644D4361C464FAC6B5B137D32391021B475FCB5F31774FD8ECABDF65475F25574C65559CB331F41C0F498B74DD941C344C50D8E64F9578714A32561FAACEAF78148E6DA4B566826925714B17108AFDD546385A3CD454D5CAA16960916282A47C4315CE236BD9E3255C604EBDC39772DB5CE0B236").expect("sk couldn't be read"); -// assert_eq!(expected_pub_key, kp.public.get_encoded(), "Public key does not match"); -// assert_eq!(expected_priv_key, kp.private.get_encoded(), "Private key does not match"); -// -// let pub_key = -// MlDsaPublicKeyParameters::init_from_encoding(new_ml_dsa_44(), expected_pub_key.as_slice()); -// assert_eq!(pub_key.t1(), kp.public.t1(), "Public key parameter t1 does not match"); -// assert_eq!(pub_key.rho(), kp.public.rho(), "Public key parameter rho does not match"); -// -// let priv_key_result = MlDsaPrivateKeyParameters::init_from_encoding( -// new_ml_dsa_44(), -// expected_priv_key.as_slice(), -// Some(pub_key), -// ); -// assert!(priv_key_result.is_ok(), "Private key generation from encoding failed"); -// let priv_key = priv_key_result.unwrap(); -// assert_eq!(priv_key.rho(), kp.private.rho(), "Private key parameter rho does not match"); -// assert_eq!(priv_key.k(), kp.private.k(), "Private key parameter k does not match"); -// assert_eq!(priv_key.tr(), kp.private.tr(), "Private key parameter tr does not match"); -// assert_eq!(priv_key.s1(), kp.private.s1(), "Private key parameter s1 does not match"); -// assert_eq!(priv_key.s2(), kp.private.s2(), "Private key parameter s2 does not match"); -// assert_eq!(priv_key.t0(), kp.private.t0(), "Private key parameter t0 does not match"); -// assert_eq!(priv_key.t1(), kp.private.t1(), "Private key parameter t1 does not match"); -// } -// -// #[test] -// fn run_kat() { -// println!("{}", "ML-DSA-44"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_44.rsp"), new_ml_dsa_44()); -// println!("{}", "ML-DSA-65"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_65.rsp"), new_ml_dsa_65()); -// println!("{}", "ML-DSA-87"); -// run_test_vectors(read_test_vectors("tests/data/PQCsignKAT_ml_dsa_87.rsp"), new_ml_dsa_87()); -// } -// -// fn run_test_vectors(test_vectors: HashMap, parameters: MlDsaParameters) { -// for (count, tc) in test_vectors { -// //println!("count {}", count); // HashMaps are not ordered in Rust, i.e. test cases are in arbitrary order -// -// // -// // Generate keys and test. -// // -// let random = NistRng::new(tc.seed.as_str()); -// let kpg = MlDsaKeyPairGenerator::init(Box::new(random), parameters); -// let kp = match kpg.generate_key_pair() { -// Ok(kp) => kp, -// Err(error) => panic!("{}", error.to_string()), -// }; -// -// assert_eq!(tc.sk, kp.private.get_encoded(), "count {} private key does not match", count); -// assert_eq!(tc.pk, kp.public.get_encoded(), "count {} public key does not match", count); -// -// // -// // Signature test -// // -// let signer = match MlDsaSigner::init(true, Box::new(kp.private), None) { -// Ok(signer) => signer, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let mut sig_generated = match signer.generate_signature(&tc.msg) { -// Ok(sig) => sig, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let mut sig_attached = sig_generated.clone(); -// sig_attached.append(&mut tc.msg.to_vec()); -// -// assert_eq!(sig_attached, tc.sm, "count {} signature does not match", count); -// -// // -// // Verify -// // -// let verifier = match MlDsaSigner::init(false, Box::new(kp.public), None) { -// Ok(verifier) => verifier, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// let vrfyrespass = match verifier.verify_signature(&tc.msg, sig_generated.as_slice()) { -// Ok(ver) => ver, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// sig_generated[3] = sig_generated[3].wrapping_add(1); -// let vrfyresfail = match verifier.verify_signature(&tc.msg, sig_generated.as_slice()) { -// Ok(ver) => ver, -// Err(error) => panic!("count {} {}", count, error.to_string()), -// }; -// -// assert!(vrfyrespass, "count {} verify failed", count); -// assert!(!vrfyresfail, "count {} verify for bad signature didn't fail", count); -// } -// } -// -// fn get_seed_buf() -> HashMap<&'static str, &'static str> { -// HashMap::from([ -// ( -// "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1", -// "7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D", -// ), -// ( -// "64335BF29E5DE62842C941766BA129B0643B5E7121CA26CFC190EC7DC3543830557FDD5C03CF123A456D48EFEA43C868", -// "4B622DE1350119C45A9F2E2EF3DC5DF50A759D138CDFBD64C81CC7CC2F513345", -// ), -// ( -// "BFF58FDA9DB4C2D8BD02E4647868D4A2FA12500A65CA4C9F918B505707FA775951018D9149C97D443EA16B07DD68435B", -// "1D836E889E46259BCD1CCD2B369583C5B47CFBB919EC2B72C280247CB15A5569", -// ), -// ( -// "58C094D217BC13EDFDBEA57EDBF3A536F8F69FED1D54648CE3D0CCB4847A5C9917C2E2BC4D5F620E937F0D329FCF8A16", -// "539577CB7F2088FBEDFF1B53F235D607321857DB32BBA645F8DF3A89DD426552", -// ), -// ( -// "F1902A7815F37BC7F5802D8CBCE5B48D82EB85691718062BFB84D8C06AA41D6E9039B0A107245DAFA4EC109A57332914", -// "2CA59C6CF33C53803749F69EF5ABFA9482FCEE7EFD87FBF17135ECC3FF3FD7F7", -// ), -// ( -// "75224ECC026C18159FF92256844D0ADF953F0A4DD8D74D4EBF1DC5EE8F5630B011A447FD4DC34A2404D620CA0E1F273E", -// "E17E72290E49A44C9C534F211195257CF13B0D45405782CEDA2D7F982A551721", -// ), -// ( -// "447F03C8CD27EDAA1FA0436DA492812F57AC946479A9F1F90EC4F5E913A05F8AB0DD7645026A96510F6D40AF05D85B07", -// "3B7388E675DE5C59A78AF095481C7DD999C6EEA898595B1E7DCDA7EDC3A2C25C", -// ), -// ( -// "8C151C556DA912A82DEB32144C8A8C9090CFAF5C12AB822AC3C72618837A41C2453B715EEFF3724CAFE69B1ADCAE9DDA", -// "DC9F40CABE2E8E4F3D1538FBC1ADA27B61B99081455AB0C4C41B5B3DA8101000", -// ), -// ( -// "9B42F41492530EAC81992F17613EFDF155F407D7E67F18AE193EDCE714D65D1031E7AD10839AAB46D0850EAF5997AB4D", -// "1DADE637AE98C393260F5BBBE288373100DD7AF37EBA913C528D2B7B998767CB", -// ), -// ( -// "11134936880F5A11ED3504CF7B273E55A351FCCB10943BBBD186623EE6C7A13A6565C3080D1F536BFDB018F99C4E46CD", -// "8866693CEE12B909E32A0C64381796633666417E1246B51A2643564B464B4113", -// ), -// ( -// "98DDA6B97E89A479D5EE214E660DD6B5D8F6CC638A1CD4F462A0EC545F5B0B0A1A403AADF566F7B1C0C5FFCA29B36FCB", -// "D6DAD5B2746422F4487B72536D70DF88AF4B2F9040AA45999F8D7784EF696DA0", -// ), -// ( -// "D34A0AAD27ECAD31A5E08E9A2D7901A9B85F864D9B1B46F40CDCA0B3615B2CBA04EF82AD7BD8CF627C3E861477030BE2", -// "68E7818F33B97BA6166768C395BD010CEF7BCE9995891D164303B53C1123A991", -// ), -// ( -// "4FDA9FB6929E3F391901D69FA0AA2F25A9657D249A620F1B9E305A5965676BA76794CAD3355EB632579C3958CA7D443D", -// "35B153A7706109D4A13D7C4B26AA5B56D9E3FAC53B47E91B0C10BD4E0EAAFC19", -// ), -// ( -// "B0E6A23FAB10A7A333E3720BE00D31507917F39C5EFE1C98CA18BEB5C3101FB4479B478A1558C4C00398C55C9822FC44", -// "0E1A1634FB2396E187CD8980EF29663C42DC3EF963CCD491F817A84283A11FA0", -// ), -// ( -// "0A98A2BD2B9FF42CFC18D3396BAD052E1D0F3372854DA69A318B142F7A1AAC609C3861263BD8FB0549DA7266784DB8B4", -// "B0BFA060F1C1A70F1AC55E321E6186A6613605DD732574B5FE6E14F0FF6F7A82", -// ), -// ( -// "9887F1FD854241A301EE0120645CD8E119B43F7BEE11F77A835E9ADF518C3A51CB76D86653FBE73AA716264C146797EE", -// "A33BC0A7A08C13C0D4C1174DDD886AAC4C5666E1F4831F006C9519D36B2CE882", -// ), -// ( -// "5B485527C3B9A5E5B7579950049CD357975D4BCFEF83FE33C087ACBFCC10A0BE4225E7F8A5F77203B5FC7C0B5FC0E78B", -// "C7E33FA5329142B668CCDDE1057EB7A8619397537F2B4C6D6755B3B9FF936441", -// ), -// ( -// "327CE565CFF6CD9A25EDD84F482FA0758B78CBC246567DAE98B818314AE28CD438E339043EB3FF16E1C2B4B104A717B8", -// "7611B5B7D4195D5F8B97244B6811748EFEA929EA272E66435A36D0BD16E3BF21", -// ), -// ( -// "790FC03F956D1301A735504075B67A05944A762E0A4BDA77BB8C036C5CF911E2B561EC1CA6AA355D5CEC919AED42A1D2", -// "5A1E3E05C72CEF1A73EF98840DA035E4FD2552912DB8DAE28A79011DE4BBC1A4", -// ), -// ( -// "716354F7DEAE272CD26929C0932CA257AED1DD23D67260726B5213D82E61466FA99BB6A7D81DEE9D0EBE03DEEE4DBFC7", -// "8F3920A235EEC3659CFCFE62931474204EAE264959702F901D461B66D9BB563D", -// ), -// ( -// "A32E6FF879EC8866A5F5E4F6318DA8FE6743812ED2CF5FB94F5C3AA3EDF953CBC32665810B71B2CFEBF343A571CBC570", -// "0B2B3EB50681403A0B9A99B25041A489C6D45D2A49DE0EC83E1FD10922ABE2D5", -// ), -// ( -// "5A64401EF8E63AEE18E8CC0162845DC7AF388230E86728ECB330007F2546F949764273EA05B397FE71F567E1527FA445", -// "8217D32CD15658D39CDCA92C41B59F5780869A68838A3579DEA48B5E3EA768AA", -// ), -// ( -// "3222E4B55D6767E300FDE03DB3D8227E19FB8B08EA9B923FEDE18D699DC3694EFFA7C4DAE2AF57E4A0162B7C564199BD", -// "CC625322C9D52898E7F60AE47BC2847E20F3722794DE41E30FDB20CA1A093208", -// ), -// ( -// "F41B3C6225245C06455272A6A073F363E5F19F09A0B146AFCDFC2B3B0EA64BAA3F90359F32B2D1017608B03064E90AB2", -// "950226D6AB0B774C5F439AFCFD0113B5DBF5905960C445F5E6E03E5D5C687A9A", -// ), -// ( -// "A08AD391E0FC57A83B74CA8CF44DB67F8178262ED9B20AA0163CDD8274AC2BE05F558B112B094244370C1AAAB75077E6", -// "A6B534767A6D839FD19075AE0BA10147C46862BF7BBCBE83F2B72F72F1368A1F", -// ), -// ( -// "6E0A8EF5156D693FD0140BC4A31084E79773A83F42C8D133AC8A9D62DE3CD74511F893DCB26041E6B35E2B175408FCE7", -// "103164ED522DF0DB131C15E139C0F83D9B1B7A1B6ECF7F89A5248CAD7E68DE8C", -// ), -// ( -// "49CC05312D1DBE216FF03B60575017A6A1464C06D2C5A4A6F973AD9F275F7C66163A29A803BE759B117043862D277C27", -// "BC962D978F38881085C1B813BC90EEE44AD9E7651681C20BA46402F557C454DE", -// ), -// ( -// "C33EE43A9CBB4347BFAF71147B7FBDD88D212462CB06FBE695A35402C503CD15732B7D0E8BF829A555B9167BCFA2F2BF", -// "C3DE54854A4060EA09ED92A363F71C7863EBA64195E9AC79E7AD7EB6A183CFAC", -// ), -// ( -// "19CB4BE2332F7FF0C078BC001FAB3C5FD8569A76EBCE373D1ED4FC8EB5D744C6464E2B5EECB9EE836CD5D87BEDA78BA7", -// "828B9804524BDD17D0EB387368B01B0E95B4960057ED63FC2289D858201E207E", -// ), -// ( -// "6BD93FD13C0299B3EC7403638673F3DBC449F3A617B691DDF73C072B62BF028913375D7460BED2CF9FDCA517690CBAC3", -// "4A84CA5C3954FAAFA11AE87FCBE701EBB5AFBCC5F8ECAE7786D10821E01ADA5A", -// ), -// ( -// "1787C82DA9F2E6CA9ACF7D6CCA70116A1724902C81EDC1439F332C74807AF2BCCCCDC7AC1788BA798520B2999F39DC3B", -// "3E74AE2B1D49EE6F149076F0BAE2D26A5CADFD5DE7BEF66DFCAE6B588A1F4067", -// ), -// ( -// "9E6E12F025B2A57B0F5A3A9FA70396FC332E1802608E5CA07CC4FBA922F1FE5DEA6721B96F1BA2BFB97825A19F08FF2F", -// "39550BD2782D66FA95380F5F101D827377B11410F8BF3BCCFBE0E504FC09AE38", -// ), -// ( -// "569B8B9BDB707B19CD6F9BEB29F304D603C1509B9CF25987C280C342E870B1E13EFC7DD7E41DC85BF4F42D0493B84B0F", -// "B18F0FDF9DC4F514107F88CC43FB29190608EBC5A2CD00B49FE20631761038DF", -// ), -// ( -// "F32C3715B0BA8C1D0BD59F0645E9697DFCF9AEAF761A71ECDF9672215B9F138C0502D7214F6B1BB4D6612432F9FBED5E", -// "D4FA14DA39548392300A41BE413EBD53BD7BCBD045B4D3C8CA44ABC9599E269D", -// ), -// ( -// "B0C7530A52AC9F561C2C14548D3A5F5053396B738EA1C7A5190F5AB01C9C38719C4DBE856E42D37A114FA24FD5DF5081", -// "C796FD12D1FEB1DF46B162C38292684C09059E4463CB95DBDBF498A4DD4F7F00", -// ), -// ( -// "B2FD7BFAAFB667C9DABE5915C3BC271EF41F18588666A6F4990C09D098E62DB590110DF6A56F08C5E0DE65B00F91D60F", -// "A18A366A5ECACAE4732DC9E954333EAD153203013BAC4E3C50BEE15269F983FB", -// ), -// ( -// "C08E846A8E039C8655651919A8433D475F494899FB617DC3B4715DEF0C992C195CE38158B7FF40E0684B30FD7E623265", -// "585E714D565AA66078BC2B12699F1E86C6FF30A1ABC8CBD19563BCDDD2F1F6D2", -// ), -// ( -// "1D9C060EA0408A068BD982D9694D39D02BA5A473378F6F9F09349F686566F331E767263FAFF5DC0E823BB6F648843876", -// "662CF70D3D5E95A9C6A33BD7C6ABF0E8CD23AB2D2D9420878C4835DE14A6C606", -// ), -// ( -// "A4563D09AD21D3916BF4636301F2E64183A8F003DA186753D7F2DC3BE0089BA09C62B8A52B72C2C8451213606801FB29", -// "1924A71628292AA3D2D34EA72E2BFC2520864205F54EC6F19F7714733AA34CC9", -// ), -// ( -// "811A8A2ED2917CC616FAF246C5F9BB902E5FBF5430AB078AD6CE871CF8C160512A748216EFAB3A4CE1271AAFEA12C11B", -// "EF7BA21809AE7E0BC3230B6061C5FEE206D805572CF1345198E1EF22A8FE7322", -// ), -// ( -// "41CC9DB2E90239AB5158A2628E7478D0B3512FDF84CD27A4CA5FE3119A455C22045F198C3C5C39F491FB975BD1CFF7F8", -// "CB0B305FB54E1CB23B63EC1F6F4689137E5048D095FB3EADC854C852CA86BE93", -// ), -// ( -// "1C13369824A3FDD41B1065E17297574715D9BD9CE5BB733D36D22C31B62BB1033989A604D78BFB1A0746BD4A2271FC0C", -// "F92FFA3A36F43F9177763AD320FD651D9357C6D99F09549FE6AF12943B58BE90", -// ), -// ( -// "7AD6C7DF00C9E52A75290D28DA946305D83CCF6DE2515C19A8E26850C34C8C2E545E2E32108F13B9C97F87AB68D10131", -// "5D3CCE926A795ABC5F6632CABFF8BF66275DCC7E4A4AB3B8399D23E62A28BD16", -// ), -// ( -// "38FFDE9B60DEDB5BBFAD6C52AA02EF6D49369BF276C99E588D796A4F260E0FF0A65C96C35863BAACFFD9B212EC305E7F", -// "BA2386BA92AA89049C64ECFE60FDDBE136815D3874527414B63ED32215F2E06F", -// ), -// ( -// "ACC98B16DCC9A50EF57F332D66255CA56C2BB679CAE705B4297F1418DA845861448DA6CC5CC458DE6C6E96128EEB2898", -// "B4E1AF25E8DC6934BA391A89984A358702BDD36838BABEBD982638703F20EEF8", -// ), -// ( -// "8BEA4E384E73C7E0B47381B3063334291A0F06D28DB61B5BF65B01D0A747722E0AA62B81AD46C00C8A5C31494E513836", -// "A62875A3A6D305E120DC7975962552126CD844554857C2943872A4E524A6EEB5", -// ), -// ( -// "CFA713E4A63A6FFBA43BFB898956DC400507F68AD164C3D24A67B5F8D7548C9DB44DAA43E5E4A0990325A4233089318A", -// "76AE71DED1F9E73AF77A2FEAE4EEF80F87414DFB7580FB4AE0325BFF20D74A5D", -// ), -// ( -// "1F3193EBC58EF65E9E396D69220ADB8ADC729BB388A72CEC9028A094F1CBDED21CFB0C41356AF31E0CF66A3B0D843666", -// "A8F65BE046001A6814F537915BE3F03F3670E1169E4AAA6D7E726174ACAEC77C", -// ), -// ( -// "CF5A04DDB5EBC45328F703D486D24443A7692D65AA55F054E3078DB76A7939590A3F35CF1A21E82A845445DD1B64A85A", -// "802E08C14F6E3446BBF7F4666C8DDF7755DC718C3E02B7865FF33E9D8290ABEC", -// ), -// ( -// "8C3D2FBBE0D39E293AF2D2CC5A9BEDEAAE3752DFD19CDC1E186D41E717A0412AA429CBDF005445AFDE684656B5D17690", -// "23D7A85A824DF3D904A511281A973C979F67F5BFAF3AB0546E85D0597F91120F", -// ), -// ( -// "C10427EF0B26328163F85D45E22EC5215415326F013FF31EDD58BD3E97B1A72FF07D275D4C1B517F4661B0638F75640C", -// "771CBB7C9FBD9FC5DB93E3E4DE6C034E58BE9BADE93748C42297142124696234", -// ), -// ( -// "4B6B73E042CE76DBE39535E45D3BB2F3B9F8B2BDA170E76CC88666844703E32B2367460A0F6A0A2E3F4E7A6CD32BE998", -// "FA812D8CC3A9631A0239474EB93AD3A2A3480F2D973D3324228EF92A3B043163", -// ), -// ( -// "3D4607399F6FCBE074FD2BEAB1A7571239D6BE6308617866B65B892EE65399E14DC7FA612CDBC5F7E23116FA86C3133D", -// "C660B84D558A7E6B4EAC47C7B62135668E0EF0FBF74D514EAA3D0D428014282A", -// ), -// ( -// "7031BA806F4D8BC28529163B239E0EE836871C51D2D62B601B71D6F2B69B203C81440F8FFC09C3AAD94DB1D880160671", -// "929F309AB3F90CDD9C21EB77A7CA762CA3AFCACBFE3E67B056290835694BA3D8", -// ), -// ( -// "C8671A5D752CC6DDF075C899797603A625C142485EAC3D57CAF14F2244D7F84D116B28F959912A758E519D588A6A07EB", -// "DAE1EBA78AD1568590348088AAE88C1ABEB59626EF65991CD76AB81198E52837", -// ), -// ( -// "D780D7688AF364949A196657A066BD48FFA8DC45B4885279B6DEF362E5957F398CDCE1D20FC3F8F63A275C325FCCE654", -// "15ED428927A7EB0C7C2DC7A98CFBB77BDD773FA8747B8232A6EC4B87CD7DBCE1", -// ), -// ( -// "36AB8588F5233D15674677535A682382C29968FF824031AF646F58FCAF0E83C1C486B1E75479149FD6F4D9E8397CAF73", -// "BA7E359B1F669783521AD35EDABE97141A816C2FABF0AD0E001E21F73CCF7736", -// ), -// ( -// "4E94DD734A371A7C6AD4A567038CF93BAACE2B9D30F3862198DC55D2F21F8FDC9A7AE5DCA1541712179E3AB1FFA3F792", -// "9DAEF95C8D5A61D3A3A267FEFB9F37D6E677D7BA26A3A5BFDBDA8C281BE89CCB", -// ), -// ( -// "D9281003AC5F7673E0E9A7BC29C4ED75E6B0F228DF49D11A2599BFF2DA9E887163BB26DBA4F071FBCE02891540EC6F1C", -// "0531B4105CB209585F9FBC29CAA57E64C2D40F0829931A42CAF7701717D9096E", -// ), -// ( -// "750A74866BE8DF4E60BC14BF36E6D83ABF6DCBB86792D125CF0980007C5435F40F87BA96498A88252D9C5C6710807652", -// "7F8515AA82DBC9EC8CF1DED5AB58EC0D08CF686E25A8C01FB1109A3C68D19E48", -// ), -// ( -// "A832D4AAE8076C4EFE8319A74CE315928AB765BB629075254CBC63EAAE691C220F4B5E1839E9A99D8747AACD7C2F1EE3", -// "5AF3838060E0F83352A75A0EC4ACE2CE8BA119BF89F34CB4D6B8E27007CAE7FB", -// ), -// ( -// "09B8441F47235EFC82D71933A0037FA4F69124C3BAD4EF6A3A7178B417A3FDA874081B7EEFD7EF1BF234C752458FBBAD", -// "B323D48B567F7EFFFFD47A7C9ABF0ADD5F11141737A8AF62B56E042EE498AD6E", -// ), -// ( -// "D2629CEEAE5C95D3C34C1FFCC2338B4A97782BDFCD39111E18540B69DB035B352D012857111F816F03550BFE5F56ABEE", -// "C1CF3107EA9B283419E27DC563ECCE950BEA78C048A3F49FB42128819959E51C", -// ), -// ( -// "EAA4FB8EF0290A499A1D92EE398A8D7E71CD3CBF01A36750DA4B7EFF175DA26D17AC4ECE49A84C88D1D2C2493563C26D", -// "A50FC40F0D9EFA5D254943DC599F7DCC2F6D197A4D2666D5D69CFACCDA560817", -// ), -// ( -// "5909111F333F3E939105DFF8532548927EBF289F31A72F4C1B0C66816D8B68F64622F36A9BC85E63601BEE8EE7CB3DC5", -// "4C0F0EF1CA8073A562D5414584EDF268913D53D5FB39FA639E02E900891EA82C", -// ), -// ( -// "238461A224ABEECCF709AB6CACF4EDD372D45E5F4274095273A49AFE614F2BF713134ABF68B4DD058E6D7B612C3658C3", -// "5BA8AD9B66C93CDE7E7E616A97FED8AA91BC7235FB4DB086CB4021877780C6B4", -// ), -// ( -// "83C653708FAF3E5F6FBC9DFBE6FB5E83E572A7688645D75D2C4835B28695DEA4BD7093740D0FF43237354EAD1C978BC2", -// "25D957B9BF68326D2EFEC93DA464F43E3DF16DD6571CEB1AB68BD58E87734A51", -// ), -// ( -// "BC81485EE93AAD8B464B5199FFEF9FEFC06EA97645BDFE0B4E915B812E606A77F93917ED925E882161CBB909747AC4C8", -// "24F0CD3B05D964F82D3702BF0A613139808D49283286294BE57E13A983C3C961", -// ), -// ( -// "DE9E2742591A5AF6A6153DA85A510C39FD31A2ACD8A8511F190A9A5E5753E63D9801A8019508E67DEB1E9219CC18BA3A", -// "5EF2732B63A1CBD2CC239EA6FED62F7FEB3102715A5BAFD8C83AAC33702FACCF", -// ), -// ( -// "272E459EAB6A0BDF720E4C5B79E641C95BAB66C3CEE261D0E3596BB04D232ACE0A1CE24BACCAAE9037665A962C711B08", -// "6A27B1666AB6FC4483D14CF84EDE49FEDFE05BB24E008AE8A01C52D83B8D40FE", -// ), -// ( -// "DCC58DFC13B035323ED44BE50A7096F697C9C143518FED50A59181160960203831A9904847BA20B85E99FFA63E4AB0B2", -// "2A855572E7E2ADD2888022BDB585B61577A75A31AA8ACEDE59ACB27EEB2936AE", -// ), -// ( -// "270BEDAA7BCD43990FD8B4F44FFB63A3AE8E991BB2BF84DA7BC2CCD1A079C579AEBE2082ACBAB7FF286DE795F31973B4", -// "7967E9DE70A7F95E69371F812C2FBF932CEC07AB4C235AE9E8A6799F3F537D36", -// ), -// ( -// "F151196F55A9ED88F1663AF6BD24B2CB9DCAF3C9B313CD8F0A27639D3CDAE72EA90D60ED5C7C6AB697A06185E5A2E215", -// "5ABA340A8E541568FBEF49E77F94CFB4B3A5E9CF14C6755CE6412CF86CF62898", -// ), -// ( -// "C7ECD1EC1A3D83F5116C0AA4345FB3ADB4D9F81BD79896BC4932EE2F9D2D1F179BAF7A002D88F4F69071A7931E7F7FAE", -// "DF4853F482CC1D0B3A2D71E9EACA064E57C5D100DF79BD004BA81B43EACEC401", -// ), -// ( -// "5DE03CAB3CBD81B8805A17E0FFC2105C3BCDC8D782EAAB161A15AAA543FED59353C1FBE03E7F36B955FC51C9B30F0C93", -// "95DA16B844BAF559C2CD6E68B237614BB9927D90811106347B5849FEE2F48640", -// ), -// ( -// "63742CEFAE9868C3C0B31DDE0F9D378FD5D71BE7CC3F0B6ECD393DB55FB043CF00264852C45D1836CC12B9C872A20251", -// "F258FF1178CC42A3CEBE238C8418B4974812A05F43B8FA95639CC46BC0738BC5", -// ), -// ( -// "B887F07DB5358C3FDC2402947BBC87ABD064B02A859FE8DB37B5BCBB916020443DABA5534A0778FD0B1C05EF3ABE6269", -// "5EFBBBE99CF5C2B6830FB8E990250BE308E662200526889EA973C8D33823EC19", -// ), -// ( -// "D08A139CC7147ECAF4B1D1E434EB2EFA2B2607B0033D8BA989133E496DC9F3654944C7AF91CBB79866443E8C4E8217ED", -// "47A048D8799784F6EC385EB984E70C62CE7C8A107232871B69B99F7BF4C3DBB5", -// ), -// ( -// "A315BCF0E6835892ADFA07C034BFCD39F80B62925A95490B20170BD29378E11559C7F1CD296377FF1E01284EC727FFCD", -// "1D9E243A35118BC7C50A50746E1CF19C9FC310C7D54181FB95F44753EAB1B94A", -// ), -// ( -// "8B47E0EBE786914C9A52D547106CEB4A3D3DE938B3244E02E5F9660954C4C95A23F2476FCCB487673AAD0513820905DF", -// "0A46FDA6BA71125F3415E8BB6C2D8C00601107FA563E7F6386486A88F87701FB", -// ), -// ( -// "07CD8F8AB7CD12EA7CC94103B8623D6F0FEA2BAFD2325BF6089DF5351BDBB9A94525C3C6B72D3820F2E4D5F9E7C849F8", -// "5229DFE11090EFFBE94EE161054CD5FF58B31E23F567B282DB42EB1FE42E44AA", -// ), -// ( -// "3D598F7C498D8A1095C40945975380554BEF6142578638A7627E2C0A21C59C579F8E8CDA309348FC54C764C899FB93E9", -// "18AA77795AA6D7ADE8B6CDCED81A1959A8329677F042283DC8CA71E13EB3ADEE", -// ), -// ( -// "6CDB757AD36DF99E52F535C2680431D5FF36C812D8EA19399F666F2FDD66D3A842A7A5AE1038359AB618FA58A0A6E840", -// "4B2D6EC32BE9C5D8FA11F3FC0008F4F26B945064D98362AD912F452692AC383D", -// ), -// ( -// "A97269579EB70D268C58D94FF744329B197F722A8A407B788510DDCACA34C8CD4C72FFC14B76300C86AEA1E4CFA66BA4", -// "196B8144DFBFB47EC01E96A6B8443211D6C9C4AA7853A8131B5218349BD6D953", -// ), -// ( -// "483A81716F91A43ACA6764C4BD2A57C9156B762E9174EA49730A6BEB9CB19A0B3755E37BA47EC524BBE2FA25B9FEF687", -// "C171793029D0CBAF8D2661A823243AD50D67F2619533180F25B50C94B1310389", -// ), -// ( -// "30F0E117513AAF27AB2516BCEADD1188B4BBDE76E57DFAF43CBF2D70723D941E8F875C5EBF02BD7D67AE81ABCC54440A", -// "615FA91F3D206B908649399F216950EC7B2420EB04AEC6ABFCB7B4528E8E33E6", -// ), -// ( -// "070FFB907EE8AB7152A9D380DEA2C4C4796780FCFD80906C5E489B917A45D5E7EDFE6F37C4420E5480E8BB599FE36451", -// "626C19B8553B2D9E5A47A758615D80B15BE11FD016D3A1962D8DE58ED5CA2219", -// ), -// ( -// "EDBCC4F6AD0F30066947D678A368B960CCD164889D77730516B444ED2DF10B49C101902F5FA227377C3163A0045B34E4", -// "B0D4BA39ADB4E8712B3A3E6495ABA2F04A29E45C68671A960BC0D8D89900C97E", -// ), -// ( -// "DEEE61A2FAC04E4D6B7A250124DFD91518D9B90A71FA02665E3088760BF69CB3CD7B6977F860A7026819D178623C9676", -// "E04328A783C10DACD96702D2E726BB11CE4ECC571564CE7CB10722D1C98C2842", -// ), -// ( -// "DAB6C05E29342106CC34769BF419ADCC88010C05B57E673A503E63AE7A4EE55B72AB2CA86C4EF57FC8C02D2E0C8694A1", -// "CEE2E53099D8CB576F8F76C5C155470F87A6EC5F7D73256A0A2AEE62CBC53597", -// ), -// ( -// "0CAF47BD9AABD7D09FFAD404449BBAB2E1D48E80AC78550831A365BED8765420DBBE9A566EFDF20D4E5233D7848582E4", -// "02D9755C369932E7F99A2E1614B03E2C86D713563785965E008BA987A6C89F49", -// ), -// ( -// "9564E88F336C091EAD50C893F3EAA8351FA388682F433F7A72A34731020B9C96DFCF75EF5EAE47E12684AFA51EFB49B7", -// "2B2BF7CBB0957A86BBEC97001B60C7C6AD98A56E94542FF561F78FED211DA755", -// ), -// ( -// "4D0788DE958A707899D5DCC02F756A10DEA2EFE0214F5E01B3281DF4E013CA75523ECEC64723D6C8BEC0B92C4F821D8F", -// "049569CC5FB969C6EEBB8AEA1AF1FCAF46F8A9E6CD6C796FC7193592BCA9CF23", -// ), -// ( -// "55A9C7A0B49706090BC0702ECFC070AB060427FFC820C3FE05B499B59AEB125F2DB4787A5910B88C6F8FAF0A69BE0AE5", -// "9D89B9A327DF0D341CD2968BA9218BBC3E934502CD88919D8BB16DD3D39FEBF7", -// ), -// ( -// "CEECCCD3F7BB922650E3F6E8F20C47AF17C1C1053EA8FE08226F167D67C3B0781BD774C4C7AAD23C6AB0B9F3E3F96F97", -// "4544C2F21054605B0EEE46F62A87DFCBC3BFEC473B9850886266F478BF9E33D7", -// ), -// ( -// "2489C04BA57D149A60F446670C13C29998B52F3BAD548A751D7134B694DB25ABFA034FB4BA45E105AE27D575CBD02B99", -// "D9931E321732BD82EC9CA1DF12BA48549BFC7D3E76A404B71892F4198777FFBB", -// ), -// ( -// "26CF860726D4DFA38AE07399838BB336F1BEE59E9F23AE4C81E73D49964997EF21CB5F5412F9A70A1EC39FC6228C36CA", -// "F838451E4A5929B8BAE9084B40B1DC0EDFB76A9354BF27F981960C88B0BA3A11", -// ), -// ( -// "13F1F446D9AA5AC853278BF74C9E6447A6CE4294C037867F43DF554370EE261D05C7260EEBF46D6694D0850B8343FBE5", -// "8E4334B2589D0CAECF0FD9BA584EA26A4123D4543A8A0FE126D4A7E07F6067AF", -// ), -// ( -// "6F6E47E8336ADEE99B2C52CF2DC8D461E0A54C3DF2F08199A9F0816AF8455381054CE47A7766726D3AFC2E2F2BEAF8E8", -// "37519A02E8021F2257259C0D2E499AF3533C8ED8DD5BF7751CCE920D79B518FA", -// ), -// ( -// "CB2E6226615393FC3BD4AB3A412AAA030AAD40E8648EE6B56D2C1591D8B97915D88F2D22F7221377B4B04CF2AE9ECC4E", -// "690482BFF6C1D0BA6C071DD395ADF69E55E1BFC4E0992A8650FFB5E60A02B172", -// ), -// ]) -// } -// -// struct TestCase { -// seed: String, -// mlen: i32, -// msg: Vec, -// pk: Vec, -// sk: Vec, -// smlen: i32, -// sm: Vec, -// } -// -// fn read_test_vectors(path: &str) -> HashMap { -// let mut test_vectors: HashMap = HashMap::new(); -// let string_content: Vec = -// fs::read_to_string(path).unwrap().lines().map(String::from).collect(); -// -// let mut i = 0; -// while i < string_content.len() { -// if string_content[i].starts_with("count = ") { -// let count = string_content[i].split(" = ").collect::>()[1].to_string(); -// let seed = string_content[i + 1].split(" = ").collect::>()[1].to_string(); -// let mlen = string_content[i + 2].split(" = ").collect::>()[1] -// .parse::() -// .unwrap(); -// let msg = hex::decode(string_content[i + 3].split(" = ").collect::>()[1]) -// .expect("msg couldn't be read"); -// let pk = hex::decode(string_content[i + 4].split(" = ").collect::>()[1]) -// .expect("pk couldn't be read"); -// let sk = hex::decode(string_content[i + 5].split(" = ").collect::>()[1]) -// .expect("sk couldn't be read"); -// let smlen = string_content[i + 6].split(" = ").collect::>()[1] -// .parse::() -// .unwrap(); -// let sm = hex::decode(string_content[i + 7].split(" = ").collect::>()[1]) -// .expect("sm couldn't be read"); -// let v = TestCase { seed, mlen, msg, pk, sk, smlen, sm }; -// test_vectors.insert(count, v); -// i += 7; -// } -// i += 1; -// } -// -// test_vectors -// } diff --git a/crypto/mldsa/tests/mldsa_wycheproof.rs b/crypto/mldsa/tests/mldsa_wycheproof.rs index 8ab2695b5..e7fc2dd88 100644 --- a/crypto/mldsa/tests/mldsa_wycheproof.rs +++ b/crypto/mldsa/tests/mldsa_wycheproof.rs @@ -210,10 +210,7 @@ impl MLDSASignNoSeedTestCase { rnd: test["rnd"].as_str().map(|s| s.to_string()), tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, comment: test["comment"].as_str().unwrap_or("").to_string(), - msg: match test["msg"].as_str() { - Some(msg) => Some(msg.to_string()), - None => None, - }, + msg: test["msg"].as_str().map(|msg| msg.to_string()), mu: test["mu"].as_str().unwrap_or("").to_string(), ctx: test["ctx"].as_str().map(|s| s.to_string()), sig: test["sig"].as_str().unwrap_or("").to_string(), @@ -259,7 +256,7 @@ impl MLDSASignNoSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -334,7 +331,7 @@ impl MLDSASignNoSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -409,7 +406,7 @@ impl MLDSASignNoSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -505,10 +502,7 @@ impl MLDSASignSeedTestCase { rnd: test["rnd"].as_str().map(|s| s.to_string()), tc_id: test["tcId"].as_u64().expect("tcId missing") as u32, comment: test["comment"].as_str().unwrap_or("").to_string(), - msg: match test["msg"].as_str() { - Some(msg) => Some(msg.to_string()), - None => None, - }, + msg: test["msg"].as_str().map(|msg| msg.to_string()), mu: test["mu"].as_str().unwrap_or("").to_string(), ctx: test["ctx"].as_str().map(|s| s.to_string()), sig: test["sig"].as_str().unwrap_or("").to_string(), @@ -578,7 +572,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -677,7 +671,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -776,7 +770,7 @@ impl MLDSASignSeedTestCase { match MuBuilder::compute_mu( &pk.compute_tr(), &hex::decode(self.msg.clone().unwrap()).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), ) { Ok(mu) => mu, Err(SignatureError::LengthError(_)) => { @@ -897,7 +891,7 @@ impl MLDSAVerifyTestCase { match MLDSA44::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { @@ -937,7 +931,7 @@ impl MLDSAVerifyTestCase { match MLDSA65::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { @@ -977,7 +971,7 @@ impl MLDSAVerifyTestCase { match MLDSA87::verify( &pk, &hex::decode(&self.msg).unwrap(), - ctx_vec.as_ref().and_then(|ctx| Some(ctx.as_slice())), + ctx_vec.as_deref(), &hex::decode(&self.sig).unwrap(), ) { Ok(()) => { diff --git a/crypto/mlkem-lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs index da2e778f7..df53db4c8 100644 --- a/crypto/mlkem-lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem-lowmemory/src/mlkem_keys.rs @@ -12,7 +12,7 @@ use bouncycastle_core::errors::KEMError; 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::{Hash, KEMPrivateKey, KEMPublicKey}; +use bouncycastle_core::traits::{Hash, HashOneShot, KEMPrivateKey, KEMPublicKey}; use bouncycastle_sha3::SHA3_256; use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; use core::fmt; @@ -177,14 +177,14 @@ impl PartialEq for MLKEMPublicKey Debug for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } impl Display for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index 06042c67e..3d858bedf 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -8,7 +8,7 @@ use crate::params::{MLKEM512Params, MLKEM768Params, MLKEM1024Params, MLKEMParams use bouncycastle_core::errors::KEMError; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, KEMPrivateKey, KEMPublicKey}; +use bouncycastle_core::traits::{HashOneShot, KEMPrivateKey, KEMPublicKey}; use bouncycastle_sha3::SHA3_256; use bouncycastle_utils::secret::Secret; use core::fmt; @@ -154,7 +154,7 @@ impl MLKEMPublicKeyTrait fn compute_hash(&self) -> [u8; 32] { let mut out = [0u8; 32]; - let bytes_written = H::default().hash_out(&self.encode(), &mut out); + let bytes_written = H::hash_out(&self.encode(), &mut out); debug_assert_eq!(bytes_written, 32); out } @@ -221,14 +221,14 @@ impl PartialEq for MLKEMPublicKey Debug for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } impl Display for MLKEMPublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKey {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } @@ -311,7 +311,7 @@ impl, const PK_LEN: u for MLKEMPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } @@ -320,7 +320,7 @@ impl, const PK_LEN: u for MLKEMPublicKeyExpanded { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let hash = SHA3_256::new().hash(&self.encode()); + let hash = SHA3_256::hash(&self.encode()); write!(f, "MLKEMPublicKeyExpanded {{ alg: {}, pub_key_hash: {:x?} }}", P::ALG_NAME, hash) } } diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 936b7f4f3..eaf26fd6e 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -10,7 +10,7 @@ use bouncycastle_core::errors::{KeyMaterialError, RNGError}; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Hash, HashAlgParams, RNG}; +use bouncycastle_core::traits::{Hash, HashAlgParams, HashOneShot, RNG}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::{min, secret::Secret}; @@ -530,7 +530,7 @@ impl RNG for HashDRBG80090A { /// no_of_bits_to_return is the length of the provided output buffer. /// Because array concatenation is not available in a no_std / no_alloc build, this takes many input parameters. // To leave a parameter unused, simply provide an empty array &[0u8;0] -fn hash_df( +fn hash_df( in1: &[u8], in2: &[u8], in3: &[u8], @@ -638,7 +638,7 @@ fn test_hash_df() { assert!(result_sha512.is_err()); } -fn hashgen(v: &[u8], out: &mut [u8]) { +fn hashgen(v: &[u8], out: &mut [u8]) { // Hashgen Process: // 1. m = ceil(requested_no_of_bits / outlen) // 2. data = V. @@ -666,7 +666,7 @@ fn hashgen(v: &[u8], out: &mut [u8]) { // 4.2 W = W || w. // 4.3 data = (data + 1) mod 2^seedlen. for i in 1..m { - H::default().hash_out( + H::hash_out( &data, &mut out[((i - 1) as usize) * H::OUTPUT_LEN..(i as usize) * H::OUTPUT_LEN], ); @@ -681,7 +681,7 @@ fn hashgen(v: &[u8], out: &mut [u8]) { if remainder != 0 { // let mut temp = [0u8; H::OUTPUT_LEN]; let mut temp = [0u8; 64]; - H::default().hash_out(&data, &mut temp); + H::hash_out(&data, &mut temp); // Copy only what we need from the last block. out[bytes_written..].copy_from_slice(&temp[..remainder as usize]); diff --git a/crypto/sha2/benches/hmac_sha2_benches.rs b/crypto/sha2/benches/hmac_sha2_benches.rs index d8ccf63b5..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 -- ::hashes()", 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 -- ::hashes()", 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 d20886775..ccaee9ef4 100644 --- a/crypto/sha2/src/hkdf.rs +++ b/crypto/sha2/src/hkdf.rs @@ -186,20 +186,17 @@ //! //! | Object | Size (bytes) | //! |---------------------------------------------------|--------------| -//! | `HKDF_SHA256` | 296 | -//! | `HKDF_SHA384` | 392 | -//! | `HKDF_SHA512` | 392 | +//! | `HKDF_SHA256` | 368 | +//! | `HKDF_SHA384` | 656 | +//! | `HKDF_SHA512` | 656 | //! | Suspended `HKDF_SHA256` state | 122 | //! | Suspended `HKDF_SHA384` state | 218 | //! | Suspended `HKDF_SHA512` state | 218 | //! -//! The object is an `Option` of the inner extract-phase HMAC -- 272 bytes for SHA-256, 368 for -//! SHA-384 and SHA-512 -- plus 24 bytes of bookkeeping (the entropy counter, the accumulated security strength -//! and the state-machine tag, with padding). Note that the inner HMAC is written as `HMAC`, which -//! takes the *default* key buffer length: the largest block length across all supported hashes -//! (144 bytes) rather than the 64 or 128 that SHA-256 and SHA-512 actually need. So the inner -//! `HMAC` is 264 bytes where the published [`crate::hmac::HMAC_SHA256`] is 184, and an -//! `HKDF_SHA256` is correspondingly larger than the HMAC it is built on. +//! The object is an `Option` of the inner extract-phase HMAC -- 224 bytes for SHA-256, 416 for +//! SHA-384 and SHA-512, each two hash states -- plus the bookkeeping (the entropy counter, the +//! accumulated security strength and the state-machine tag, with padding); the `Option` is +//! niche-filled for SHA-256 and not for the larger pair, hence the uneven step. //! //! The suspended state is the inner HMAC's suspended state (which is the hash's) plus 14 bytes; the //! salt is deliberately excluded and must be re-supplied on resume. @@ -216,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, @@ -235,12 +232,18 @@ use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{KDF, SuspendableKeyed, XOF}; /*** String constants ***/ +/// The name of HKDF over SHA-224, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA224_NAME: &str = "HKDF-SHA224"; /// pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256"; /// pub const HKDF_SHA384_NAME: &str = "HKDF-SHA384"; /// pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512"; +/// The name of HKDF over SHA-512/224, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA512_224_NAME: &str = "HKDF-SHA512/224"; +/// The name of HKDF over SHA-512/256, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA512_256_NAME: &str = "HKDF-SHA512/256"; /*** Serialized-state length constants ***/ // HKDF wraps the inner extract-phase HMAC's blob in 14 bytes of its own bookkeeping (a 3-byte library diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index ab35cb8f8..4ba15c733 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -9,100 +9,89 @@ //! HMAC over a SHA2 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no -//! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output -//! length, so the buffer must be able to hold a full block. It is taken from -//! [`HashAlgParams::BLOCK_LEN`] rather than restated as a literal so the two cannot drift apart. +//! 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 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::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(); @@ -110,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 { @@ -147,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" @@ -182,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"; @@ -192,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) @@ -203,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(); @@ -216,23 +211,26 @@ //! //! | Object | Size (bytes) | //! |-----------------------------------------------------------|--------------| -//! | `HMAC_SHA224`, `HMAC_SHA256` | 184 | -//! | `HMAC_SHA384`, `HMAC_SHA512` | 344 | +//! | `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 object is the underlying hash object (see the crate-level Memory Usage section), plus one -//! block of key buffer, plus a `usize` recording the key length: 112 + 64 + 8 = 184 for the SHA-256 -//! family, 208 + 128 + 8 = 344 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. @@ -255,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 ***/ /// @@ -276,10 +276,11 @@ 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::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA224_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; @@ -289,10 +290,11 @@ impl HMACParams for SHA224 { /// Public type for HMAC using SHA256. #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA256_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; @@ -302,10 +304,11 @@ impl HMACParams for SHA256 { /// Public type for HMAC using SHA384. #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA384_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; @@ -315,10 +318,11 @@ impl HMACParams for SHA384 { /// Public type for HMAC using SHA512. #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA512_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; @@ -328,10 +332,11 @@ impl HMACParams for SHA512 { /// Public type for HMAC using SHA512/224. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_224 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA512_224_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; @@ -341,10 +346,11 @@ impl HMACParams for SHA512_224 { /// Public type for HMAC using SHA512/256. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_256 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = crate::hkdf::HKDF_SHA512_256_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index e54c733f7..93c4124ce 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -13,11 +13,11 @@ //! //! The simplest usage is via the static functions. //! ``` -//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_core::traits::HashOneShot; //! use bouncycastle_sha2 as sha2; //! //! let data: &[u8] = b"Hello, world!"; -//! let output: Vec = sha2::SHA256::new().hash(data); +//! let output: Vec = sha2::SHA256::hash(data); //! ``` //! //! More advanced usage will require creating a SHA2 object to hold state between successive calls, @@ -232,15 +232,15 @@ pub type SHA512 = SHA512Internal; /// # Example /// /// ``` -/// use bouncycastle_core::traits::{Algorithm, Hash}; +/// use bouncycastle_core::traits::{Algorithm, HashOneShot}; /// use bouncycastle_sha2::{SHA512_256, SHA512t}; /// /// // SHA512t<256> *is* SHA512_256. -/// let digest = SHA512_256::new().hash(b"abc"); -/// assert_eq!(SHA512t::<256>::new().hash(b"abc"), digest); +/// let digest = SHA512_256::hash(b"abc"); +/// assert_eq!(SHA512t::<256>::hash(b"abc"), digest); /// /// // A custom truncation: 96 bits, so a 12-byte digest. -/// let digest = SHA512t::<96>::new().hash(b"abc"); +/// let digest = SHA512t::<96>::hash(b"abc"); /// assert_eq!(digest.len(), 12); /// assert_eq!( as Algorithm>::ALG_NAME, "SHA512/96"); /// ``` diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 1e1a07739..1cab12ab9 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,7 +1,7 @@ use crate::SHA256InitValue; use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, HashOneShot, Suspendable}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{min, secret::Secret}; use core::slice; @@ -280,19 +280,6 @@ impl Hash for SHA256Internal { PARAMS::OUTPUT_LEN } - fn hash(self, data: &[u8]) -> Vec { - let mut output = vec![0u8; PARAMS::OUTPUT_LEN]; - self.hash_out(data, &mut output); - output - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - output.fill(0); - - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, block: &[u8]) { let len = block.len(); @@ -368,10 +355,6 @@ impl Hash for SHA256Internal { } Ok(self.do_final_internal(partial_byte, num_partial_bits, output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) - } } /// Length in bytes of the serialized state of SHA224 and SHA256. @@ -449,3 +432,6 @@ impl Suspendable for SHA256 }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for SHA256Internal {} diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index cfbaf4cc5..141201823 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,7 +1,7 @@ use crate::SHA512InitValue; use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, HashOneShot, Suspendable}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{min, secret::Secret}; use core::slice; @@ -434,19 +434,6 @@ impl Hash for SHA512Internal { PARAMS::OUTPUT_LEN } - fn hash(self, data: &[u8]) -> Vec { - let mut output = vec![0u8; self.output_len()]; - self.hash_out(data, &mut output); - output - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - output.fill(0); - - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, block: &[u8]) { let len = block.len(); @@ -517,10 +504,6 @@ impl Hash for SHA512Internal { } Ok(self.do_final_internal(partial_byte, num_partial_bits, output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) - } } /// Length in bytes of the serialized state of SHA384, SHA512, SHA512/224 and SHA512/256. @@ -596,3 +579,6 @@ impl Suspendable for SHA512 }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for SHA512Internal {} diff --git a/crypto/sha2/tests/sha2_bc-test-data.rs b/crypto/sha2/tests/sha2_bc-test-data.rs index 810669d78..0592edb20 100644 --- a/crypto/sha2/tests/sha2_bc-test-data.rs +++ b/crypto/sha2/tests/sha2_bc-test-data.rs @@ -16,7 +16,7 @@ //! for the next COUNT. 100 counts per file. (This differs from the SHA-3 Monte test, which hashes //! only the previous digest.) -use bouncycastle_core::traits::Hash; +use bouncycastle_core::traits::HashOneShot; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_hex as hex; use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; @@ -54,12 +54,12 @@ fn parse_msg_file(content: &str) -> Vec { } /// Hashes the first `len_bits` bits of `msg` (CAVP MSB-first packing, as the API takes it) with `H`. -fn hash_bits(msg: &[u8], len_bits: usize) -> Vec { +fn hash_bits(msg: &[u8], len_bits: usize) -> Vec { let whole_bytes = len_bits / 8; let partial_bits = len_bits % 8; if partial_bits == 0 { // Note: CAVP writes `Msg = 00` for Len = 0, so always slice rather than using msg directly. - H::default().hash(&msg[..whole_bytes]) + H::hash(&msg[..whole_bytes]) } else { let mut h = H::default(); h.do_update(&msg[..whole_bytes]); @@ -68,7 +68,7 @@ fn hash_bits(msg: &[u8], len_bits: usize) -> Vec { } } -fn run_msg_file(orientation: &str, filename: &str) { +fn run_msg_file(orientation: &str, filename: &str) { let Some(content) = bc_test_data(&format!("crypto/sha2/{orientation}"), filename) else { return; }; @@ -126,7 +126,7 @@ fn parse_monte_file(content: &str) -> MonteFile { } /// SHAVS s. 6.4 Monte Carlo test. -fn run_monte_file(orientation: &str, filename: &str) { +fn run_monte_file(orientation: &str, filename: &str) { let Some(content) = bc_test_data(&format!("crypto/sha2/{orientation}"), filename) else { return; }; @@ -141,7 +141,7 @@ fn run_monte_file(orientation: &str, filename: &str) { m.extend_from_slice(&md[0]); m.extend_from_slice(&md[1]); m.extend_from_slice(&md[2]); - let next = H::default().hash(&m); + let next = H::hash(&m); md.rotate_left(1); md[2] = next; } diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 7f5741b5e..e3c13beae 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -2,7 +2,7 @@ mod sha2_tests { use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams}; + use bouncycastle_core::traits::{Algorithm, AlgorithmInfo, Hash, HashAlgParams, HashOneShot}; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_sha2::*; @@ -81,11 +81,11 @@ mod sha2_tests { /// `partial_bits_known_answers`. #[test] fn partial_bits() { - fn check() { + fn check() { // 0 partial bits == do_final let mut a = H::default(); a.do_update(b"abc"); - assert_eq!(a.do_final_partial_bits(0xFF, 0).unwrap(), H::default().hash(b"abc")); + assert_eq!(a.do_final_partial_bits(0xFF, 0).unwrap(), H::hash(b"abc")); // out of range -> InvalidLength, never a panic for bad in [8usize, 9, 16, 64, usize::MAX] { @@ -106,8 +106,8 @@ mod sha2_tests { assert_eq!(x, y, "n={n}"); assert_ne!(x, z, "n={n}: the leading bit must change the digest"); // and a bit-message is distinct from byte-messages of nearby length - assert_ne!(x, H::default().hash(&[]), "n={n}"); - assert_ne!(x, H::default().hash(&[0xA5 & mask]), "n={n}"); + assert_ne!(x, H::hash(&[]), "n={n}"); + assert_ne!(x, H::hash(&[0xA5 & mask]), "n={n}"); } // the partial-bit path must also work when the pad byte spills into a second block @@ -137,7 +137,7 @@ mod sha2_tests { fn hex(s: &str) -> Vec { (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() } - fn check(cases: &[(usize, u8, u8, usize, &str)]) { + fn check(cases: &[(usize, u8, u8, usize, &str)]) { for &(prefix_len, fill, partial_byte, bits, expected) in cases { let mut h = H::default(); h.do_update(&vec![fill; prefix_len]); diff --git a/crypto/sha2/tests/sha512t_tests.rs b/crypto/sha2/tests/sha512t_tests.rs index c366e6bad..a67a338f2 100644 --- a/crypto/sha2/tests/sha512t_tests.rs +++ b/crypto/sha2/tests/sha512t_tests.rs @@ -20,7 +20,7 @@ //! Generation Function -- including which decimal branch it took -- as well as the truncation. use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams}; +use bouncycastle_core::traits::{Algorithm, AlgorithmInfo, Hash, HashAlgParams, HashOneShot}; use bouncycastle_sha2::{SHA512_224, SHA512_224_NAME, SHA512_256, SHA512_256_NAME, SHA512t}; // The generic name and output length must keep reproducing exactly what the two approved @@ -46,7 +46,7 @@ fn from_hex(s: &str) -> Vec { /// Drives one `SHA512t` through the whole [`Hash`] surface and checks every route agrees with /// `expected_hex`. `construct` builds a fresh instance for each route. -fn check( +fn check( construct: impl Fn() -> H, input: &[u8], expected_hex: &str, @@ -61,12 +61,12 @@ fn check( H::OUTPUT_LEN ); - /*** fn hash(self, data: &[u8]) -> Vec ***/ - assert_eq!(construct().hash(input), expected, "{}: hash()", H::ALG_NAME); + /*** fn hash(data: &[u8]) -> Vec ***/ + assert_eq!(H::hash(input), expected, "{}: hash()", H::ALG_NAME); - /*** fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize ***/ + /*** fn hash_out(data: &[u8], output: &mut [u8]) -> usize ***/ let mut out = vec![0u8; H::OUTPUT_LEN]; - assert_eq!(construct().hash_out(input, &mut out), H::OUTPUT_LEN, "{}: hash_out()", H::ALG_NAME); + assert_eq!(H::hash_out(input, &mut out), H::OUTPUT_LEN, "{}: hash_out()", H::ALG_NAME); assert_eq!(out, expected, "{}: hash_out()", H::ALG_NAME); /*** streaming in one do_update, then do_final() ***/ @@ -266,11 +266,11 @@ fn output_buffer_shorter_and_longer_than_the_digest() { let full = from_hex("44ab9c7c3eb2da370d2c0ed7"); // SHA512/96("") let mut short = [0u8; 5]; - assert_eq!(SHA512t::<96>::new().hash_out(b"", &mut short), 5); + assert_eq!(SHA512t::<96>::hash_out(b"", &mut short), 5); assert_eq!(short, full[..5]); let mut long = [0xAAu8; 20]; - assert_eq!(SHA512t::<96>::new().hash_out(b"", &mut long), 12); + assert_eq!(SHA512t::<96>::hash_out(b"", &mut long), 12); assert_eq!(&long[..12], &full[..]); assert_eq!(&long[12..], &[0u8; 8], "past the digest the buffer is zero-filled"); } 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 b7555c80c..2744bf40d 100644 --- a/crypto/sha3/benches/sha3_benches.rs +++ b/crypto/sha3/benches/sha3_benches.rs @@ -2,7 +2,7 @@ use bouncycastle_rng as rng; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; -use bouncycastle_core::traits::{Hash, RNG, XOF}; +use bouncycastle_core::traits::{Hash, HashOneShot, RNG, XOF}; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256}; fn bench_sha3_224_static(c: &mut Criterion) { @@ -19,7 +19,7 @@ fn bench_sha3_224_static(c: &mut Criterion) { group.throughput(Throughput::Bytes(big_data.len() as u64)); group.bench_function(format!("{} bytes -- ::hashes()", big_data.len() as u64), |b| { b.iter(|| { - SHA3_224::new().hash_out(black_box(&big_data), &mut digest); + SHA3_224::hash_out(black_box(&big_data), &mut digest); black_box(&digest); }) }); @@ -60,7 +60,7 @@ fn bench_sha3_256(c: &mut Criterion) { group.throughput(Throughput::Bytes(big_data.len() as u64)); group.bench_function(format!("{} bytes -- ::hashes()", big_data.len()), |b| { b.iter(|| { - SHA3_256::new().hash_out(black_box(&big_data), &mut digest); + SHA3_256::hash_out(black_box(&big_data), &mut digest); black_box(&digest); }) }); @@ -81,7 +81,7 @@ fn bench_sha3_384(c: &mut Criterion) { group.throughput(Throughput::Bytes(big_data.len() as u64)); group.bench_function(format!("{} bytes -- ::hashes()", big_data.len()), |b| { b.iter(|| { - SHA3_384::new().hash_out(black_box(&big_data), &mut digest); + SHA3_384::hash_out(black_box(&big_data), &mut digest); black_box(&digest); }) }); @@ -102,7 +102,7 @@ fn bench_sha3_512(c: &mut Criterion) { group.throughput(Throughput::Bytes(big_data.len() as u64)); group.bench_function(format!("{} bytes -- ::hashes()", big_data.len()), |b| { b.iter(|| { - SHA3_512::new().hash_out(black_box(&big_data), &mut digest); + SHA3_512::hash_out(black_box(&big_data), &mut digest); black_box(&digest); }) }); diff --git a/crypto/sha3/src/cshake.rs b/crypto/sha3/src/cshake.rs index fafa1f8a9..f0a23072f 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) } } @@ -241,16 +241,6 @@ impl Hash for CSHAKEInternal { self.shake.output_len() } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { self.shake.do_update(data); } @@ -300,10 +290,6 @@ impl Hash for CSHAKEInternal { output[written..].fill(0); Ok(squeezer.do_output_final_out(&mut output[..written])) } - - fn max_security_strength(&self) -> SecurityStrength { - Hash::max_security_strength(&self.shake) - } } impl XOF for CSHAKEInternal { @@ -470,7 +456,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 = (); @@ -490,7 +476,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, @@ -603,3 +589,19 @@ mod tests { } } } + +impl CSHAKEInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 2e67b8b2f..3541ace67 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -9,104 +9,91 @@ //! HMAC over a SHA3 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no -//! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output -//! length, so the buffer must be able to hold a full block. It is taken from -//! [`HashAlgParams::BLOCK_LEN`] -- the values FIPS 202 Table 3 ("Input block sizes for HMAC") gives -//! for the SHA-3 hash functions -- rather than restated as a literal so the two cannot drift apart. -//! Note that for SHA-3 the block length is the sponge *rate*, which *shrinks* as the output size -//! grows, so HMAC-SHA3-224 has the largest key buffer (144 bytes) and HMAC-SHA3-512 the smallest -//! (72 bytes) -- the opposite of the SHA-2 family. +//! 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 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(); @@ -114,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 { @@ -151,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" @@ -189,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"; @@ -199,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) @@ -210,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(); @@ -221,25 +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` | 592 | -//! | `HMAC_SHA3_256` | 584 | -//! | `HMAC_SHA3_384` | 552 | -//! | `HMAC_SHA3_512` | 520 | -//! | Suspended state, all four ([`SuspendableKeyed`]) | 415 | -//! -//! The object is the Keccak-f\[1600\] sponge (440 bytes, see the crate-level Memory Usage section), -//! plus one block of key buffer, plus a `usize` recording the key length -- so 440 + 144 + 8 = 592 -//! for HMAC-SHA3-224 down to 440 + 72 + 8 = 520 for HMAC-SHA3-512. That is why the sizes *decrease* -//! as the output size grows. The suspended state is exactly the inner hash's suspended state -- the +//! | 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 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. @@ -266,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 ***/ /// @@ -280,13 +273,23 @@ pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; /// pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; +/// The name of HKDF over SHA3-224, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA3_224_NAME: &str = "HKDF-SHA3-224"; +/// The name of HKDF over SHA3-256, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA3_256_NAME: &str = "HKDF-SHA3-256"; +/// The name of HKDF over SHA3-384, for `HMACParams::HKDF_ALG_NAME`. +pub const HKDF_SHA3_384_NAME: &str = "HKDF-SHA3-384"; +/// The name of HKDF over SHA3-512, for `HMACParams::HKDF_ALG_NAME`. +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::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = HKDF_SHA3_224_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; @@ -296,10 +299,11 @@ impl HMACParams for SHA3_224 { /// Public type for HMAC using SHA3_256. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = HKDF_SHA3_256_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; @@ -309,10 +313,11 @@ impl HMACParams for SHA3_256 { /// Public type for HMAC using SHA3_384. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = HKDF_SHA3_384_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; @@ -322,10 +327,11 @@ impl HMACParams for SHA3_384 { /// Public type for HMAC using SHA3_512. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = HKDF_SHA3_512_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; diff --git a/crypto/sha3/src/kmac.rs b/crypto/sha3/src/kmac.rs index a3ea3928c..e2909d991 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. /// @@ -100,54 +116,79 @@ pub const SUSPENDED_KMACXOF_STATE_LEN: usize = LIB_VERSION_LEN + CSHAKE_COMPONEN /// it is what separates KMAC from any other cSHAKE-derived function. const KMAC_FUNCTION_NAME: &[u8] = b"KMAC"; -/// Internal struct for KMAC. Use [`KMAC128`] or [`KMAC256`]. -/// -/// KMAC is cSHAKE with the function name `"KMAC"`, the key bound to the front of the message and -/// the requested output length bound to the end (Sec 4.3): -/// -/// ```text -/// KMAC128(K, X, L, S) = cSHAKE128(bytepad(encode_string(K), 168) || X || right_encode(L), -/// L, "KMAC", S) -/// ``` -/// -/// # Two functions, not one function truncated -/// -/// 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. +/// Internal struct for the KMAC key. Use [`KMAC128Key`] or [`KMAC256Key`]. /// -/// [`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 -/// prefix of the fixed-length KMAC over the same inputs, and are not meant to be. +/// 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 KMACInternal { +pub struct KMACKeyInternal { cshake: CSHAKEInternal, output_len: usize, - strength: SecurityStrength, } -impl Algorithm for KMACInternal { - const ALG_NAME: &'static str = PARAMS::KMAC_ALG_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; -} +/// A KMAC128 key: see [`KMACKeyInternal`]. +pub type KMAC128Key = KMACKeyInternal; +/// A KMAC256 key: see [`KMACKeyInternal`]. +pub type KMAC256Key = KMACKeyInternal; -impl KMACInternal { - /// A new KMAC with a customization string and an output length of the caller's choosing. +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. /// - /// `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 [`Self::with_params_allow_weak`] is the escape hatch. /// - /// 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. + /// # 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::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. - pub fn new_with_params( - key: &impl KeyMaterialTrait, + /// [`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, @@ -162,7 +203,7 @@ impl KMACInternal { 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", + "KMAC: provided key has a lower security strength than the instantiated KMAC", ))? } @@ -170,9 +211,102 @@ impl KMACInternal { // 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, strength }) + 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`]. +/// +/// KMAC is cSHAKE with the function name `"KMAC"`, the key bound to the front of the message and +/// the requested output length bound to the end (Sec 4.3): +/// +/// ```text +/// KMAC128(K, X, L, S) = cSHAKE128(bytepad(encode_string(K), 168) || X || right_encode(L), +/// L, "KMAC", S) +/// ``` +/// +/// # Two functions, not one function truncated +/// +/// 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 [`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 +/// prefix of the fixed-length KMAC over the same inputs, and are not meant to be. +#[derive(Clone)] +pub struct KMACInternal { + cshake: CSHAKEInternal, + output_len: usize, +} + +impl Algorithm for KMACInternal { + const ALG_NAME: &'static str = PARAMS::KMAC_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + +impl KMACInternal { /// 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); @@ -180,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 = (); @@ -192,24 +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)?; + // 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()); - // The strength is fixed by the parameter set (see `new_with_params`), not stored. - Ok(Self { - cshake, - output_len, - strength: SecurityStrength::from_bits(PARAMS::SIZE as usize), - }) + 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. @@ -224,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 } @@ -290,10 +412,6 @@ impl MAC for KMACInternal { let computed = self.do_final(); ct::ct_eq_bytes(&computed, mac) } - - fn max_security_strength(&self) -> SecurityStrength { - self.strength - } } /// Internal struct for KMACXOF. Use [`KMACXOF128`] or [`KMACXOF256`]. @@ -321,12 +439,12 @@ impl MAC for KMACInternal { /// Read as a *final* read, it is bound, because a caller that names a length and will not be back /// has said what `L` is: [`XOFSqueezer::do_output_final`] and [`XOF::xof`] absorb /// `right_encode(8n)` and so produce `KMAC(K, X, 8n, S)` exactly (see [`CSHAKESqueezer`]), and -/// the [`Hash`] view -- [`Hash::do_final`], [`Hash::hash`] and [`Hash::hash_out`] -- does the same -/// at the nominal [`Hash::output_len`], since a hash's output length is fixed by its type. +/// the [`Hash`] view -- [`Hash::do_final`] and the inherent [`hash`](Self::hash) and +/// [`hash_out`](Self::hash_out) -- does the same at the nominal [`Hash::output_len`], since a +/// hash's output length is fixed by its type. #[derive(Clone)] pub struct KMACXOFInternal { cshake: CSHAKEInternal, - strength: SecurityStrength, } impl Algorithm for KMACXOFInternal { @@ -337,7 +455,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 @@ -349,12 +467,13 @@ impl KMACXOFInternal { ) -> Result { // The key binding is identical to KMAC's; only the length encoding differs, and that is // applied when output begins. - let kmac = KMACInternal::::new_with_params(key, customization, 0, allow_weak_key)?; - Ok(Self { cshake: kmac.cshake, strength: kmac.strength }) + 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 = (); @@ -362,9 +481,9 @@ 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, strength: SecurityStrength::from_bits(PARAMS::SIZE as usize) }) + Ok(Self { cshake }) } } @@ -392,16 +511,6 @@ impl Hash for KMACXOFInternal { self.cshake.output_len() } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { self.cshake.do_update(data); } @@ -453,10 +562,6 @@ impl Hash for KMACXOFInternal { } Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - self.strength - } } impl XOF for KMACXOFInternal { @@ -481,3 +586,19 @@ impl XOF for KMACXOFInternal { Ok(self.into_squeezer()) } } + +impl KMACXOFInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 6d96a0346..8c6a764f8 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -17,11 +17,11 @@ //! //! The simplest usage is via the one-shot functions. //! ``` -//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_core::traits::HashOneShot; //! use bouncycastle_sha3 as sha3; //! //! let data: &[u8] = b"Hello, world!"; -//! let output: Vec = sha3::SHA3_256::new().hash(data); +//! let output: Vec = sha3::SHA3_256::hash(data); //! ``` //! //! More advanced usage will require creating a SHA3 or SHAKE object to hold state between successive calls, @@ -102,7 +102,7 @@ //! Because [`XOF`] extends [`Hash`], SHAKE can also be used wherever a hash is wanted: //! [`Hash::do_final`] produces the nominal digest size, 32 bytes for SHAKE128 and 64 for SHAKE256 //! (the length at which the output carries the full security level), and the one-shot -//! [`Hash::hash`] does the same. +//! [`HashOneShot::hash`] does the same. //! //! ## KDF //! SHA3 offers Key Derivation Functions in the form of KDF, which is accessed through the [`KDF`] trait, @@ -224,7 +224,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 @@ -249,7 +249,7 @@ use bouncycastle_core::errors::HashError; #[allow(unused_imports)] use bouncycastle_core::key_material::{KeyMaterial, KeyType}; #[allow(unused_imports)] -use bouncycastle_core::traits::{Hash, KDF, MAC, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{Hash, HashOneShot, KDF, MAC, Suspendable, XOF, XOFSqueezer}; // end of doc-only imports pub mod hmac; diff --git a/crypto/sha3/src/parallelhash.rs b/crypto/sha3/src/parallelhash.rs index b975c97c0..0d20dacbd 100644 --- a/crypto/sha3/src/parallelhash.rs +++ b/crypto/sha3/src/parallelhash.rs @@ -250,7 +250,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 = (); @@ -262,7 +262,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); @@ -296,16 +296,6 @@ impl Hash for ParallelHashInternal { self.output_len } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { self.state.do_update(data); } @@ -352,10 +342,6 @@ impl Hash for ParallelHashInternal { } Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } /// Internal struct for ParallelHashXOF (Sec 6.3.1). Use [`ParallelHashXOF128`] or @@ -384,7 +370,7 @@ impl ParallelHashXOFInternal { } } -impl SuspendableComponent for ParallelHashXOFInternal { +impl<'k, PARAMS: SHAKEParams> SuspendableComponent<'k> for ParallelHashXOFInternal { const STATE_LEN: usize = PARALLEL_STATE_LEN; type Key = (); @@ -392,7 +378,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)? }) } } @@ -423,16 +409,6 @@ impl Hash for ParallelHashXOFInternal { self.state.cshake.output_len() } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, data: &[u8]) { self.state.do_update(data); } @@ -480,10 +456,6 @@ impl Hash for ParallelHashXOFInternal { } Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } impl XOF for ParallelHashXOFInternal { @@ -509,3 +481,35 @@ impl XOF for ParallelHashXOFInternal { Ok(self.into_squeezer()) } } + +impl ParallelHashInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} + +impl ParallelHashXOFInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 407d83e63..dd9ae8ba9 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -7,7 +7,7 @@ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; 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, KDF, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, HashOneShot, KDF, Suspendable}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{max, min}; @@ -37,14 +37,6 @@ impl SHA3Internal { } } - /// Swallows errors and simply returns an empty Vec if the hashes fails for whatever reason. - fn hash_internal(mut self, data: &[u8], output: &mut [u8]) -> usize { - output.fill(0); - - self.do_update(data); - self.do_final_out(output) - } - /// Appends the SHA3 domain-separation suffix and pads as per FIPS 202 s. 6.1, then squeezes the digest. /// /// Private, infallible body shared by [`Hash::do_final_out`] and [`Hash::do_final_partial_bits_out`]. @@ -193,16 +185,6 @@ impl Hash for SHA3Internal { PARAMS::OUTPUT_LEN } - fn hash(self, data: &[u8]) -> Vec { - let mut output: Vec = vec![0u8; PARAMS::OUTPUT_LEN]; - _ = self.hash_internal(data, &mut output[..]); - output - } - - fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize { - self.hash_internal(data, output) - } - fn do_update(&mut self, data: &[u8]) { self.keccak.absorb(data) } @@ -246,10 +228,6 @@ impl Hash for SHA3Internal { // do_final_bits_out appends the SHA-3 `01` suffix, pads, and squeezes. Ok(self.do_final_bits_out(partial_byte, num_partial_bits, output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) - } } /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. @@ -300,10 +278,6 @@ impl KDF for SHA3Internal { } self.derive_key_out_final_internal(additional_input, output_key) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) - } } impl Suspendable for SHA3Internal { @@ -348,3 +322,6 @@ impl Suspendable for SHA3Internal< }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for SHA3Internal {} diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 9b332a99c..5847937d2 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -7,7 +7,7 @@ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; 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, KDF, Suspendable, XOF, XOFSqueezer}; +use bouncycastle_core::traits::{Algorithm, Hash, HashOneShot, KDF, Suspendable, XOF, XOFSqueezer}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{max, min}; @@ -322,10 +322,6 @@ impl KDF for SHAKEInternal { } self.derive_key_out_final_internal(additional_input, output_key) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } impl Default for SHAKEInternal { @@ -426,16 +422,6 @@ impl Hash for SHAKEInternal { (PARAMS::SIZE as usize) / 4 } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - /// Infallible, and this is a fact about the type rather than a promise. /// /// Absorbing after squeezing has begun would be wrong -- FIPS 202 defines SHAKE as a single @@ -496,10 +482,6 @@ impl Hash for SHAKEInternal { output[written..].fill(0); Ok(squeezer.do_output_final_out(&mut output[..written])) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } /// The absorb-then-squeeze rule, as a compile error rather than a runtime one. @@ -595,3 +577,6 @@ impl SHAKEInternal { Ok(SHAKESqueezer { shake: self }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for SHAKEInternal {} diff --git a/crypto/sha3/src/tuplehash.rs b/crypto/sha3/src/tuplehash.rs index 1c22fa897..192585ea7 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); @@ -184,18 +184,6 @@ impl Hash for TupleHashInternal { self.output_len } - /// Hashes `data` as a one-element tuple. For more than one element use - /// [`Self::hash_tuple`] or successive [`Hash::do_update`] calls. - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - /// Appends **one tuple element**. See the note on the type: this is not byte-wise streaming. fn do_update(&mut self, data: &[u8]) { absorb_encoded_string_into(&mut self.cshake, data); @@ -247,10 +235,6 @@ impl Hash for TupleHashInternal { } Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } /// Internal struct for TupleHashXOF. Use [`TupleHashXOF128`] or [`TupleHashXOF256`]. @@ -263,9 +247,9 @@ impl Hash for TupleHashInternal { /// /// A *final* read binds it, because a caller that names a length and will not be back has said /// what `L` is: [`XOFSqueezer::do_output_final`] and [`XOF::xof`] produce the fixed-length -/// TupleHash of Sec 5.3 (see [`CSHAKESqueezer`]), and the [`Hash`] view -- [`Hash::do_final`], -/// [`Hash::hash`] and [`Hash::hash_out`] -- does the same at the nominal [`Hash::output_len`], -/// since a hash's output length is fixed by its type. +/// TupleHash of Sec 5.3 (see [`CSHAKESqueezer`]), and the [`Hash`] view -- [`Hash::do_final`] +/// and the inherent [`hash`](Self::hash) and [`hash_out`](Self::hash_out) -- does the same at the +/// nominal [`Hash::output_len`], since a hash's output length is fixed by its type. /// /// [`Hash::do_update`] appends one tuple element, exactly as for [`TupleHashInternal`]. #[derive(Clone)] @@ -293,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 = (); @@ -301,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 }) } @@ -334,16 +318,6 @@ impl Hash for TupleHashXOFInternal { self.cshake.output_len() } - fn hash(mut self, data: &[u8]) -> Vec { - self.do_update(data); - self.do_final() - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - /// Appends **one tuple element**. fn do_update(&mut self, data: &[u8]) { absorb_encoded_string_into(&mut self.cshake, data); @@ -392,10 +366,6 @@ impl Hash for TupleHashXOFInternal { } Ok(self.do_final_out(output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::from_bits(PARAMS::SIZE as usize) - } } impl XOF for TupleHashXOFInternal { @@ -420,3 +390,35 @@ impl XOF for TupleHashXOFInternal { Ok(self.into_squeezer()) } } + +impl TupleHashInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} + +impl TupleHashXOFInternal { + /// One-shot: hashes `data` with this instance's parameters. + /// [`HashOneShot`](bouncycastle_core::traits::HashOneShot) is for the hashes + /// that take none; here the parameters come with the instance, so the one-shot is a method. + pub fn hash(mut self, data: &[u8]) -> Vec { + self.do_update(data); + self.do_final() + } + + /// As [`Self::hash`], into `output`, which is zeroized first; returns the bytes written. + pub fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { + self.do_update(data); + self.do_final_out(output) + } +} diff --git a/crypto/sha3/tests/kmac_bc-test-data.rs b/crypto/sha3/tests/kmac_bc-test-data.rs index efeb55ed3..ae4f029f9 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, @@ -335,13 +336,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 71c6f8189..808815528 100644 --- a/crypto/sha3/tests/kmac_tests.rs +++ b/crypto/sha3/tests/kmac_tests.rs @@ -2,8 +2,13 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey}; use bouncycastle_core::traits::{Algorithm, Hash, MAC, XOF, XOFSqueezer}; -use bouncycastle_sha3::kmac::{KMAC128, KMAC256, KMACXOF128, KMACXOF256}; +use bouncycastle_core_test_framework::from_key_material::TestFrameworkFromAnyKeyMaterial; +use bouncycastle_sha3::kmac::{ + 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 /// the weak-key escape hatch. @@ -17,8 +22,8 @@ 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); @@ -29,9 +34,8 @@ fn output_length_changes_the_function() { #[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"); } @@ -41,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 @@ -76,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 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 the_keys_enforce_the_material_policy() { + let key = key_material(&[0x42u8; 32]); + let framework = TestFrameworkFromAnyKeyMaterial::new(); + framework.test::(&key); + framework.test::(&key); } -/// The default constructor: no customization, nominal output length. +/// `from_key_material`: no customization, nominal output length. #[test] -fn default_constructor_uses_the_nominal_length() { +fn from_key_material_uses_the_nominal_length() { let key = key_material(&[0x42u8; 32]); - assert_eq!(KMAC128::new(&key).unwrap().output_len(), 32); - assert_eq!(KMAC256::new(&key).unwrap().output_len(), 64); + 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"); @@ -125,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" ); } @@ -154,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 @@ -177,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!( @@ -191,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()); } @@ -225,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/parallelhash_bc-test-data.rs b/crypto/sha3/tests/parallelhash_bc-test-data.rs index 5dca6f16a..4d19f8a2b 100644 --- a/crypto/sha3/tests/parallelhash_bc-test-data.rs +++ b/crypto/sha3/tests/parallelhash_bc-test-data.rs @@ -237,12 +237,18 @@ fn parallelhashxof_is_not_parallelhash_truncated() { /// The sample-value test above goes through `hash` only, which left `hash_out` and /// `do_final_out` unexercised: `cargo mutants` could replace each with a constant, and change the /// `* 8` in the `right_encode(L)` that `do_final_out` binds, without a test noticing. -fn check_fixed_view(make: impl Fn() -> H, msg: &[u8], expected: &[u8], ctx: &str) { +fn check_fixed_view( + make: impl Fn() -> H, + hash_out: impl Fn(&[u8], &mut [u8]) -> usize, + msg: &[u8], + expected: &[u8], + ctx: &str, +) { let n = expected.len(); assert_eq!(make().output_len(), n, "{ctx}: output_len"); let mut out = vec![0u8; n]; - assert_eq!(make().hash_out(msg, &mut out), n, "{ctx}: hash_out returns the length"); + assert_eq!(hash_out(msg, &mut out), n, "{ctx}: hash_out returns the length"); assert_eq!(out, expected, "{ctx}: hash_out"); let mut h = make(); @@ -262,13 +268,17 @@ fn check_fixed_view(make: impl Fn() -> H, msg: &[u8], expected: &[u8], assert_eq!(&out[n..], &[0u8; 7], "{ctx}: bytes past the output length are zeroized"); } -/// Every `Hash` and `XOF` entry point of the XOF form, against one paired sample value. +/// Every `Hash` and `XOF` entry point of the XOF form, plus the inherent one-shots, against one +/// paired sample value. The one-shots are inherent (the type takes parameters, so it is not +/// `HashOneShot`), hence they come in as closures. /// /// The samples ask for the nominal length, and the `Hash` view is a final read at that length, so /// it binds `L` and must reproduce the *fixed-length* sample; reading the stream with `do_output` /// must reproduce the XOF one. fn check_xof_view( make: impl Fn() -> X, + hash: impl Fn(&[u8]) -> Vec, + hash_out: impl Fn(&[u8], &mut [u8]) -> usize, msg: &[u8], expected: &[u8], fixed_expected: &[u8], @@ -278,10 +288,10 @@ fn check_xof_view( assert_eq!(make().output_len(), n, "{ctx}: the samples ask for the nominal length"); assert_eq!(fixed_expected.len(), n, "{ctx}: ... and the paired samples share it"); - assert_eq!(make().hash(msg), fixed_expected, "{ctx}: hash"); + assert_eq!(hash(msg), fixed_expected, "{ctx}: hash"); let mut out = vec![0u8; n]; - assert_eq!(make().hash_out(msg, &mut out), n, "{ctx}: hash_out returns the length"); + assert_eq!(hash_out(msg, &mut out), n, "{ctx}: hash_out returns the length"); assert_eq!(out, fixed_expected, "{ctx}: hash_out"); let mut x = make(); @@ -337,8 +347,20 @@ fn hash_trait_view_agrees_with_the_sample_values() { let (b, s) = (v.block_size, v.s.as_bytes()); let ctx = format!("COUNT {i}: ParallelHash{} B={b}", v.strength); match v.strength { - 128 => check_fixed_view(|| ParallelHash128::new(b, s, n), &v.msg, &v.output, &ctx), - 256 => check_fixed_view(|| ParallelHash256::new(b, s, n), &v.msg, &v.output, &ctx), + 128 => check_fixed_view( + || ParallelHash128::new(b, s, n), + |m, o| ParallelHash128::new(b, s, n).hash_out(m, o), + &v.msg, + &v.output, + &ctx, + ), + 256 => check_fixed_view( + || ParallelHash256::new(b, s, n), + |m, o| ParallelHash256::new(b, s, n).hash_out(m, o), + &v.msg, + &v.output, + &ctx, + ), other => panic!("COUNT {i}: unexpected strength {other}"), } } @@ -357,12 +379,24 @@ fn xof_trait_view_agrees_with_the_sample_values() { let (b, s) = (v.block_size, v.s.as_bytes()); let ctx = format!("COUNT {i}: ParallelHashXOF{} B={b}", v.strength); match v.strength { - 128 => { - check_xof_view(|| ParallelHashXOF128::new(b, s), &v.msg, &v.output, &f.output, &ctx) - } - 256 => { - check_xof_view(|| ParallelHashXOF256::new(b, s), &v.msg, &v.output, &f.output, &ctx) - } + 128 => check_xof_view( + || ParallelHashXOF128::new(b, s), + |m| ParallelHashXOF128::new(b, s).hash(m), + |m, o| ParallelHashXOF128::new(b, s).hash_out(m, o), + &v.msg, + &v.output, + &f.output, + &ctx, + ), + 256 => check_xof_view( + || ParallelHashXOF256::new(b, s), + |m| ParallelHashXOF256::new(b, s).hash(m), + |m, o| ParallelHashXOF256::new(b, s).hash_out(m, o), + &v.msg, + &v.output, + &f.output, + &ctx, + ), other => panic!("COUNT {i}: unexpected strength {other}"), } } diff --git a/crypto/sha3/tests/sha3_bc-test-data.rs b/crypto/sha3/tests/sha3_bc-test-data.rs index 1bd6ae986..00a377e2b 100644 --- a/crypto/sha3/tests/sha3_bc-test-data.rs +++ b/crypto/sha3/tests/sha3_bc-test-data.rs @@ -21,7 +21,7 @@ //! * Monte (s. 6.2.2) — `MD0 = Seed`; for i in 1..=1000: `MDi = SHA3(MDi-1)`; report `MD1000` //! per COUNT and reseed with it. -use bouncycastle_core::traits::Hash; +use bouncycastle_core::traits::{Hash, HashOneShot}; use bouncycastle_core_test_framework::test_data_loaders::bc_test_data; use bouncycastle_hex as hex; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; @@ -73,12 +73,12 @@ fn parse_msg_file(content: &str) -> Vec { /// Hashes the first `len_bits` bits of `msg` (FIPS 202 B.1 packing: excess bits in the LSBs, so the /// final byte is bit-reversed into the API's MSB-first order). -fn sha3_bits(msg: &[u8], len_bits: usize) -> Vec { +fn sha3_bits(msg: &[u8], len_bits: usize) -> Vec { let whole_bytes = len_bits / 8; let partial_bits = len_bits % 8; if partial_bits == 0 { // CAVP writes `Msg = 00` for Len = 0, so always slice rather than using msg directly. - H::default().hash(&msg[..whole_bytes]) + H::hash(&msg[..whole_bytes]) } else { let mut h = H::default(); h.do_update(&msg[..whole_bytes]); @@ -87,7 +87,7 @@ fn sha3_bits(msg: &[u8], len_bits: usize) -> Vec { } } -fn run_sha3_msg_file(orientation: &str, filename: &str) { +fn run_sha3_msg_file(orientation: &str, filename: &str) { let Some(content) = bc_test_data(&format!("crypto/sha3/{orientation}"), filename) else { return; }; @@ -110,7 +110,7 @@ fn run_sha3_msg_file(orientation: &str, filename: &str) { } /// SHA3VS s. 6.2.2 Monte Carlo test for the fixed-length SHA3 functions. -fn run_sha3_monte_file(orientation: &str, filename: &str) { +fn run_sha3_monte_file(orientation: &str, filename: &str) { let Some(content) = bc_test_data(&format!("crypto/sha3/{orientation}"), filename) else { return; }; @@ -129,7 +129,7 @@ fn run_sha3_monte_file(orientation: &str, filename: &str) { for (count, expected) in mds.iter().enumerate() { // MD0 = Seed; for i = 1 to 1000: MDi = SHA3(MDi-1); MDj = MD1000; Seed = MDj for _ in 1..=1000 { - md = H::default().hash(&md); + md = H::hash(&md); } assert_eq!(&md, expected, "{orientation}/{filename}: COUNT = {count}"); } diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index e58a7eb6b..abad4f752 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -6,7 +6,7 @@ mod sha3_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Hash, HashAlgParams, KDF}; + use bouncycastle_core::traits::{AlgorithmInfo, Hash, HashAlgParams, HashOneShot, KDF}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; @@ -44,44 +44,44 @@ mod sha3_tests { #[test] fn test_static_hash() { // success case -- return vec version - assert_eq!(SHA3_224::new().hash(&DUMMY_SEED[..512]), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); - assert_eq!(SHA3_256::new().hash(&DUMMY_SEED[..512]), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); - assert_eq!(SHA3_384::new().hash(&DUMMY_SEED[..512]), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); - assert_eq!(SHA3_512::new().hash(&DUMMY_SEED[..512]), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); + assert_eq!(SHA3_224::hash(&DUMMY_SEED[..512]), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); + assert_eq!(SHA3_256::hash(&DUMMY_SEED[..512]), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); + assert_eq!(SHA3_384::hash(&DUMMY_SEED[..512]), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); + assert_eq!(SHA3_512::hash(&DUMMY_SEED[..512]), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); // success case -- output slice version // We're just gonna hand an output slice that's too big and the result had better get written to the beginning of it. let mut out: [u8; 64] = [0; 64]; assert_eq!(SHA3_224::new().output_len(), 28); - let bytes_written = SHA3_224::new().hash_out(&DUMMY_SEED[..512], &mut out); + let bytes_written = SHA3_224::hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(bytes_written, 28); assert_eq!(&out[..28], b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); assert_eq!(SHA3_256::new().output_len(), 32); - let bytes_written = SHA3_256::new().hash_out(&DUMMY_SEED[..512], &mut out); + let bytes_written = SHA3_256::hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out[..32], b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); assert_eq!(bytes_written, 32); assert_eq!(SHA3_384::new().output_len(), 48); - let bytes_written = SHA3_384::new().hash_out(&DUMMY_SEED[..512], &mut out); + let bytes_written = SHA3_384::hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out[..48], b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); assert_eq!(bytes_written, 48); assert_eq!(SHA3_512::new().output_len(), 64); - let bytes_written = SHA3_512::new().hash_out(&DUMMY_SEED[..512], &mut out); + let bytes_written = SHA3_512::hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out, b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); assert_eq!(bytes_written, 64); // check that the bytes of an oversized output buffer past the digest length get zeroized. let mut out = DUMMY_SEED.clone(); - SHA3_256::new().hash_out(DUMMY_SEED, &mut out); + SHA3_256::hash_out(DUMMY_SEED, &mut out); assert!(out[32..].iter().all(|&b| b == 0)); } #[test] fn test_do_update() { // success case -- return vec version - let output1 = SHA3_224::new().hash(DUMMY_SEED); + let output1 = SHA3_224::hash(DUMMY_SEED); let mut sha3 = SHA3_224::new(); for i in (0..DUMMY_SEED.len()).step_by(8) { @@ -162,7 +162,7 @@ mod sha3_tests { } let mut h = SHA3_256::new(); h.do_update(b"abc"); - assert_eq!(h.do_final_partial_bits(0xFF, 0).unwrap(), SHA3_256::new().hash(b"abc")); + assert_eq!(h.do_final_partial_bits(0xFF, 0).unwrap(), SHA3_256::hash(b"abc")); } #[test] @@ -401,17 +401,13 @@ mod sha3_tests { #[test] fn test_security_strength() { - assert_eq!(Hash::max_security_strength(&SHA3_224::default()), SecurityStrength::_112bit); - assert_eq!(KDF::max_security_strength(&SHA3_224::default()), SecurityStrength::_112bit); + assert_eq!(SHA3_224::default().max_security_strength(), SecurityStrength::_112bit); - assert_eq!(Hash::max_security_strength(&SHA3_256::default()), SecurityStrength::_128bit); - assert_eq!(KDF::max_security_strength(&SHA3_256::default()), SecurityStrength::_128bit); + assert_eq!(SHA3_256::default().max_security_strength(), SecurityStrength::_128bit); - assert_eq!(Hash::max_security_strength(&SHA3_384::default()), SecurityStrength::_192bit); - assert_eq!(KDF::max_security_strength(&SHA3_384::default()), SecurityStrength::_192bit); + assert_eq!(SHA3_384::default().max_security_strength(), SecurityStrength::_192bit); - assert_eq!(Hash::max_security_strength(&SHA3_512::default()), SecurityStrength::_256bit); - assert_eq!(KDF::max_security_strength(&SHA3_512::default()), SecurityStrength::_256bit); + assert_eq!(SHA3_512::default().max_security_strength(), SecurityStrength::_256bit); } #[test] diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index b004407c3..3bdf84d95 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -7,7 +7,7 @@ mod shake_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Hash, KDF, XOF, XOFSqueezer}; + use bouncycastle_core::traits::{AlgorithmInfo, Hash, HashOneShot, KDF, XOF, XOFSqueezer}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_core_test_framework::xof::TestFrameworkXOF; @@ -74,8 +74,8 @@ mod shake_tests { assert_eq!(SHAKE256::new().output_len(), 64, "nominal digest size for SHAKE256"); // and do_final actually produces that many bytes - assert_eq!(SHAKE128::new().hash(b"abc").len(), 32); - assert_eq!(SHAKE256::new().hash(b"abc").len(), 64); + assert_eq!(SHAKE128::hash(b"abc").len(), 32); + assert_eq!(SHAKE256::hash(b"abc").len(), 64); } /// The `Hash` view writes [`Hash::output_len`] bytes and zeroizes the rest of the buffer; the @@ -88,8 +88,8 @@ mod shake_tests { #[test] fn the_hash_view_writes_output_len_bytes_and_zeroes_the_rest() { let mut hash_view = [0xFFu8; 100]; - assert_eq!(SHAKE128::new().hash_out(b"abc", &mut hash_view), 32, "the nominal length"); - assert_eq!(&hash_view[..32], &SHAKE128::new().hash(b"abc")[..], "... written in full"); + assert_eq!(SHAKE128::hash_out(b"abc", &mut hash_view), 32, "the nominal length"); + assert_eq!(&hash_view[..32], &SHAKE128::hash(b"abc")[..], "... written in full"); assert_eq!(&hash_view[32..], &[0u8; 68][..], "everything past output_len is zeroized"); // do_final_out and the byte-aligned partial-bit spelling follow the same rule. @@ -107,7 +107,7 @@ mod shake_tests { // A short buffer truncates, as it always did. let mut short = [0xFFu8; 16]; - assert_eq!(SHAKE128::new().hash_out(b"abc", &mut short), 16); + assert_eq!(SHAKE128::hash_out(b"abc", &mut short), 16); assert_eq!(&short[..], &hash_view[..16], "a short buffer truncates the same output"); // The XOF spelling takes its length from the buffer and keeps reading past output_len. @@ -118,7 +118,7 @@ mod shake_tests { // SHAKE256's nominal length is 64, so its split lands elsewhere. let mut hash_view = [0xFFu8; 100]; - assert_eq!(SHAKE256::new().hash_out(b"abc", &mut hash_view), 64, "the nominal length"); + assert_eq!(SHAKE256::hash_out(b"abc", &mut hash_view), 64, "the nominal length"); assert_eq!(&hash_view[64..], &[0u8; 36][..], "everything past output_len is zeroized"); } @@ -272,10 +272,8 @@ mod shake_tests { #[test] fn security_strength() { - assert_eq!(KDF::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); - assert_eq!(Hash::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); - assert_eq!(KDF::max_security_strength(&SHAKE256::default()), SecurityStrength::_256bit); - assert_eq!(Hash::max_security_strength(&SHAKE256::default()), SecurityStrength::_256bit); + assert_eq!(SHAKE128::default().max_security_strength(), SecurityStrength::_128bit); + assert_eq!(SHAKE256::default().max_security_strength(), SecurityStrength::_256bit); } #[test] diff --git a/crypto/sha3/tests/sp800_185_suspend_tests.rs b/crypto/sha3/tests/sp800_185_suspend_tests.rs index a017904fd..3b8c79c57 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::*; @@ -92,10 +93,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() }); } @@ -156,7 +157,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"); @@ -191,11 +194,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); @@ -241,7 +264,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"")), @@ -249,7 +272,7 @@ fn state_tags_are_distinct() { tag(ParallelHashXOF128::new(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/sha3/tests/tuplehash_bc-test-data.rs b/crypto/sha3/tests/tuplehash_bc-test-data.rs index ff7218a68..dc4f72f09 100644 --- a/crypto/sha3/tests/tuplehash_bc-test-data.rs +++ b/crypto/sha3/tests/tuplehash_bc-test-data.rs @@ -275,12 +275,16 @@ fn check_fixed_view(make: impl Fn() -> H, tuple: &[&[u8]], expected: &[ let Some((last, rest)) = tuple.split_last() else { return }; let mut h = make(); rest.iter().for_each(|e| h.do_update(e)); - assert_eq!(h.hash(last), expected, "{ctx}: hash as the final element"); + let mut h = h; + h.do_update(last); + assert_eq!(h.do_final(), expected, "{ctx}: the last element through do_update"); let mut h = make(); rest.iter().for_each(|e| h.do_update(e)); let mut out = vec![0u8; n]; - assert_eq!(h.hash_out(last, &mut out), n, "{ctx}: hash_out returns the length"); + let mut h = h; + h.do_update(last); + assert_eq!(h.do_final_out(&mut out), n, "{ctx}: do_final_out returns the length"); assert_eq!(out, expected, "{ctx}: hash_out"); } @@ -336,12 +340,16 @@ fn check_xof_view( let Some((last, rest)) = tuple.split_last() else { return }; let mut x = make(); rest.iter().for_each(|e| x.do_update(e)); - assert_eq!(x.hash(last), fixed_expected, "{ctx}: hash"); + let mut x = x; + x.do_update(last); + assert_eq!(x.do_final(), fixed_expected, "{ctx}: the last element through do_update"); let mut x = make(); rest.iter().for_each(|e| x.do_update(e)); let mut out = vec![0u8; n]; - assert_eq!(x.hash_out(last, &mut out), n, "{ctx}: hash_out returns the length"); + let mut x = x; + x.do_update(last); + assert_eq!(x.do_final_out(&mut out), n, "{ctx}: do_final_out returns the length"); assert_eq!(out, fixed_expected, "{ctx}: hash_out"); // The XOF reading of the stream is do_output; the one-shots bind the length they are given, 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 29fcababe..0d928fd72 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -9,21 +9,21 @@ //! as part of the library's public API. See [`bouncycastle_hmac`] for the full description of the //! three-phase [`MAC`] lifecycle, key typing and suspend/resume. //! -//! The key buffer length is the underlying hash's block length: per RFC 2104, a key no longer than -//! the block is used verbatim, and only longer keys are pre-hashed down to the output length, so the -//! buffer must be able to hold a full block. It is taken from [`HashAlgParams::BLOCK_LEN`] -- the -//! 512-bit block GB/T 32905-2016 s. 5.2 pads to -- rather than restated as a literal so the two -//! cannot drift apart. +//! 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 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); //! ``` //! @@ -53,20 +53,23 @@ 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 ***/ /// Algorithm name string for HMAC-SM3, as used by the factories and CLI. pub const HMAC_SM3_NAME: &str = "HMAC-SM3"; +/// The name of HKDF over SM3, for `HMACParams::HKDF_ALG_NAME`. +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::BLOCK_LEN }>; +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; + const HKDF_ALG_NAME: &'static str = HKDF_SM3_NAME; const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 const HMAC_OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; diff --git a/crypto/sm3/src/lib.rs b/crypto/sm3/src/lib.rs index 79a7d9483..6e8d1f92f 100644 --- a/crypto/sm3/src/lib.rs +++ b/crypto/sm3/src/lib.rs @@ -10,11 +10,11 @@ //! //! The simplest usage is via the one-shot functions. //! ``` -//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_core::traits::HashOneShot; //! use bouncycastle_sm3::SM3; //! //! let data: &[u8] = b"abc"; -//! let output: Vec = SM3::new().hash(data); +//! let output: Vec = SM3::hash(data); //! ``` //! //! It also has a streaming API that can accept input in chunks of any size. diff --git a/crypto/sm3/src/sm3.rs b/crypto/sm3/src/sm3.rs index 7290f9685..b351b13fb 100644 --- a/crypto/sm3/src/sm3.rs +++ b/crypto/sm3/src/sm3.rs @@ -1,6 +1,5 @@ use bouncycastle_core::errors::{HashError, SuspendableError}; -use bouncycastle_core::security_strength::SecurityStrength; -use bouncycastle_core::traits::{Hash, Suspendable}; +use bouncycastle_core::traits::{Hash, HashOneShot, Suspendable}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{min, secret::Secret}; use core::slice; @@ -237,17 +236,6 @@ impl Hash for SM3 { 32 } - fn hash(self, data: &[u8]) -> Vec { - let mut output = vec![0u8; 32]; - self.hash_out(data, &mut output); - output - } - - fn hash_out(mut self, data: &[u8], output: &mut [u8]) -> usize { - self.do_update(data); - self.do_final_out(output) - } - fn do_update(&mut self, block: &[u8]) { let len = block.len(); @@ -318,10 +306,6 @@ impl Hash for SM3 { } Ok(self.do_final_internal(partial_byte, num_partial_bits, output)) } - - fn max_security_strength(&self) -> SecurityStrength { - SecurityStrength::_128bit - } } /// Length in bytes of the serialized state of SM3. @@ -377,3 +361,6 @@ impl Suspendable for SM3 { Ok(SM3 { v, byte_count, x_buf, x_buf_off }) } } + +// The one-shot form: `Default` is the function itself. +impl HashOneShot for SM3 {} diff --git a/crypto/sm3/tests/sm3_tests.rs b/crypto/sm3/tests/sm3_tests.rs index 7d5fb697b..47097ff46 100644 --- a/crypto/sm3/tests/sm3_tests.rs +++ b/crypto/sm3/tests/sm3_tests.rs @@ -2,7 +2,9 @@ mod sm3_tests { use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, HashAlgParams}; + use bouncycastle_core::traits::{ + Algorithm, AlgorithmInfo, AlgorithmOID, Hash, HashAlgParams, HashOneShot, + }; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_hex as hex; @@ -65,7 +67,7 @@ mod sm3_tests { "7C0240F88F1CD4E16352A73C17B7F16F07353E53A176D684A9FE0C6BB798E857", )); assert_eq!( - SM3::new().hash(&msg), + SM3::hash(&msg), h("f4a38489e32b45b6f876e3ac2168ca392362dc8f23459c1d1146fc3dbfb7bc9a") ); } @@ -85,7 +87,7 @@ mod sm3_tests { (119, "8f3ea392a89a7119982d6634660db1a95f35d68267a2235e3255998a857f4fbf"), (128, "a9e7985473ca09df1510d83b572f72375430756c4a661b00724afeb8b75dd0a5"), ] { - assert_eq!(SM3::new().hash(&DUMMY_SEED[..len]), h(expected), "len={len}"); + assert_eq!(SM3::hash(&DUMMY_SEED[..len]), h(expected), "len={len}"); // and the same via byte-at-a-time streaming, which exercises every x_buf_off value let mut sm3 = SM3::new(); @@ -125,7 +127,7 @@ mod sm3_tests { fn partial_bits() { let mut a = SM3::new(); a.do_update(b"abc"); - assert_eq!(a.do_final_partial_bits(0xFF, 0).unwrap(), SM3::new().hash(b"abc")); + assert_eq!(a.do_final_partial_bits(0xFF, 0).unwrap(), SM3::hash(b"abc")); for bad in [8usize, 9, 16, 64, usize::MAX] { let mut sm3 = SM3::new(); @@ -148,8 +150,8 @@ mod sm3_tests { let z = SM3::new().do_final_partial_bits(0xA5 ^ 0x80, n).unwrap(); assert_eq!(x, y, "n={n}"); assert_ne!(x, z, "n={n}: the leading bit must change the digest"); - assert_ne!(x, SM3::new().hash(&[]), "n={n}"); - assert_ne!(x, SM3::new().hash(&[0xA5 & mask]), "n={n}"); + assert_ne!(x, SM3::hash(&[]), "n={n}"); + assert_ne!(x, SM3::hash(&[0xA5 & mask]), "n={n}"); } for len in [55usize, 56, 63, 64, 119, 128] { 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/Cargo.toml b/mem_usage_benches/Cargo.toml index fb55fb094..9411b576a 100644 --- a/mem_usage_benches/Cargo.toml +++ b/mem_usage_benches/Cargo.toml @@ -30,3 +30,11 @@ path = "src/bench_ccm_mem_usage.rs" [[bin]] name = "bench_ascon_mem_usage" path = "src/bench_ascon_mem_usage.rs" + +[[bin]] +name = "bench_blake2_mem_usage" +path = "src/bench_blake2_mem_usage.rs" + +[[bin]] +name = "bench_blake3_mem_usage" +path = "src/bench_blake3_mem_usage.rs" 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 bb13906b0..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,8 +68,9 @@ 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, RNG, SymmetricCipherDecryptor, + 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], @@ -523,14 +545,12 @@ fn bench_hash256_final() { #[inline(never)] fn bench_hash256_oneshot() { eprintln!("AsconHash256::hash_out, {MSG_LEN} B"); - let h = black_box(AsconHash256::new()); let msg = black_box(message::(0xa5)); measure(move || { let mut out = [0u8; 32]; - let op = black_box( - ::hash_out as fn(AsconHash256, &[u8], &mut [u8]) -> usize, - ); - let n = op(h, &msg, &mut out); + let op = + black_box(::hash_out as fn(&[u8], &mut [u8]) -> usize); + let n = op(&msg, &mut out); print!("{:x?}", &out[..n]); }); } @@ -644,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_blake2_mem_usage.rs b/mem_usage_benches/src/bench_blake2_mem_usage.rs new file mode 100644 index 000000000..28791077f --- /dev/null +++ b/mem_usage_benches/src/bench_blake2_mem_usage.rs @@ -0,0 +1,170 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! ```text +//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake2_mem_usage > /dev/null +//! +//! ms_print massif.out.* +//! ``` +//! +//! or, shoved all into one line: +//! +//! ```text +//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake2_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! ``` +//! +//! Make sure you build in release mode! +//! +//! The code is using print!() to force the compiler not to optimize away the actual code. +//! It is printing important outputs for benchmarking to stderr so that the rest can be mapped to /dev/null +//! (this is because /usr/bin/time prints useful outputs to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. `print_struct_sizes()` is the source of +//! the numbers in the "Memory Usage" table in the bouncycastle-blake2 crate docs. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle::blake2::{ + BLAKE2b_512, BLAKE2b_512_MAC, BLAKE2b_512_MACKey, BLAKE2s_256, BLAKE2s_256_MAC, + BLAKE2s_256_MACKey, SUSPENDED_BLAKE2B_512_MAC_STATE_LEN, SUSPENDED_BLAKE2B_512_STATE_LEN, + SUSPENDED_BLAKE2S_256_MAC_STATE_LEN, SUSPENDED_BLAKE2S_256_STATE_LEN, +}; +use bouncycastle::core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{Hash, HashOneShot, MAC, Suspendable, SuspendableKeyed}; + +/// A 1 KiB message: 8 BLAKE2b blocks, 16 BLAKE2s blocks. +const MSG: [u8; 1024] = [0xA5; 1024]; + +/// This prints the in-memory size of all the hash / MAC objects and the suspended states. +fn print_struct_sizes() { + use core::mem::size_of; + + println!("\nBLAKE2"); + 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!("SUSPENDED_BLAKE2B_512_STATE_LEN: {}", SUSPENDED_BLAKE2B_512_STATE_LEN); + println!("SUSPENDED_BLAKE2B_512_MAC_STATE_LEN: {}", SUSPENDED_BLAKE2B_512_MAC_STATE_LEN); + println!("SUSPENDED_BLAKE2S_256_STATE_LEN: {}", SUSPENDED_BLAKE2S_256_STATE_LEN); + println!("SUSPENDED_BLAKE2S_256_MAC_STATE_LEN: {}", SUSPENDED_BLAKE2S_256_MAC_STATE_LEN); +} + +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_blake2b_512_hash() { + eprintln!("BLAKE2b-512/hash"); + + let mut out = [0u8; 64]; + BLAKE2b_512::hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_hash() { + eprintln!("BLAKE2s-256/hash"); + + let mut out = [0u8; 32]; + BLAKE2s_256::hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake2b_512_streaming() { + eprintln!("BLAKE2b-512/do_update+do_final_out"); + + let mut h = BLAKE2b_512::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 64]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_streaming() { + eprintln!("BLAKE2s-256/do_update+do_final_out"); + + let mut h = BLAKE2s_256::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2b_512_mac() { + eprintln!("BLAKE2b-512-MAC/mac_out"); + + let key = KeyMaterial512::from_bytes_as_type(&[0x0Bu8; 64], KeyType::MACKey) + .expect("a 64-byte MAC key"); + let mac_key = + BLAKE2b_512_MACKey::from_key_material_allow_weak(&key).expect("a 64-byte MAC key"); + let mut out = [0u8; 64]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 64-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake2s_256_mac() { + eprintln!("BLAKE2s-256-MAC/mac_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut out = [0u8; 32]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake2b_512_suspend_resume() { + eprintln!("BLAKE2b-512/suspend+from_suspended"); + + let mut h = BLAKE2b_512::new(); + h.do_update(&MSG[..500]); + let state = h.suspend(); + let mut h = + BLAKE2b_512::from_suspended(state).expect("round-trip of a freshly suspended state"); + h.do_update(&MSG[500..]); + let mut out = [0u8; 64]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake2s_256_mac_suspend_resume() { + eprintln!("BLAKE2s-256-MAC/suspend+from_suspended"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE2s_256_MACKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut mac = mac_key.create_generator(); + mac.do_update(&MSG[..500]); + let state = mac.suspend(); + let mut mac = BLAKE2s_256_MAC::from_suspended(state, &mac_key) + .expect("round-trip of a freshly suspended state"); + mac.do_update(&MSG[500..]); + let mut out = [0u8; 32]; + mac.do_final_out(&mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn main() { + print_struct_sizes() + // bench_do_nothing() + // bench_blake2b_512_hash() + // bench_blake2s_256_hash() + // bench_blake2b_512_streaming() + // bench_blake2s_256_streaming() + // bench_blake2b_512_mac() + // bench_blake2s_256_mac() + // bench_blake2b_512_suspend_resume() + // bench_blake2s_256_mac_suspend_resume() +} diff --git a/mem_usage_benches/src/bench_blake3_mem_usage.rs b/mem_usage_benches/src/bench_blake3_mem_usage.rs new file mode 100644 index 000000000..4ec1e26e2 --- /dev/null +++ b/mem_usage_benches/src/bench_blake3_mem_usage.rs @@ -0,0 +1,168 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! ```text +//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake3_mem_usage > /dev/null +//! +//! ms_print massif.out.* +//! ``` +//! +//! or, shoved all into one line: +//! +//! ```text +//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_blake3_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! ``` +//! +//! Make sure you build in release mode! +//! +//! The code is using print!() to force the compiler not to optimize away the actual code. +//! It is printing important outputs for benchmarking to stderr so that the rest can be mapped to /dev/null +//! (this is because /usr/bin/time prints useful outputs to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. `print_struct_sizes()` is the source of +//! the numbers in the "Memory Usage" table in the bouncycastle-blake3 crate docs. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle::blake3::{ + BLAKE3, BLAKE3DeriveKey, BLAKE3DeriveKeySqueezer, BLAKE3KeyedHash, BLAKE3KeyedHashKey, + BLAKE3KeyedSqueezer, BLAKE3Squeezer, SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN, + SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN, SUSPENDED_BLAKE3_STATE_LEN, +}; +use bouncycastle::core::key_material::{KeyMaterial256, KeyType}; +use bouncycastle::core::symmetric_key::{FromAnyKeyMaterial, MacGenerationKey}; +use bouncycastle::core::traits::{Hash, HashOneShot, MAC, Suspendable, XOF, XOFSqueezer}; + +/// A 4 KiB message: four 1 KiB chunks, so the CV stack is pushed and merged (BLAKE3 spec s. 5.1.2). +const MSG: [u8; 4096] = [0xA5; 4096]; + +/// This prints the in-memory size of all the hasher / squeezer objects and the suspended states. +fn print_struct_sizes() { + use core::mem::size_of; + + println!("\nBLAKE3"); + 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::()); + println!("SUSPENDED_BLAKE3_STATE_LEN: {}", SUSPENDED_BLAKE3_STATE_LEN); + println!("SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN: {}", SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN); + println!("SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN: {}", SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN); +} + +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_blake3_hash() { + eprintln!("BLAKE3/hash"); + + let mut out = [0u8; 32]; + BLAKE3::hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_blake3_streaming() { + eprintln!("BLAKE3/do_update+do_final_out"); + + let mut h = BLAKE3::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_xof() { + eprintln!("BLAKE3/absorb+squeeze_out"); + + let mut x = BLAKE3::new(); + x.do_update(&MSG); + let mut out = [0u8; 512]; + let mut x = x.into_squeezer(); + x.do_output_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_keyed_hash() { + eprintln!("BLAKE3-KeyedHash/mac_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut out = [0u8; 32]; + mac_key.create_generator().mac_out(&MSG, &mut out).expect("a 32-byte output buffer"); + println!("{:x?}", out); +} + +fn bench_blake3_keyed_hash_xof() { + eprintln!("BLAKE3-KeyedHash/absorb+into_squeezer+squeeze_out"); + + let key = KeyMaterial256::from_bytes_as_type(&[0x0Bu8; 32], KeyType::MACKey) + .expect("a 32-byte MAC key"); + let mac_key = + BLAKE3KeyedHashKey::from_key_material_allow_weak(&key).expect("a 32-byte MAC key"); + let mut mac = mac_key.create_generator(); + mac.do_update(&MSG); + let mut out = [0u8; 512]; + mac.into_squeezer().do_output_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_derive_key() { + eprintln!("BLAKE3DeriveKey/new+hash"); + + let mut out = [0u8; 32]; + BLAKE3DeriveKey::new(b"bc-rust 2026-10-22 mem_usage_benches example context") + .hash_out(&MSG[..32], &mut out); + println!("{:x?}", out); +} + +fn bench_blake3_suspend_resume() { + eprintln!("BLAKE3/suspend+from_suspended"); + + let mut h = BLAKE3::new(); + h.do_update(&MSG[..2500]); + let state = h.suspend(); + let mut h = BLAKE3::from_suspended(state).expect("round-trip of a freshly suspended state"); + h.do_update(&MSG[2500..]); + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_blake3_squeezer_suspend_resume() { + eprintln!("BLAKE3Squeezer/suspend+from_suspended"); + + let mut h = BLAKE3::new(); + h.do_update(&MSG); + let mut s = h.into_squeezer(); + let mut out = [0u8; 100]; + s.do_output_out(&mut out); + let state = s.suspend(); + let mut s = + BLAKE3Squeezer::from_suspended(state).expect("round-trip of a freshly suspended state"); + s.do_output_out(&mut out); + println!("{:x?}", out); +} + +fn main() { + print_struct_sizes() + // bench_do_nothing() + // bench_blake3_hash() + // bench_blake3_streaming() + // bench_blake3_xof() + // bench_blake3_keyed_hash() + // bench_blake3_keyed_hash_xof() + // bench_blake3_derive_key() + // bench_blake3_suspend_resume() + // bench_blake3_squeezer_suspend_resume() +} diff --git a/mem_usage_benches/src/bench_ccm_mem_usage.rs b/mem_usage_benches/src/bench_ccm_mem_usage.rs index 76ba7321a..22a187913 100644 --- a/mem_usage_benches/src/bench_ccm_mem_usage.rs +++ b/mem_usage_benches/src/bench_ccm_mem_usage.rs @@ -28,13 +28,18 @@ #![allow(dead_code)] #![allow(unused_imports)] +use bouncycastle::aes::AES128Key; use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal}; -use bouncycastle::cipher::modes::{CcmDecryptor, CcmEncryptor, CcmPacket}; -use bouncycastle::cipher::{Decrypting, Encrypting}; +use bouncycastle::cipher::modes::{CCM, CCM_Packet, CcmPacketDecryptor, CcmPacketEncryptor}; +use bouncycastle::core::hazmat::{ + 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; @@ -49,16 +54,27 @@ const DATA_LEN: usize = 16384; const AAD_LEN: usize = 64; const MESSAGE_LEN: usize = DATA_LEN; -type Aes128Ccm

= CcmPacket; -type Aes128CcmEncryptor = - CcmEncryptor; -type Aes128CcmDecryptor = - CcmDecryptor; +/// The mode's in-place one-shots over the keyed permutation: the direct path. +type Aes128CcmMode = CCM; +/// 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() } +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 @@ -80,81 +96,76 @@ fn bench_do_nothing() { /// Prints the in-memory size of each CCM value: the persistent cost of holding one open. /// -/// The two things to notice are that `CcmPacket` does not depend on `NONCE_LEN` or `TAG_LEN` -- the nonce -/// lives inside the counter template and the tag is assembled at finalization -- and that the -/// trait adapters are `CcmPacket` plus the `AAD_LEN` buffer and a few words, at any `DATA_LEN`. +/// 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)] fn print_struct_sizes() { use core::mem::size_of; + eprintln!("--- the key state: the keyed permutation alone ---"); + eprintln!("AES128Internal {:>7} B", size_of::()); + eprintln!("AES192Internal {:>7} B", size_of::()); + eprintln!("AES256Internal {:>7} B", size_of::()); + eprintln!( - "--- CcmPacket: permutation + 3 blocks + 5 counters, independent of nonce/tag length ---" - ); - eprintln!("CcmPacket {:>7} B", size_of::>()); - eprintln!( - "CcmPacket {:>7} B", - size_of::>() + "--- the trait pair: `Ccm` state + AAD_LEN + bookkeeping, independent of DATA_LEN ---" ); eprintln!( - "CcmPacket {:>7} B", - size_of::>() + "CcmPacketEncryptor<'_, .., {AAD_LEN}, {DATA_LEN}> {:>7} B", + size_of::() ); eprintln!( - "CcmPacket {:>7} B", - size_of::>() + "CcmPacketDecryptor<'_, .., {AAD_LEN}, {DATA_LEN}> {:>7} B", + size_of::() ); eprintln!( - "CcmPacket {:>7} B", - size_of::>() + "CcmPacketEncryptor<'_, .., 64, 240> {:>7} B", + size_of::>() ); - eprintln!("Decrypting is the same size:"); - eprintln!("CcmPacket {:>7} B", size_of::>()); - eprintln!( - "--- the trait adapters: CcmPacket + AAD_LEN + bookkeeping, independent of DATA_LEN ---" + "CcmPacketEncryptor<'_, .., 7, 4, ..> {:>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<'_, AES256Internal> {:>7} B", + size_of::>( + ) ); - print!("{}", size_of::>()); + print!("{}", size_of::()); } -/// The direct path over the message: `CcmPacket` plus the caller's own buffers, and nothing else. -/// This is the baseline for `bench_streaming_encrypt`, `bench_streaming_decrypt` and -/// `bench_oneshot_encrypt_out_detached`. +/// 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)] -fn bench_direct_encrypt_detached() { - eprintln!("CcmPacket::encrypt_detached_out, {MESSAGE_LEN} B"); +fn bench_in_place_encrypt() { + eprintln!("CCM::encrypt_detached_in_place_nonce, {MESSAGE_LEN} B"); - let k = key::<16>(); + let perm = perm(); let nonce = [0x24u8; NONCE_LEN]; - let plaintext = message(); - let plaintext = core::hint::black_box(&plaintext); - let mut ciphertext = [0u8; MESSAGE_LEN]; - let (_, tag) = - Aes128Ccm::::encrypt_detached_out(&k, &nonce, &[], plaintext, &mut ciphertext) - .unwrap(); + let mut data = message(); + let data = core::hint::black_box(&mut data); + let tag = Aes128CcmMode::encrypt_detached_in_place_nonce(&perm, &nonce, &[], data).unwrap(); 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(); @@ -167,27 +178,30 @@ fn bench_streaming_encrypt() { /// The decrypting side of the same comparison, with the tag inline: the decryptor releases each /// chunk's plaintext as it arrives into the caller's `opened` array and holds back only the tag. /// -/// The sealed message is produced in place with the direct streaming API, so that the bench -/// holds two arrays -- `sealed` and `opened` -- like `bench_direct_encrypt_detached` does, and -/// only the streaming decrypt is under measurement. +/// The sealed message is produced in place with the mode's one-shot, so that the bench holds +/// two arrays -- `sealed` and `opened` -- and only the streaming decrypt is under measurement. #[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)); - let mut ccm = Aes128Ccm::::new(&k, &nonce, &[], MESSAGE_LEN).unwrap(); - ccm.do_encrypt_inplace(&mut sealed[..MESSAGE_LEN]).unwrap(); - let tag = ccm.do_encrypt_final().unwrap(); + let tag = Aes128CcmMode::encrypt_detached_in_place_nonce( + &perm(), + &nonce, + &[], + &mut sealed[..MESSAGE_LEN], + ) + .unwrap(); sealed[MESSAGE_LEN..].copy_from_slice(&tag); 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(); @@ -196,47 +210,27 @@ fn bench_streaming_decrypt() { print!("{}", written + m); } -/// The trait encryptor's **one-shot**, which calls `CcmPacket::encrypt_detached_out` under a generated -/// nonce, so it should measure what `bench_direct_encrypt_detached` measures plus 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(); - print!("{:x?}", &tag); -} - -/// The streaming direct path, which is what a caller in SP 800-38C Sec 3's packet environment -/// with a run-time length should use: the payload length is declared up front and encrypted in -/// place, so peak stack is the `CcmPacket` value plus one array. -#[inline(never)] -fn bench_direct_streaming() { - eprintln!("CcmPacket::do_encrypt_inplace, {MESSAGE_LEN} B in 1 KiB chunks"); - - let k = key::<16>(); - let nonce = [0x24u8; NONCE_LEN]; - let mut data = message(); - let data = core::hint::black_box(&mut data); - let mut ccm = Aes128Ccm::::new(&k, &nonce, &[], data.len()).unwrap(); - for chunk in data.chunks_mut(1024) { - ccm.do_encrypt_inplace(chunk).unwrap(); - } - let tag = ccm.do_encrypt_final().unwrap(); + let (_, _, tag) = k.encrypt_detached_out(&mut rng, &[], plaintext, &mut ciphertext).unwrap(); print!("{:x?}", &tag); } fn main() { print_struct_sizes() // bench_do_nothing() - // bench_direct_encrypt_detached() + // bench_in_place_encrypt() // bench_streaming_encrypt() // bench_streaming_decrypt() // bench_oneshot_encrypt_out_detached() - // bench_direct_streaming() } diff --git a/mem_usage_benches/src/bench_sha3_mem_usage.rs b/mem_usage_benches/src/bench_sha3_mem_usage.rs index 006c0b637..5016113e8 100644 --- a/mem_usage_benches/src/bench_sha3_mem_usage.rs +++ b/mem_usage_benches/src/bench_sha3_mem_usage.rs @@ -25,8 +25,8 @@ #![allow(dead_code)] #![allow(unused_imports)] -use bouncycastle::core::traits::{Hash, Suspendable, XOF, XOFSqueezer}; -use bouncycastle::sha3::kmac::{KMAC128, KMAC256, KMACXOF128, KMACXOF256}; +use bouncycastle::core::traits::{Hash, HashOneShot, Suspendable, XOF, XOFSqueezer}; +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::()); @@ -81,7 +83,7 @@ fn bench_sha3_256_hash() { eprintln!("SHA3-256/hash"); let mut out = [0u8; 32]; - SHA3_256::new().hash_out(&MSG, &mut out); + SHA3_256::hash_out(&MSG, &mut out); println!("{:x?}", out); } @@ -89,7 +91,7 @@ fn bench_sha3_512_hash() { eprintln!("SHA3-512/hash"); let mut out = [0u8; 64]; - SHA3_512::new().hash_out(&MSG, &mut out); + SHA3_512::hash_out(&MSG, &mut out); println!("{:x?}", out); } diff --git a/mem_usage_benches/src/lib.rs b/mem_usage_benches/src/lib.rs index 8c3f55926..b29171e87 100644 --- a/mem_usage_benches/src/lib.rs +++ b/mem_usage_benches/src/lib.rs @@ -1,5 +1,7 @@ mod bench_aes_mem_usage; mod bench_ascon_mem_usage; +mod bench_blake2_mem_usage; +mod bench_blake3_mem_usage; mod bench_ccm_mem_usage; mod bench_mldsa_mem_usage; mod bench_mlkem_mem_usage; diff --git a/src/lib.rs b/src/lib.rs index ae250f332..edcdbf3ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ pub use bouncycastle_aes as aes; pub use bouncycastle_ascon as ascon; pub use bouncycastle_base64 as base64; +pub use bouncycastle_blake2 as blake2; +pub use bouncycastle_blake3 as blake3; pub use bouncycastle_cipher as cipher; pub use bouncycastle_core as core; pub use bouncycastle_factory as factory;