diff --git a/CLAUDE.md b/CLAUDE.md index 6f858b53..6219e32e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Required reading + +This file is a *map*, not a rulebook. It records repo mechanics — commands, layout, where things live, how to work +here. The project's binding standards live in their own documents, which are authoritative and are updated +independently of this file. Read them; do not infer their contents from this file, and do not work from memory of a +previous session's reading of them. + +- **[QUALITY_AND_STYLE.md](QUALITY_AND_STYLE.md) — read before writing or changing code, and before reviewing a + diff.** The authority on architecture, crate and API shape, naming conventions, fallibility, macros, what tests and + benchmarks a crate owes, and which sections crate docs must have. Its own opening line invites an AI to review a PR + against it, so treat it as exactly that checklist. +- **[CONTRIBUTING.md](CONTRIBUTING.md) — read before writing a commit message, opening a PR, or advising on how a + change gets merged.** The authority on coding philosophy, PR hygiene and self-review, the quality bar a submission + must clear to be accepted, how merges actually happen in this project, and the AI policy. That policy places + requirements on the commit messages and PR descriptions of AI-assisted work, which covers anything written here: + never compose a commit message or PR description for this repo without checking it first. It links onward to + [SECURITY.md](SECURITY.md) for anything security-sensitive and [ISSUES_STYLE_GUIDE.md](ISSUES_STYLE_GUIDE.md) for + issue and sub-issue structure. +- **[INTRODUCTION.md](INTRODUCTION.md) — read for design intent**, when a change touches public API shape or you need + the reasoning behind a convention rather than the convention itself. + +Where this file and one of those documents disagree, the document wins — and say so, so the stale line here gets +fixed. + ## Toolchain - Uses Rust **nightly** (pinned in `rust-toolchain.toml`) — `core/src/lib.rs` uses `#![feature(adt_const_params)]`. @@ -69,23 +93,27 @@ crypto// `#![no_std]` is the long-term goal but the `core` crate still has a `Vec`-removal TODO blocking it (see the comment at the top of `crypto/core/src/lib.rs`). Don't add new `Vec` usage where a const-sized array would do. -## Project-specific conventions (from QUALITY_AND_STYLE.md and INTRODUCTION.md) +## Project-specific conventions + +The house rules are deliberately **not** reproduced here — see [Required reading](#required-reading) above. +QUALITY_AND_STYLE.md governs API shape, naming, fallibility, macro use, and the tests, benches and doc sections a +crate owes; CONTRIBUTING.md governs what a submission must satisfy to be accepted. Both cover ground that is easy to +violate without noticing, so read them at the start of a session that will touch code rather than guessing which +conventions apply. -These are non-obvious house rules — follow them when writing or modifying code: +Repo mechanics behind those rules, which the documents don't spell out: -- **No `unsafe`, no runtime third-party deps.** `#![forbid(unsafe_code)]` is required at every crate's `lib.rs`. Avoid adding any non-internal runtime dependency; dev/bench dependencies (`criterion`, `clap`) are fine. -- **Push errors to compile time.** Prefer `&[u8; N]` over `&[u8]` + length-check, prefer the typestate pattern over runtime "initialized" booleans. `Result` should only carry truly-uncontrollable failures (bad user input, RNG init failure). If you're returning `Result` for something the caller can't reasonably hit with valid usage, redesign the signature instead. Run `./dev_scripts/quality_stats.sh` before and after to confirm you haven't increased unwrap/`Err()` counts. -- **No `init()` / `reset()`; `do_final` takes `self` by value.** Constructors set up state; consumption methods consume. This is the deliberate departure from other Bouncy Castle ports. Stateful builder-style patterns are discouraged. -- **One-shot static APIs are the default.** Every primitive should expose a take-data-return-result static method in addition to any streaming API. -- **Sensitive types impl `core::Secret` (and its supertraits).** Anything that holds key material needs this — don't reach for raw byte arrays for secrets. -- **`unwrap()` requires justification.** Either a preceding check that proves success, or an inline comment explaining why it's infallible. -- **Spec correspondence in comments.** Code that mirrors a FIPS/NIST/RFC spec should be commented line-by-line against the spec, citing section/algorithm/step numbers. Any deliberate deviation must be called out and justified. The "would 6-months-from-now me need >10 minutes to re-understand this?" check is the bar. Never write or check these comments from memory — see "Working from specifications" below. -- **Every primitive crate must ship: tests (`src/tests` or `tests/`), criterion benches in `benches/`, and a CLI subcommand.** Stack-memory characteristics matter — algorithms with non-trivial stack usage get a `mem_usage_benches/` harness. -- **CLI commands stream.** The `cli/` binary's design is stdin→stdout with ~1 KB buffers so commands compose in shell pipelines; preserve that when adding subcommands. -- **Crate docs must include sections:** "Usage Examples", "Memory Usage" (stack-usage table), and usually "Security Considerations". +- `./dev_scripts/quality_stats.sh` produces the fallibility metrics both documents ask you to check. Run it before + and after a change and compare, rather than eyeballing the diff. +- **CLI commands stream.** The `cli/` binary is stdin→stdout with ~1 KB buffers so commands compose in shell + pipelines; preserve that when adding subcommands. +- Trait → factory → CLI is the wiring path for a new primitive; see [the workspace architecture](#the-core--core-test-framework--factory-spine) above for the crates involved. ## Working from specifications +QUALITY_AND_STYLE.md is where the requirement for spec-corresponding comments and justified deviations lives. This +section is only about *how* to satisfy it without introducing errors. + **Never cite, paraphrase, or implement a specification from recall.** Model recall of RFC text, FIPS algorithm steps, NIST parameter tables, and section numbering is unreliable — plausible-looking but wrong step numbers and subtly wrong constants are the failure mode. Before writing or reviewing any code, comment, or doc that references a spec, download a fresh copy and read the relevant part of it. Where to get them: @@ -112,6 +140,9 @@ Rules when working from the downloaded copy: ## Notes on testing +What a crate must be tested against — including the mutation-testing expectation, the trait test framework, and the +external vector suites — is specified in QUALITY_AND_STYLE.md and CONTRIBUTING.md. Repo-specific mechanics: + - `cargo mutants` is expected to be run on each crate; surviving mutants must be investigated but not all need to die (e.g. XOR/OR equivalences in crypto code are acceptable). Config lives in `.cargo/mutants.toml` (output dir `custom_mutants_output/`). - Behaviour-critical private functions can use in-file `#[cfg(test)] mod tests` blocks when they can't be exercised from outside the crate. - For traits in `core`, the canonical tests live in `core-test-framework` and are invoked from each implementor's integration tests — don't duplicate them per-implementation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 35e9de42..5f250d37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,10 +32,9 @@ regarded as one. There is a time and a place for "Move fast and break things", but the source code of a crypto library is not one of them. -This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of -the runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to -speed with the code -and avoid introducing bugs due to the code being hard to understand. +This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of the +runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to speed +with the code and avoid introducing bugs due to the code being hard to understand. Some specifics: @@ -80,9 +79,26 @@ For minor updates, you can instead choose to create an issue with short snippets For more information, refer to the Bouncy Castle documentation on [Getting Started with Bouncy Castle](https://doc.primekey.com/bouncycastle/introduction#Introduction-GettingStartedwithBouncyCastle). +### Merging a pull request + +While this project uses GitHub for its issue tracking and Pull Request reviews, GitHub is a read-only mirror of a +private upstream git server. Therefore, all PRs need to be merged manually to the private server by a committer. + +Merge checklist for committers: + +* Merges should be done as a squash merge both to reduce the overall number of commits in the git tree, and so that only + the final version of a feature or bug fix gets merged; the intermediate sequence of working commits could be useful + for a PR reviewer, but not once merged (and it is persisted in the PR branch anyway). +* Committers may make stylistic changes while processing the merge. +* Attributing the contributor: if the PR was submitted by an external contributor, they should be recognized + in [CONTRIBUTORS.md], and, if possible, preserving their commit will give them credit for the contribution in Github. + ### Creating sub-issues -When an issue requires a large amount of time or code changes to complete, it may be convenient for a contributor to break it up into distinct sub-issues, which can each be addressed by a separate pull request. This avoids reviewers managing very large PRs, or submitters needing to frequently resolve merge conflicts in their branches. If you would like to break up issue into sub-issues, see the instructions in [Issues Style Guide](ISSUES_STYLE_GUIDE.md). +When an issue requires a large amount of time or code changes to complete, it may be convenient for a contributor to +break it up into distinct sub-issues, which can each be addressed by a separate pull request. This avoids reviewers +managing very large PRs, or submitters needing to frequently resolve merge conflicts in their branches. If you would +like to break up issue into sub-issues, see the instructions in [Issues Style Guide](ISSUES_STYLE_GUIDE.md). ### Quality Standards @@ -90,12 +106,12 @@ Except where otherwise noted, all crates must have: * benchmarks * unit tests that (mostly) satisfy cargo mutants -* lib.rs needs to compile with: #![forbid(missing_docs)], #![no_std] -* Fallibility: as much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and - not "Programmer didn't read the docs" type things. Things like \[u8]'s of the wrong length, or trying to call an - algorithm with a key of the wrong parameter set should be detected at compile time via the typing system and should - not require a Result / unwrap() mechanism. Please run `./dev_scripts/quality_stats.sh` before and after your change to - see if you have increased the fallibility of the code you changed. +* lib.rs needs to compile with: #![forbid (missing_docs)], #![no_std] +* Fallibility: as much as humanly possible, Result and unwrap () should be used for "Bad input data" type things and not + "Programmer didn't read the docs" type things. Things like \[u8]'s of the wrong length, or trying to call an algorithm + with a key of the wrong parameter set should be detected at compile time via the typing system and should not require + a Result / unwrap () mechanism. Please run `./dev_scripts/quality_stats.sh` before and after your change to see if you + have increased the fallibility of the code you changed. Code submissions that do not meet these standards, or that require significant effort from the maintainers in order to meet these standards, will not be accepted. @@ -160,10 +176,9 @@ rejected for this reason. Specifics: -What counts as "non-trivial"? -A non-trivial portion of a submission has been created with an AI tool when it has generated meaningful code, logic, or -documentation — not merely assisted with trivial tasks like autocompletion of a single line, reformatting, or -spell-checking. If in doubt, declare it as a non-trivial contribution. +What counts as "non-trivial"? A non-trivial portion of a submission has been created with an AI tool when it has +generated meaningful code, logic, or documentation — not merely assisted with trivial tasks like autocompletion of a +single line, reformatting, or spell-checking. If in doubt, declare it as a non-trivial contribution. How to declare it: The commit message or body of the pull request must include a line of the form `Assisted-by: {agent}:{model}`; for diff --git a/cli/src/hkdf_cmd.rs b/cli/src/hkdf_cmd.rs index b49d167b..9c7a00ee 100644 --- a/cli/src/hkdf_cmd.rs +++ b/cli/src/hkdf_cmd.rs @@ -7,6 +7,7 @@ use bouncycastle::core::key_material::{ }; use bouncycastle::hex; use bouncycastle::hkdf; +use bouncycastle::sha2::hkdf::{HKDF_SHA256, HKDF_SHA512}; pub(crate) fn hkdf_cmd( hkdfname: &str, @@ -70,14 +71,14 @@ pub(crate) fn hkdf_cmd( match hkdfname { "HKDF-SHA256" => { - let mut h = hkdf::HKDF_SHA256::new(); + let mut h = HKDF_SHA256::new(); h.do_extract_init(&salt_key).unwrap(); h.do_extract_update_bytes(ikm_bytes.as_slice()).unwrap(); h.do_extract_update_bytes(additional_input_bytes.as_slice()).unwrap(); h.do_extract_final_out(&mut out_key).unwrap(); } "HKDF-SHA512" => { - let mut h = hkdf::HKDF_SHA512::new(); + let mut h = HKDF_SHA512::new(); h.do_extract_init(&salt_key).unwrap(); h.do_extract_update_bytes(ikm_bytes.as_slice()).unwrap(); h.do_extract_update_bytes(additional_input_bytes.as_slice()).unwrap(); diff --git a/cli/src/mac_cmd.rs b/cli/src/mac_cmd.rs index bb7aafc8..8f095efc 100644 --- a/cli/src/mac_cmd.rs +++ b/cli/src/mac_cmd.rs @@ -7,7 +7,7 @@ use bouncycastle::core::key_material::{ }; use bouncycastle::core::traits::MAC; use bouncycastle::hex; -use bouncycastle::hmac::{HMAC_SHA256, HMAC_SHA512}; +use bouncycastle::sha2::hmac::{HMAC_SHA256, HMAC_SHA512}; pub(crate) enum HMACVariant { SHA256, diff --git a/crypto/factory/Cargo.toml b/crypto/factory/Cargo.toml index d3060ebd..e35e31f4 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -5,8 +5,6 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true -bouncycastle-hkdf.workspace = true -bouncycastle-hmac.workspace = true bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true diff --git a/crypto/factory/src/kdf_factory.rs b/crypto/factory/src/kdf_factory.rs index b5da24e7..7c46a570 100644 --- a/crypto/factory/src/kdf_factory.rs +++ b/crypto/factory/src/kdf_factory.rs @@ -13,7 +13,7 @@ //! let seed_key = KeyMaterial256::from_rng(&mut bouncycastle_rng::DefaultRNG::default()).unwrap(); //! let additional_input: &[u8] = b"some additional input"; //! -//! let mut h = bouncycastle_factory::kdf_factory::KDFFactory::new(bouncycastle_hkdf::HKDF_SHA256_NAME).unwrap(); +//! let mut h = bouncycastle_factory::kdf_factory::KDFFactory::new(bouncycastle_sha2::hkdf::HKDF_SHA256_NAME).unwrap(); //! let new_key = h.derive_key(&seed_key, additional_input).unwrap(); //! ``` //! @@ -51,8 +51,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::traits::{KDF, SecurityStrength}; -use bouncycastle_hkdf as hkdf; -use bouncycastle_hkdf::{HKDF_SHA256_NAME, HKDF_SHA512_NAME}; +use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA256_NAME, HKDF_SHA512, HKDF_SHA512_NAME}; use bouncycastle_sha3 as sha3; use bouncycastle_sha3::{ SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME, SHAKE128_NAME, SHAKE256_NAME, @@ -63,10 +62,10 @@ use bouncycastle_sha3::{ pub enum KDFFactory { /// #[allow(non_camel_case_types)] - HKDF_SHA256(hkdf::HKDF_SHA256), + HKDF_SHA256(HKDF_SHA256), /// #[allow(non_camel_case_types)] - HKDF_SHA512(hkdf::HKDF_SHA512), + HKDF_SHA512(HKDF_SHA512), /// SHA3_224(sha3::SHA3_224), /// @@ -83,17 +82,17 @@ pub enum KDFFactory { impl Default for KDFFactory { fn default() -> Self { - Self::HKDF_SHA512(hkdf::HKDF_SHA512::new()) + Self::HKDF_SHA512(HKDF_SHA512::new()) } } impl AlgorithmFactory for KDFFactory { fn default_128_bit() -> Self { - Self::HKDF_SHA256(hkdf::HKDF_SHA256::new()) + Self::HKDF_SHA256(HKDF_SHA256::new()) } fn default_256_bit() -> Self { - Self::HKDF_SHA512(hkdf::HKDF_SHA512::new()) + Self::HKDF_SHA512(HKDF_SHA512::new()) } fn new(alg_name: &str) -> Result { @@ -101,8 +100,8 @@ impl AlgorithmFactory for KDFFactory { DEFAULT => Ok(KDFFactory::default()), DEFAULT_128_BIT => Ok(KDFFactory::default_128_bit()), DEFAULT_256_BIT => Ok(KDFFactory::default_256_bit()), - HKDF_SHA256_NAME => Ok(Self::HKDF_SHA256(hkdf::HKDF_SHA256::new())), - HKDF_SHA512_NAME => Ok(Self::HKDF_SHA512(hkdf::HKDF_SHA512::new())), + HKDF_SHA256_NAME => Ok(Self::HKDF_SHA256(HKDF_SHA256::new())), + HKDF_SHA512_NAME => Ok(Self::HKDF_SHA512(HKDF_SHA512::new())), SHA3_224_NAME => Ok(Self::SHA3_224(sha3::SHA3_224::new())), SHA3_256_NAME => Ok(Self::SHA3_256(sha3::SHA3_256::new())), SHA3_384_NAME => Ok(Self::SHA3_384(sha3::SHA3_384::new())), diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index f9a46768..14e4d0b4 100644 --- a/crypto/factory/src/mac_factory.rs +++ b/crypto/factory/src/mac_factory.rs @@ -20,13 +20,13 @@ //! &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), //! KeyType::MACKey, //! ).unwrap(); -//! let hmac = MACFactory::new(bouncycastle_hmac::HMAC_SHA3_256_NAME, &key).unwrap(); +//! let hmac = MACFactory::new(bouncycastle_sha3::hmac::HMAC_SHA3_256_NAME, &key).unwrap(); //! //! // Generate the MAC value //! let mac_value: Vec = hmac.mac(data); //! //! // Verify the MAC value -//! let hmac = MACFactory::new(bouncycastle_hmac::HMAC_SHA3_256_NAME, &key).unwrap(); +//! let hmac = MACFactory::new(bouncycastle_sha3::hmac::HMAC_SHA3_256_NAME, &key).unwrap(); //! if hmac.verify(data, &mac_value,) { //! println!("MAC verified successfully!") //! } else { @@ -74,13 +74,14 @@ use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; use bouncycastle_core::errors::MACError; use bouncycastle_core::key_material::KeyMaterialTrait; use bouncycastle_core::traits::{MAC, SecurityStrength}; -use bouncycastle_hmac as hmac; -use bouncycastle_hmac::{ - HMAC_SHA3_224_NAME, HMAC_SHA3_256_NAME, HMAC_SHA3_384_NAME, HMAC_SHA3_512_NAME, -}; -use bouncycastle_hmac::{HMAC_SHA224_NAME, HMAC_SHA256_NAME, HMAC_SHA384_NAME, HMAC_SHA512_NAME}; use bouncycastle_sha2 as sha2; +use bouncycastle_sha2::hmac::{ + HMAC_SHA224_NAME, HMAC_SHA256_NAME, HMAC_SHA384_NAME, HMAC_SHA512_NAME, +}; 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, +}; /*** Defaults ***/ /// @@ -98,21 +99,21 @@ pub const DEFAULT_256BIT_MAC_NAME: &str = HMAC_SHA256_NAME; #[non_exhaustive] pub enum MACFactory { /// - HMAC_SHA224(hmac::HMAC), + HMAC_SHA224(sha2::hmac::HMAC_SHA224), /// - HMAC_SHA256(hmac::HMAC), + HMAC_SHA256(sha2::hmac::HMAC_SHA256), /// - HMAC_SHA384(hmac::HMAC), + HMAC_SHA384(sha2::hmac::HMAC_SHA384), /// - HMAC_SHA512(hmac::HMAC), + HMAC_SHA512(sha2::hmac::HMAC_SHA512), /// - HMAC_SHA3_224(hmac::HMAC), + HMAC_SHA3_224(sha3::hmac::HMAC_SHA3_224), /// - HMAC_SHA3_256(hmac::HMAC), + HMAC_SHA3_256(sha3::hmac::HMAC_SHA3_256), /// - HMAC_SHA3_384(hmac::HMAC), + HMAC_SHA3_384(sha3::hmac::HMAC_SHA3_384), /// - HMAC_SHA3_512(hmac::HMAC), + HMAC_SHA3_512(sha3::hmac::HMAC_SHA3_512), } impl MACFactory { @@ -134,14 +135,14 @@ impl MACFactory { DEFAULT => Self::default(key), DEFAULT_128_BIT => Self::default_128_bit(key), DEFAULT_256_BIT => Self::default_256_bit(key), - HMAC_SHA224_NAME => Ok(Self::HMAC_SHA224(hmac::HMAC::::new(key)?)), - HMAC_SHA256_NAME => Ok(Self::HMAC_SHA256(hmac::HMAC::::new(key)?)), - HMAC_SHA384_NAME => Ok(Self::HMAC_SHA384(hmac::HMAC::::new(key)?)), - HMAC_SHA512_NAME => Ok(Self::HMAC_SHA512(hmac::HMAC::::new(key)?)), - HMAC_SHA3_224_NAME => Ok(Self::HMAC_SHA3_224(hmac::HMAC::::new(key)?)), - HMAC_SHA3_256_NAME => Ok(Self::HMAC_SHA3_256(hmac::HMAC::::new(key)?)), - HMAC_SHA3_384_NAME => Ok(Self::HMAC_SHA3_384(hmac::HMAC::::new(key)?)), - HMAC_SHA3_512_NAME => Ok(Self::HMAC_SHA3_512(hmac::HMAC::::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_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)?)), _ => Err(FactoryError::UnsupportedAlgorithm(format!( "The algorithm: \"{}\" is not a known MAC", alg_name diff --git a/crypto/hkdf/Cargo.toml b/crypto/hkdf/Cargo.toml index 12319fae..6df444bd 100644 --- a/crypto/hkdf/Cargo.toml +++ b/crypto/hkdf/Cargo.toml @@ -6,15 +6,16 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-hmac.workspace = true -bouncycastle-sha2.workspace = true bouncycastle-utils.workspace = true +# The concrete HKDF instantiations (HKDF_SHA256, HKDF_SHA512) live in bouncycastle-sha2, which makes +# that crate depend on this one, so this crate must not depend on it. bouncycastle-sha2 is a +# dev-dependency so that the tests, benches and doc examples here can still exercise HKDF over the +# library's own hashes; Cargo permits cycles through dev-dependencies. +# todo -- we're about to change that and move them to their respective crates in the next phase. [dev-dependencies] bouncycastle-core-test-framework.workspace = true criterion.workspace = true bouncycastle-rng.workspace = true bouncycastle-hex.workspace = true - -[[bench]] -name = "hkdf_benches" -harness = false +bouncycastle-sha2.workspace = true diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 70c1014a..8d7dc8ac 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -1,195 +1,108 @@ -//! HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as per RFC5859, as allowed by -//! NIST SP 800-56Cr2. +//! The generic HMAC-based Extract-and-Expand Key Derivation Function (HKDF) construction, as per +//! RFC 5869, as allowed by NIST SP 800-56Cr2. //! -//! # Usage +//! 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. +//! The library provides the following concrete instantiations of HKDF: //! -//! Since HKDF uses `HMAC` as its underlying primitive, most of what is said in the [`HMAC`] crate docs -//! about instantiating HMAC objects applies here as well. Unlike HMAC, an HKDF object is created without -//! an initial key, and will self-initialize the internal HMAC object as part of the [`HKDF::extract`] phase. +//! | Hash family | Instantiations | +//! |-------------|---------------------------------------------------------------| +//! | SHA-2 | `bouncycastle_sha2::hkdf` -- `HKDF_SHA256`, `HKDF_SHA512` | //! +//! # Instantiating HKDF over a hash //! -//! # Examples -//! ## Constructing an object +//! 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. //! -//! HMAC objects can be constructed with any underlying hash function that implements [`Hash`]. -//! Type aliases are provided for the common HKDF-HASH algorithms. +//! What the alias has to supply is [`HKDF`]'s two const parameters: //! -//! The following object instantiations are equivalent: +//! * `HASH_STATE_LEN` -- the hash's own suspended-state length, i.e. the `N` in +//! `H: Suspendable`. +//! * `HKDF_STATE_LEN` -- this HKDF's suspended-state length, which is always `HASH_STATE_LEN + 14`. //! -//! ``` -//! use bouncycastle_hkdf::HKDF_SHA256; -//! -//! let hkdf = HKDF_SHA256::new(); -//! ``` -//! and -//! ``` -//! use bouncycastle_hkdf::HKDF; -//! use bouncycastle_sha2::SHA256; +//! Both are const parameters of the struct rather than being derived from the hash because +//! [`SuspendableKeyed`] takes its length as a const generic, and naming `HASH_STATE_LEN + 14` in that +//! position requires the unstable `generic_const_exprs` feature. Carrying both on the struct is what +//! lets a single blanket [`SuspendableKeyed`] impl serve every hash while still pinning the two +//! lengths together per concrete type. They are given no defaults on purpose: there is no value that +//! is correct for more than one hash, so each instantiation states them. Getting the pair wrong is a +//! compile error at the point of use, not a silent mis-sizing -- the blanket impl only applies when +//! `HASH_STATE_LEN` really is the hash's `Suspendable` length. //! -//! let hkdf = HKDF::::new(); -//! ``` +//! One limitation to be aware of when instantiating over a hash of your own: the extract phase +//! writes its pseudorandom key into a [`MAX_HMAC_OUTPUT_LEN`]-byte buffer, so the hash's output +//! length must not exceed that (64 bytes). See the note on that constant. //! -//! ## Deriving a key via the [`KDF`] trait -//! Being a Key Derivation Function (KDF), the objective of HKDF is to take input key material which is not -//! directly usable for its intended purpose and transform into a suitable output key. -//! Typically, this takes one or both of the following forms: +//! ## Worked example //! -//! * Starting with a seed and mixing in additional input to diversify the output key (ie make it unique). An example of this would be starting with a secret seed and mixing in a public ID or URL to generate keys which are unique per URL. -//! * Starting with a full-entropy seed which is at the correct security level for the application, but which is not long enough. An example could be starting with a 128-bit seed and mixing it with the strings "read" and "write" to produce one AES-128 key for each of the two directions of a communication channel. -//! -//! The simplest usage is via the one-shot functions provided by the [`KDF`] trait. +//! As an example, the `bouncycastle-sha2` crate instantiates HKDF-SHA256 and HKDF-SHA512 this way. The library does not ship +//! HKDF-SHA384, so that makes a good illustration of adding one -- for a hash in this library or for +//! a hash of your own, the shape is identical: //! //! ``` //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::{KDF }; -//! use bouncycastle_hkdf::HKDF_SHA256; -//! -//! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", -//! KeyType::Seed).unwrap(); -//! -//! let hkdf = HKDF_SHA256::new(); -//! let key = hkdf.derive_key(&key, b"extra input").unwrap(); -//! ``` -//! -//! [`KDF::derive_key`] will produce a key the same length as the underlying hash function. -//! Longer output can be requested by instead using [`KDF::derive_key_out`] and providing a larger output buffer, -//! which will be filled. -//! -//! As with other uses of [`KeyMaterialTrait`], the [`KDF::derive_key`] function will track the entropy of the input -//! key material, and will set the entropy of the output key material accordingly. -//! -//! The [`KDF`] trait also provides the [`KDF::derive_key_from_multiple`] and [`KDF::derive_key_from_multiple_out`] -//! functions, which allows for multiple inputs to be mixed into a single output key, and which allows -//! for some advanced control of the underlying HKDF primitive. -//! -//! -//! ## HKDF Extract-and-Expand -//! -//! The HKDF algorithm defined in RFC5896 and SP 800-56Cr2 is a two-step KDF, broken into an Extract step -//! which essentially absorbs entropy from the input key material, -//! and an Expand step which produces the output key material of any requested size. -//! This interface is essentially a pre-cursor to the [`XOF`] API which was introduced with SHA3; the main -//! difference being that HKDF-Expand needs to be told up-front how much output to produce, whereas XOFs -//! can stream output as needed. -//! -//! Naturally, the full two-step HKDF-Extract and HKDF-Expand interface is provided by the [`HKDF`] struct, -//! and exposes additional HKDF-specific parameters beyond what is exposed by the functions of the [`KDF`] trait. -//! -//! The usage pattern here is flexible, but generally follows the pattern of first calling [`HKDF::extract`] -//! with a `salt` and an input key material `ikm`, which produces a pseudorandom key `prk`. -//! The `prk` will have a [`KeyType`] and [`SecurityStrength`] that results from combining the two provided input keys, -//! The `prk` may be! used directly as a full-entropy cryptographic key. +//! use bouncycastle_core::traits::{KDF, SuspendableKeyed}; +//! use bouncycastle_hkdf::HKDF; +//! use bouncycastle_sha2::{SHA384, SUSPENDED_SHA512_STATE_LEN}; //! -//! Since the extract step may be called with any number of input keys, a streaming interface is provided -//! whereby streaming mode in initialized with a call to [`HKDF::do_extract_init`], and then -//! repeated calls to [`HKDF::do_extract_update_key`] and [`HKDF::do_extract_update_bytes`] may be made. -//! Entropy from the inputs keys provided via [`HKDF::do_extract_update_key`] are credited towards the output key, -//! while bytes provided via [`HKDF::do_extract_update_bytes`] are not. -//! One restriction here is that once you start provided un-credited bytes via [`HKDF::do_extract_update_bytes`], -//! no more calls to [`HKDF::do_extract_update_key`] may be made. -//! The streaming API is completed with a call to either [`HKDF::do_extract_final`] or [`HKDF::do_extract_final_out`]. +//! // SHA-384 is a member of the SHA-512 family, so its suspended state is the SHA-512 one. +//! const SUSPENDED_HKDF_SHA384_STATE_LEN: usize = SUSPENDED_SHA512_STATE_LEN + 14; //! -//! The second stage, [`HKDF::expand_out`] stretches the `prk` into a longer output key, still of the same [`KeyType`] -//! and [`SecurityStrength`]. +//! #[allow(non_camel_case_types)] +//! pub type HKDF_SHA384 = +//! HKDF; //! -//! A typical flow looks like this: +//! pub const HKDF_SHA384_NAME: &str = "HKDF-SHA384"; //! -//! ``` -//! use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::KDF; -//! use bouncycastle_hkdf::{HKDF, HKDF_SHA256}; -//! use bouncycastle_sha2::{SHA256}; -//! -//! // setup variables -//! let salt = KeyMaterial256::from_bytes_as_type( +//! // That is all it takes: the KDF trait and the extract/expand API are now available. +//! let ikm = 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 ikm = KeyMaterial256::from_bytes_as_type( -//! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00", -//! KeyType::MACKey).unwrap(); -//! -//! let info = b"some extra context info"; -//! -//! // Use the streaming API to derive an output key of length 200 bytes. -//! let mut okm = KeyMaterial::<200>::new(); -//! let mut hkdf = HKDF::::default(); -//! hkdf.do_extract_init(&salt).unwrap(); -//! hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap(); -//! let prk = hkdf.do_extract_final().unwrap(); -//! HKDF_SHA256::expand_out(&prk, info, 200, &mut okm).unwrap(); -//! ``` -//! -//! Various convenience wrapper functions are provided which can reduce the amount of boilerplate code -//! for common cases. -//! For example, the above code can be condensed to: -//! -//! ``` -//! use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType}; -//! use bouncycastle_hkdf::{HKDF_SHA256}; +//! KeyType::Seed).unwrap(); +//! let okm = HKDF_SHA384::new().derive_key(&ikm, b"extra input").unwrap(); //! -//! // setup variables +//! // ...and so is suspend/resume, because SHA-384 implements Suspendable and the two const +//! // parameters above agree. //! let salt = 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 ikm = KeyMaterial256::from_bytes_as_type( //! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00", //! KeyType::MACKey).unwrap(); -//! -//! let info = b"some extra context info"; -//! -//! // Use the one-shot API to derive an output key of length 200 bytes. -//! let mut okm = KeyMaterial::<200>::new(); -//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap(); -//! ``` -//! -//! # Suspending and resuming execution -//! -//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying -//! material can be provided either via [`HKDF::do_extract_update_key`] -- which will -//! credit the entropy of the provided [`KeyMaterial`] -- or as raw uncredited bytes via -//! [`HKDF::do_extract_update_bytes`]. -//! -//! As such, The *HKDF-Extract* phase can be suspended to a cache and resumed later via the -//! [`SuspendableKeyed`] trait. -//! -//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again -//! during finalization. Suspension and resumption are supported via the [`SuspendableKeyed`] trait -//! which requires the caller to store the salt securely and provide it again during resumption. -//! Note that providing a different salt during resumption cannot be detected by the library and -//! would silently produce a different PRK. -//! -//! ```rust -//! use bouncycastle_hkdf::HKDF_SHA256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::SuspendableKeyed; -//! -//! let salt = 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 ikm_part1 = b"input keying material part 1"; -//! let ikm_part2 = b" ...and part 2"; -//! -//! let mut hkdf = HKDF_SHA256::new(); +//! let mut hkdf = HKDF_SHA384::new(); //! hkdf.do_extract_init(&salt).unwrap(); -//! hkdf.do_extract_update_bytes(ikm_part1).unwrap(); -//! -//! // suspend the in-progress extract (the salt is NOT included in the serialized state) -//! let serialized_state = hkdf.suspend(); -//! -//! // ... -//! // do other things in the meantime -//! // ... +//! hkdf.do_extract_update_bytes(b"part 1").unwrap(); +//! let suspended = hkdf.suspend(); +//! assert_eq!(suspended.len(), SUSPENDED_HKDF_SHA384_STATE_LEN); //! -//! // ... later, possibly on another host: resume from the serialized state by re-supplying -//! // the same salt (make sure you store it securely!). -//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap(); -//! hkdf.do_extract_update_bytes(ikm_part2).unwrap(); -//! let _prk = hkdf.do_extract_final().unwrap(); +//! let mut resumed = HKDF_SHA384::from_suspended(suspended, &salt).unwrap(); +//! resumed.do_extract_update_bytes(b"part 2").unwrap(); +//! let _prk = resumed.do_extract_final().unwrap(); //! ``` - +//! +//! # Security Considerations +//! +//! These apply to every instantiation; `bouncycastle_sha2::hkdf` repeats the ones that matter most in +//! day-to-day use. +//! +//! * HKDF is keyed by its `salt`, which keys the extract-phase HMAC. The salt is deliberately +//! excluded from the suspended state and must be re-supplied on resume; resuming with a different +//! salt cannot be detected and silently produces a different PRK. +//! * Entropy is credited only for input supplied via [`HKDF::do_extract_update_key`]. Bytes supplied +//! via [`HKDF::do_extract_update_bytes`] are treated as uncredited context, so a PRK derived only +//! from raw bytes will not be tagged as full-entropy key material even if those bytes were random. +//! [`HKDF::do_extract_update_key`] is rejected after the first call to +//! [`HKDF::do_extract_update_bytes`], so that the input matches the ordering of the key-extraction +//! method in NIST SP 800-133r2 Section 6.3, `K = T(HMAC-hash(salt, K1 || ... || Kn || D1 || ... +//! || Dm), kLen)`, in which the component keys precede the other data. Note (h) of that method +//! permits other orderings, so this is a deliberate restriction of this API rather than a +//! requirement of the specification. +//! * HKDF stretches key material but does not create entropy. The output key inherits the +//! [`SecurityStrength`] of its inputs: 200 bytes expanded from a 128-bit seed is 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, and the extract phase accepts one, but a salt that the caller believes to be +//! random and is not provides none of the benefit the recommendation is aimed at. #![forbid(unsafe_code)] #![forbid(missing_docs)] @@ -200,10 +113,9 @@ use bouncycastle_core::key_material::{ }; use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{ - Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, + Hash, HashAlgParams, KDF, MAC, SecurityStrength, Suspendable, SuspendableKeyed, }; -use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN}; -use bouncycastle_sha2::{SHA256, SHA512}; +use bouncycastle_hmac::HMAC; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; // Imports needed only for docs @@ -223,25 +135,34 @@ use bouncycastle_core::traits::XOF; /// and declare `prk: &mut KeyMaterial` instead of this hack. pub const MAX_HMAC_OUTPUT_LEN: usize = 64; -/*** String constants ***/ - +/*** Types ***/ +/// The generic HKDF construction (RFC 5869). /// -pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256"; +/// Can be instantiated with hash functions other than the ones provided by this library (even custom +/// ones). The concrete instantiations over the library's own hashes, along with their name constants, +/// live in the hash crates -- see `bouncycastle_sha2::hkdf::HKDF_SHA256` and +/// `bouncycastle_sha2::hkdf::HKDF_SHA512`. /// -pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512"; - -/*** Types ***/ -/// Public type for HKDF using SHA256. -#[allow(non_camel_case_types)] -pub type HKDF_SHA256 = HKDF; -/// Public type for HKDF using SHA512. -#[allow(non_camel_case_types)] -pub type HKDF_SHA512 = HKDF; - -/// Internal struct for HKDF. -/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones). +/// # Const parameters +/// +/// `HASH_STATE_LEN` is the suspended-state length of `H` (as in `H: Suspendable`) and +/// `HKDF_STATE_LEN` is this HKDF's own suspended-state length, which is always `HASH_STATE_LEN + 14` +/// (see the [`SuspendableKeyed`] impl below for the layout that accounts for those 14 bytes). +/// +/// Both are const parameters of the struct rather than being derived from `H` because `SuspendableKeyed` +/// takes its length as a const generic, and naming `HASH_STATE_LEN + 14` in that position requires +/// the `generic_const_exprs` feature. Carrying both on the struct is what lets a single blanket +/// `SuspendableKeyed` impl serve every hash while still pinning the two lengths together per concrete +/// type. They are deliberately given no defaults: there is no value that is correct for more than one +/// hash, so each instantiation must state them. +/// 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 { +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. // None must correspond to a state of Uninitialized. @@ -262,7 +183,11 @@ enum HkdfStates { Initialized = 1, /// [`HKDF::do_extract_update_key`] has been called, after which no more credited IKMs can be given. - /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs. + /// This keeps the input in the order used by the key-extraction method of NIST SP 800-133r2 + /// Section 6.3, `K = T(HMAC-hash(salt, K1 || ... || Kn || D1 || ... || Dm), kLen)`, in which the + /// component keys precede the other data. Note (h) of that method explicitly permits alternative + /// orderings ("including interleaving the keys and data"), so this ordering is conformant but is + /// a restriction this API chooses, not one the specification imposes. TakingAdditionalInfo = 2, } @@ -319,37 +244,17 @@ impl HkdfEntropyTracker { } } -// Because this struct is not public, the tests have to go here. -#[test] -fn test_entropy_tracker() { - let mut entropy = HkdfEntropyTracker::::new(); - - assert_eq!(entropy.get_entropy(), 0); - assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); - - let key = KeyMaterial512::from_bytes_as_type( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - KeyType::CryptographicRandom, - ) - .unwrap(); - entropy.credit_entropy(&key); - assert_eq!(entropy.get_entropy(), 16); - assert_eq!(entropy.is_fully_seeded(), false); - assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); - - entropy.credit_entropy(&key); - assert_eq!(entropy.get_entropy(), 32); - assert_eq!(entropy.is_fully_seeded(), true); - assert_eq!(entropy.get_output_key_type(), KeyType::CryptographicRandom); -} - -impl Default for HKDF { +impl + Default for HKDF +{ fn default() -> Self { Self::new() } } -impl HKDF { +impl + HKDF +{ /// Get a new, uninstantiated HKDF object. pub fn new() -> Self { Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized } @@ -705,7 +610,9 @@ impl HKDF { /// [`KDF::derive_key_from_multiple_out`], or by using the [`HKDF`] impl directly. /// /// Entropy tracking: this implementation will map entropy from the input keys to the output key. -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. fn derive_key( @@ -727,7 +634,7 @@ impl KDF for HKDF { additional_input: &[u8], output_key: &mut impl KeyMaterialTrait, ) -> Result { - let bytes_written = HKDF::::extract_and_expand_out( + let bytes_written = Self::extract_and_expand_out( &KeyMaterial::<0>::new(), key, additional_input, @@ -765,7 +672,7 @@ impl KDF for HKDF { additional_input: &[u8], output_key: &mut impl KeyMaterialTrait, ) -> Result { - let mut hkdf = HKDF::::new(); + let mut hkdf = Self::new(); let mut entropy = HkdfEntropyTracker::::new(); if keys.len() >= 1 { @@ -784,7 +691,7 @@ impl KDF for HKDF { let mut prk = KeyMaterial::::new(); _ = hkdf.do_extract_final_out(&mut prk)?; let bytes_written = - HKDF::::expand_out(&prk, additional_input, output_key.capacity(), output_key)?; + Self::expand_out(&prk, additional_input, output_key.capacity(), output_key)?; key_material::do_hazardous_operations(output_key, |output_key| { output_key.set_key_type(entropy.get_output_key_type())?; @@ -805,15 +712,10 @@ impl KDF for HKDF { } } -/// Length in bytes of the serialized state of [`HKDF_SHA256`]. -pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14; -/// Length in bytes of the serialized state of [`HKDF_SHA512`]. -pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14; - /// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements -/// [`SuspendableKeyed`] (not [`SerializableState`]). An in-progress +/// [`SuspendableKeyed`] (not [`Suspendable`]). An in-progress /// extract operation can be suspended and resumed, but the salt is NOT written into the serialized -/// state and must be re-supplied to [`SuspendableKeyed::from_serialized_state`]. +/// state and must be re-supplied to [`SuspendableKeyed::from_suspended`]. /// /// Only the extract phase carries resumable state (expand is a one-shot static operation). As with /// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK. @@ -822,108 +724,125 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /// parsing anything else. This matters because the inner HMAC blob (which carries its own header) is /// absent before extract is initialized -- without HKDF's own header, a pre-init state would have no /// version tag at all. Using `B` = the inner HMAC blob length: +/// +/// ```text /// [0 .. 3) HKDF library version header (checked on resume) /// [3] inner-HMAC present flag (0 = extract not yet initialized) /// [4 .. 4 + B) the inner HMAC's SuspendableKeyed blob (salt excluded); zeroed when absent /// [4 + B] state-machine tag (see `HkdfStates`) /// [5 + B .. 13 + B) entropy counter (usize serialized as u64, little-endian) /// [13 + B] accumulated security strength (1-byte tag) +/// ``` +/// /// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping -/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`. -macro_rules! impl_suspendable_keyed_state_for_hkdf { - // $hash: the concrete hash; $serialized_hmac_len: the inner HMAC's serialized-state length for that - // hash; $serialized_hkdf_len: the full HKDF serialized-state length (= 3 + 11 + $serialized_hmac_len). - ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => { - impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$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(self) -> [u8; $serialized_hkdf_len] { - debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 14); - let mut state = [0u8; $serialized_hkdf_len]; - - // HKDF's own library version header comes first: the inner HMAC blob is absent before - // extract is initialized, so we can't rely on its header being present. - add_lib_ver(&mut state); - - // 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 + $serialized_hmac_len].copy_from_slice(&hmac.suspend()); - } - // else None: - // the presence flag = 0 - // the content = [u8; 0] - // which is how it already is, so nothing to do. - - state[4 + $serialized_hmac_len] = self.state as u8; - state[5 + $serialized_hmac_len..13 + $serialized_hmac_len] - .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); - state[13 + $serialized_hmac_len] = self.entropy.security_strength as u8; - - state - } +/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`, +/// which is the relationship `HKDF_STATE_LEN == HASH_STATE_LEN + 14` asserted below. +impl SuspendableKeyed + for HKDF +where + H: Hash + HashAlgParams + Default + 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 + // bytes at from_serialized_state, so dynamic dispatch here is negligible. + type Key = dyn KeyMaterialTrait; + + fn suspend(self) -> [u8; HKDF_STATE_LEN] { + debug_assert_eq!(HKDF_STATE_LEN, HASH_STATE_LEN + 14); + let mut state = [0u8; HKDF_STATE_LEN]; + + // HKDF's own library version header comes first: the inner HMAC blob is absent before + // extract is initialized, so we can't rely on its header being present. + add_lib_ver(&mut state); + + // 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()); + } + // else None: + // the presence flag = 0 + // the content = [u8; 0] + // which is how it already is, so nothing to do. - fn from_suspended( - state: [u8; $serialized_hkdf_len], - salt: &Self::Key, - ) -> Result { - // Check HKDF's own version header before parsing anything else. - check_lib_ver(&state, None)?; - - // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt. - let hmac = match state[3] { - 0 => None, - // infallible: the sub-slice is exactly $serialized_hmac_len bytes by const construction. - 1 => Some(HMAC::<$hash>::from_suspended( - state[4..4 + $serialized_hmac_len].try_into().unwrap(), - salt, - )?), - _ => return Err(SuspendableError::InvalidData), - }; - - let hkdf_state = HkdfStates::try_from(state[4 + $serialized_hmac_len])?; - - // check that the hkdf_state aligns with the presence of an hmac - if - // an hmac object should not be present in the init state. - (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) || - // any other state must have an hmac object. - (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized) - { - return Err(SuspendableError::InvalidData); - } - - // infallible: the sub-slice is exactly 8 bytes by const construction. - let entropy = u64::from_le_bytes( - state[5 + $serialized_hmac_len..13 + $serialized_hmac_len].try_into().unwrap(), - ) as usize; - let security_strength = - SecurityStrength::try_from(state[13 + $serialized_hmac_len])?; - - Ok(HKDF { - hmac, - entropy: HkdfEntropyTracker { - _phantomhash: PhantomData, - entropy, - security_strength, - }, - state: hkdf_state, - }) - } + state[4 + HASH_STATE_LEN] = self.state as u8; + state[5 + HASH_STATE_LEN..13 + HASH_STATE_LEN] + .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); + state[13 + HASH_STATE_LEN] = self.entropy.security_strength as u8; + + state + } + + fn from_suspended( + state: [u8; HKDF_STATE_LEN], + salt: &Self::Key, + ) -> Result { + debug_assert_eq!(HKDF_STATE_LEN, HASH_STATE_LEN + 14); + + // Check HKDF's own version header before parsing anything else. + check_lib_ver(&state, None)?; + + // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt. + 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, + )?), + _ => return Err(SuspendableError::InvalidData), + }; + + let hkdf_state = HkdfStates::try_from(state[4 + HASH_STATE_LEN])?; + + // Check that the hkdf_state aligns with the presence of an hmac: an hmac object should not + // be present in the init state, and any other state must have one. + if (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) + || (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized) + { + 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; + let security_strength = SecurityStrength::try_from(state[13 + HASH_STATE_LEN])?; + + Ok(HKDF { + hmac, + entropy: HkdfEntropyTracker { _phantomhash: PhantomData, entropy, security_strength }, + state: hkdf_state, + }) + } } -impl_suspendable_keyed_state_for_hkdf!( - SHA256, - SUSPENDED_HMAC_SHA256_STATE_LEN, - SUSPENDED_HKDF_SHA256_STATE_LEN -); -impl_suspendable_keyed_state_for_hkdf!( - SHA512, - SUSPENDED_HMAC_SHA512_STATE_LEN, - SUSPENDED_HKDF_SHA512_STATE_LEN -); +// Because this struct is not public, the tests have to go here. +#[cfg(test)] +mod tests { + use super::*; + use bouncycastle_sha2::SHA256; + + #[test] + fn test_entropy_tracker() { + let mut entropy = HkdfEntropyTracker::::new(); + + assert_eq!(entropy.get_entropy(), 0); + assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); + + let key = KeyMaterial512::from_bytes_as_type( + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + KeyType::CryptographicRandom, + ) + .unwrap(); + entropy.credit_entropy(&key); + assert_eq!(entropy.get_entropy(), 16); + assert_eq!(entropy.is_fully_seeded(), false); + assert_eq!(entropy.get_output_key_type(), KeyType::Unknown); + + entropy.credit_entropy(&key); + assert_eq!(entropy.get_entropy(), 32); + assert_eq!(entropy.is_fully_seeded(), true); + assert_eq!(entropy.get_output_key_type(), KeyType::CryptographicRandom); + } +} diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 84b99105..896c0a08 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -10,8 +10,11 @@ mod hkdf_tests { use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_hex as hex; - use bouncycastle_hkdf::{HKDF, HKDF_SHA256, HKDF_SHA512}; - use bouncycastle_sha2::{SHA256, SHA512}; + use bouncycastle_hkdf::HKDF; + use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA512}; + use bouncycastle_sha2::{ + SHA256, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, + }; use bouncycastle_utils::ct; #[test] @@ -24,7 +27,7 @@ mod hkdf_tests { _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 64, &mut okm).unwrap(); // Test that streaming API do_extract gets the same result - let mut hkdf = HKDF::::default(); + let mut hkdf = HKDF_SHA256::default(); hkdf.do_extract_init(&salt).unwrap(); hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap(); let prk = hkdf.do_extract_final().unwrap(); @@ -394,28 +397,28 @@ mod hkdf_tests { // can't test with a low entropy salt because the salt has to be full entropy or zero. // but can test with a zeroized key - let mut hkdf = HKDF::::new(); + let mut hkdf = HKDF_SHA256::new(); assert_eq!(hkdf.get_entropy(), 0); hkdf.do_extract_init(&KeyMaterial0::new()).unwrap(); assert_eq!(hkdf.get_entropy(), 0); assert_eq!(hkdf.is_fully_seeded(), false); // test do_extract_init with a full entropy salt - let mut hkdf = HKDF::::new(); + let mut hkdf = HKDF_SHA256::new(); assert_eq!(hkdf.get_entropy(), 0); hkdf.do_extract_init(&salt16).unwrap(); assert_eq!(hkdf.get_entropy(), 16); assert_eq!(hkdf.is_fully_seeded(), false); // with enough entropy in the salt. - let mut hkdf = HKDF::::new(); + let mut hkdf = HKDF_SHA256::new(); assert_eq!(hkdf.get_entropy(), 0); hkdf.do_extract_init(&salt64).unwrap(); assert_eq!(hkdf.get_entropy(), 64); assert_eq!(hkdf.is_fully_seeded(), true); // building up to full entropy - let mut hkdf = HKDF::::new(); + let mut hkdf = HKDF_SHA256::new(); assert_eq!(hkdf.get_entropy(), 0); hkdf.do_extract_init(&salt16).unwrap(); assert_eq!(hkdf.get_entropy(), 16); @@ -696,7 +699,7 @@ mod hkdf_tests { // do it manually just to check that we have the test vector right. let mut output_key = KeyMaterial::<128>::new(); - let bytes_written = HKDF::::extract_and_expand_out( + let bytes_written = HKDF_SHA256::extract_and_expand_out( &salt, &ikm, additional_input.as_slice(), @@ -708,7 +711,7 @@ mod hkdf_tests { assert_eq!(output_key.ref_to_bytes(), expected_key.ref_to_bytes()); // One-key derive_key -- since HKDF.derive_key() doesn't accept a salt but sets it to zero, we can only test vectors with a zero salt. - let hkdf = HKDF::::default(); + let hkdf = HKDF_SHA256::default(); let output_key = hkdf.derive_key(&ikm, &additional_input).unwrap(); // kdf.derive_key is a one-step that doesn't expand, so need to truncate the expected key to match. let mut expected_key_truncated = expected_key.clone(); @@ -716,10 +719,10 @@ mod hkdf_tests { assert_eq!(output_key.ref_to_bytes(), expected_key_truncated.ref_to_bytes()); testframework - .test_kdf_single_key::>(&ikm, &additional_input, &expected_key_truncated); + .test_kdf_single_key::(&ikm, &additional_input, &expected_key_truncated); let keys = [&salt, &ikm]; - testframework.test_kdf_multiple_key::>( + testframework.test_kdf_multiple_key::( &keys, additional_input.as_slice(), &mut expected_key, @@ -729,7 +732,9 @@ mod hkdf_tests { fn serializable_keyed_state() { use bouncycastle_core::traits::{Hash, SuspendableKeyed}; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; - use bouncycastle_hkdf::{SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN}; + use bouncycastle_sha2::hkdf::{ + SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, + }; // HKDF is keyed by its salt: the salt is NOT serialized and is re-supplied on resume. let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); @@ -739,17 +744,21 @@ 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, part1: &[u8], part2: &[u8]) - where + fn round_trip( + salt: &KeyMaterial128, + part1: &[u8], + part2: &[u8], + ) where H: Hash + HashAlgParams + Default, - HKDF: Clone + SuspendableKeyed, + HKDF: Clone + SuspendableKeyed, { - let hkdf = HKDF::::new(); + let hkdf = HKDF::::new(); // it can be serialized pre-init, which is kinda a no-op, but at least it works. let serialized_state = hkdf.suspend(); assert_eq!(serialized_state.len(), LEN); - let mut hkdf = HKDF::::from_suspended(serialized_state, salt).unwrap(); + let mut hkdf = + HKDF::::from_suspended(serialized_state, salt).unwrap(); hkdf.do_extract_init(salt).unwrap(); hkdf.do_extract_update_bytes(part1).unwrap(); @@ -765,15 +774,20 @@ mod hkdf_tests { let prk = hkdf.do_extract_final().unwrap(); // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs - let mut resumed = HKDF::::from_suspended(serialized_state, salt).unwrap(); + let mut resumed = + HKDF::::from_suspended(serialized_state, salt).unwrap(); resumed.do_extract_update_bytes(part2).unwrap(); let prk_resumed = resumed.do_extract_final().unwrap(); assert_eq!(prk.ref_to_bytes(), prk_resumed.ref_to_bytes()); } - round_trip::(&salt, part1, part2); - round_trip::(&salt, part1, part2); + round_trip::( + &salt, part1, part2, + ); + round_trip::( + &salt, part1, part2, + ); // Test the guard for invalid states // testing just on HKDF_SHA256 diff --git a/crypto/hmac/Cargo.toml b/crypto/hmac/Cargo.toml index ebb14077..44d1eed1 100644 --- a/crypto/hmac/Cargo.toml +++ b/crypto/hmac/Cargo.toml @@ -5,16 +5,16 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true -bouncycastle-rng.workspace = true -bouncycastle-sha2.workspace = true -bouncycastle-sha3.workspace = true bouncycastle-utils.workspace = true +# bouncycastle-sha2, -sha3 and -rng are dev-dependencies so that the tests, benches and doc examples +# here can still exercise HMAC over the library's own hashes; Cargo permits cycles through +# dev-dependencies. +# todo -- we're about to change that and move them to their respective crates in the next phase. [dev-dependencies] bouncycastle-core-test-framework.workspace = true criterion.workspace = true bouncycastle-hex.workspace = true - -[[bench]] -name = "hmac_benches" -harness = false +bouncycastle-rng.workspace = true +bouncycastle-sha2.workspace = true +bouncycastle-sha3.workspace = true diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 26d16999..8de8bc23 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -1,333 +1,154 @@ -//! This crate contains an implementation of the Hash-Based Message Authentication Code (HMAC) -//! as specified in RFC2104, taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8 -//! and NIST SP 800-107-r1. -//! -//! # Usage -//! -//! The HMAC object (and the [`MAC`] trait in general) is designed in three phases: -//! -//! * 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. -//! -//! -//! # Examples -//! -//! Instantiation of an HMAC object is straightforward: -//! -//! ``` -//! use bouncycastle_hmac::HMAC_SHA256; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_core::key_material::{KeyMaterial256}; -//! -//! let key: KeyMaterial256 = HMAC_SHA256::keygen().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: -//! ``` -//! use bouncycastle_hmac::HMAC_SHA256; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! -//! let key = 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( -//! "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. -//! ``` -//! use bouncycastle_hmac::HMAC_SHA256; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! -//! let key: KeyMaterial256 = HMAC_SHA256::keygen().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); -//! ``` -//! -//! 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: -//! -//! ``` -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_hmac::HMAC_SHA256; -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! -//! let key: KeyMaterial256 = HMAC_SHA256::keygen().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"); -//! hmac.do_update(b"Hello,"); -//! hmac.do_update(b" world!"); -//! let output: Vec = hmac.do_final(); -//! ``` -//! -//! ## Verifying a MAC -//! MAC functionality is accessed via the [`MAC`] trait which 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 simplest usage is via the one-shot functions. -//! ``` -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! -//! // 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( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", -//! KeyType::MACKey).unwrap(); -//! -//! let data: &[u8] = b"Hello, world!"; -//! -//! // .verify() returns a bool: true if the MAC is valid, false otherwise. -//! if bouncycastle_hmac::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" -//! ) -//! { -//! println!("MAC is valid!"); -//! } else { -//! println!("MAC is invalid!"); +//! 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: +//! +//! | Hash family | Instantiations | +//! |-------------|------------------------------------------------------------------------------| +//! | SHA-2 | `bouncycastle_sha2::hmac` -- `HMAC_SHA224` .. `HMAC_SHA512_256` | +//! | SHA-3 | `bouncycastle_sha3::hmac` -- `HMAC_SHA3_224` .. `HMAC_SHA3_512` | +//! +//! 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. +//! +//! # Instantiating HMAC over a Hash +//! +//! HMAC works with any hash: [`HMAC`](HMAC) needs only [`Hash`]. What HMAC cannot +//! derive on its own is the *metadata* of the resulting construction -- the name "HMAC-SHA256" is not +//! mechanically obtainable from "SHA256", and RFC 4231 assigns each hash/HMAC combination its own OID +//! rather than deriving it from the hash's OID. Supplying that metadata is what makes an HMAC a +//! first-class algorithm in this library rather than an anonymous `HMAC`. +//! +//! There are four steps, of which only the second is mandatory: +//! +//! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing +//! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. +//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name, claimed security +//! 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. +//! 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. +//! +//! ## 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 }>; +//! +//! impl HMACParams for SHA256 { +//! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; +//! const HMAC_ALG_NAME: &'static str = "HMAC-SHA256"; +//! 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]; +//! const HMAC_OID_DER: &'static [u8] = +//! &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; //! } -//! ``` -//! -//! Similarly, a streaming version is available, which is identical to the streaming interface for -//! computing a mac value, but calls [`MAC::do_verify_final`] instead of [`MAC::do_final`]. -//! -//! ``` -//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; -//! use bouncycastle_core::traits::MAC; -//! use bouncycastle_hmac::HMAC_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( -//! 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(); -//! 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" -//! ) -//! { -//! println!("MAC is valid!"); -//! } else { -//! println!("MAC is invalid!"); -//! } +//! pub const SUSPENDED_HMAC_SHA256_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; //! ``` //! -//! # Suspending and resuming execution -//! -//! When MAC'ing a large message, it can be advantageous to be able to suspend the operation -//! to a cache and resume it later; for example if waiting for the message to stream over a slow network -//! connection. For this reason, all HMAC algorithms impl [`SuspendableKeyed`]. -//! -//! Note that since HMAC is a keyed -//! algorithm and we do not want to serialize the private key into the state, the trait structure forces you to -//! re-provide the same key when you resume the operation. Securely storing this key in the interim -//! is the responsibility of the caller. Note also that if you resume the HMAC with the wrong key, -//! `from_serialized_state` has no way to detect this, so the end result will be a broken MAC value -//! computed with different keys in the inner and outer pad. So make sure you resume with the same key! -//! -//!```rust -//! use bouncycastle_hmac::HMAC_SHA256; -//! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; -//! use bouncycastle_core::key_material::KeyType; -//! -//! let msg_part1 = b"The quick brown fox"; -//! let msg_part2 = b" jumped over the lazy dog"; -//! -//! let key = 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(); -//! hmac.do_update(msg_part1); -//! -//! // suspend the in-progress mac (the key is NOT included in the serialized state) -//! let serialized_state = hmac.suspend(); -//! -//! // ... -//! // do other things in the meantime -//! // ... -//! -//! // ... later, possibly on another host: resume from the serialized state by re-supplying -//! // the same salt (make sure you store it 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(); -//! ``` +//! [`HMACParams`] is deliberately **not** sealed, so the same recipe works for a hash function +//! defined in any other crate. Simply follow the recipe above! +//! +//! # Security Considerations +//! +//! 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`]. +//! * 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. #![forbid(unsafe_code)] #![forbid(missing_docs)] use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, SuspendableError}; -use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, MAC, RNG, SecurityStrength, Suspendable, SuspendableKeyed, -}; -use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; -use bouncycastle_sha2::{ - SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, + Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, RNG, SecurityStrength, Suspendable, + SuspendableKeyed, }; -use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::{ct, secret::Secret}; use core::fmt::{Debug, Display, Formatter}; -/*** String constants ***/ -/// -pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224"; -/// -pub const HMAC_SHA256_NAME: &str = "HMAC-SHA256"; -/// -pub const HMAC_SHA384_NAME: &str = "HMAC-SHA384"; -/// -pub const HMAC_SHA512_NAME: &str = "HMAC-SHA512"; -/// -pub const HMAC_SHA3_224_NAME: &str = "HMAC-SHA3-224"; +/*** Parameters ***/ + +/// The HMAC-specific parameters for one underlying hash function. /// -pub const HMAC_SHA3_256_NAME: &str = "HMAC-SHA3-256"; +/// [`HMAC`] itself is fully generic: it works with any [`Hash`], including hashes supplied by crates +/// outside this library. What HMAC cannot derive on its own is the *metadata* of the resulting +/// construction -- this trait supplies exactly that metadata, so the blanket [`Algorithm`], [`AlgorithmOID`] and +/// [`HMAC::keygen_from_rng`] can be impl'd generically rather than being written out once +/// per hash. /// -pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; +/// Each hash crate is expected to implement this trait for its own hash types and publishes the resulting type +/// alias. For example,`HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and `HMAC_SHA3_256` in +/// `bouncycastle_sha3::hmac`. /// -pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; - -/*** Type aliases ***/ -/// Public type for HMAC using SHA224. -#[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC; -impl Algorithm for HMAC_SHA224 { - const ALG_NAME: &'static str = HMAC_SHA224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} -/// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } -impl AlgorithmOID for HMAC_SHA224 { - const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; - const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; -} - -/// Public type for HKDF using SHA256. -#[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC; -impl Algorithm for HMAC_SHA256 { - const ALG_NAME: &'static str = HMAC_SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } -impl AlgorithmOID for HMAC_SHA256 { - const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; - const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; -} - -/// Public type for HKDF using SHA384. -#[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC; -impl Algorithm for HMAC_SHA384 { - const ALG_NAME: &'static str = HMAC_SHA384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } -impl AlgorithmOID for HMAC_SHA384 { - const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; - const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; -} - -/// Public type for HKDF using SHA512. -#[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC; -impl Algorithm for HMAC_SHA512 { - const ALG_NAME: &'static str = HMAC_SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} -/// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } -impl AlgorithmOID for HMAC_SHA512 { - const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; - const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; -} - -/// Public type for HKDF using SHA3_224. -#[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC; -impl Algorithm for HMAC_SHA3_224 { - const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } -impl AlgorithmOID for HMAC_SHA3_224 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0d]; +/// 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. + /// + /// Implementors should set this to `KeyMaterial<{Self::OUTPUT_LEN}>`. + /// + // todo: once rust stabilizes generic_const_exprs, delete this and return + // `KeyMaterial<{Self::OUTPUT_LEN}>` from `keygen_from_rng` instead. + type MACKey: KeyMaterialTrait + Default; + + /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. + const HMAC_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; + /// The OID of the HMAC over this hash in component form, as reported by [`AlgorithmOID::OID`]. + const HMAC_OID: &'static [u32]; + /// The DER encoding of [`HMACParams::HMAC_OID`], as reported by [`AlgorithmOID::OID_DER`]. + const HMAC_OID_DER: &'static [u8]; } -/// Public type for HKDF using SHA3_256. -#[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC; -impl Algorithm for HMAC_SHA3_256 { - const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } -impl AlgorithmOID for HMAC_SHA3_256 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0e]; -} - -/// Public type for HKDF using SHA3_384. -#[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC; -impl Algorithm for HMAC_SHA3_384 { - const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } -impl AlgorithmOID for HMAC_SHA3_384 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0f]; +impl Algorithm for HMAC { + const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::HMAC_MAX_SECURITY_STRENGTH; } -/// Public type for HKDF using SHA3_512. -#[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC; -impl Algorithm for HMAC_SHA3_512 { - const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} -/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } -impl AlgorithmOID for HMAC_SHA3_512 { - const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; - const OID_DER: &'static [u8] = - &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x10]; +impl AlgorithmOID for HMAC { + 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 below. Block lengths (bytes): SHA-224/256 = 64, SHA-384/512 = 128, -// SHA3-224 = 144, SHA3-256 = 136, SHA3-384 = 104, SHA3-512 = 72. +// 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. @@ -545,24 +366,6 @@ impl MAC for HMAC based on the HASH type. -// Using a macro to cut down on code duplication. -// todo: once rust supports const generics, we can remove the need for this macro and impl this directly -// on HMAC as `-> KeyMetarial` -macro_rules! impl_hmac_keygen { - ($hash:ty, $block_len:literal, $n:literal, $drbg:ty) => { - impl HMAC<$hash, $block_len> { - /// Generate a key of the appropriate length for the given HMAC - pub fn keygen() -> Result, RNGError> { - let mut key = KeyMaterial::<$n>::new(); - let mut os_rng = <$drbg>::new_from_os(); - os_rng.fill_keymaterial_out(&mut key)?; - key.set_key_type(KeyType::MACKey)?; - Ok(key) - } +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. + /// + // 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. + 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); } - }; -} -impl_hmac_keygen!(SHA224, 64, 28, HashDRBG_SHA256); -impl_hmac_keygen!(SHA256, 64, 32, HashDRBG_SHA256); -impl_hmac_keygen!(SHA384, 128, 48, HashDRBG_SHA512); -impl_hmac_keygen!(SHA512, 128, 64, HashDRBG_SHA512); -impl_hmac_keygen!(SHA3_224, 144, 28, HashDRBG_SHA256); -impl_hmac_keygen!(SHA3_256, 136, 32, HashDRBG_SHA256); -impl_hmac_keygen!(SHA3_384, 104, 48, HashDRBG_SHA512); -impl_hmac_keygen!(SHA3_512, 72, 64, HashDRBG_SHA512); + let mut key = HASH::MACKey::default(); + rng.fill_keymaterial_out(&mut key)?; + key.set_key_type(KeyType::MACKey)?; + Ok(key) + } +} diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 6b211c3b..0cfbe415 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod hmac_tests { - use bouncycastle_core::errors::{KeyMaterialError, MACError}; + use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, @@ -10,7 +10,10 @@ mod hmac_tests { use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; use bouncycastle_hmac::*; + use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; + use bouncycastle_sha2::hmac::*; use bouncycastle_sha2::*; + use bouncycastle_sha3::hmac::*; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; #[test] @@ -683,15 +686,19 @@ mod hmac_tests { assert_eq!(format!("{:?}", &hmac), "HMAC-SHA256 instance"); } - /// Exercises the `keygen()` function of each HMAC type alias: + /// Exercises the `keygen_from_rng()` function of each HMAC type alias: /// * the generated key must not be the all-zero array, - /// * `keygen()` returns a ready-to-use `KeyType::MACKey` key, so that + /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that /// * `HMAC::new(&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] fn $test_name() { - let key = $hmac::keygen().expect("keygen should succeed"); + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = $hmac::keygen_from_rng(&mut rng).expect("keygen_from_rng should succeed"); assert_eq!(key.key_len(), $n, "key should be the hash's output length"); assert_eq!(key.key_type(), KeyType::MACKey, "keygen should return a MAC key"); @@ -713,4 +720,23 @@ mod hmac_tests { 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_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. + /// HashDRBG_SHA256 offers 128 bits, which is enough for HMAC-SHA256 but not for HMAC-SHA512. + #[test] + fn keygen_rejects_weak_rng() { + let mut weak_rng = HashDRBG_SHA256::new_from_os(); + assert!( + matches!( + HMAC_SHA512::keygen_from_rng(&mut weak_rng), + Err(RNGError::SecurityStrengthInsufficientForAlgorithm) + ), + "a 128-bit RNG must not be accepted for a 256-bit HMAC" + ); + + let mut ok_rng = HashDRBG_SHA256::new_from_os(); + HMAC_SHA256::keygen_from_rng(&mut ok_rng) + .expect("a 128-bit RNG is sufficient for a 128-bit HMAC"); + } } diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index 7ff2e037..affcde1b 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -5,6 +5,8 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true +bouncycastle-hkdf.workspace = true +bouncycastle-hmac.workspace = true bouncycastle-utils.workspace = true [dev-dependencies] @@ -15,3 +17,11 @@ bouncycastle-rng.workspace = true [[bench]] name = "sha2_benches" harness = false + +[[bench]] +name = "hkdf_sha2_benches" +harness = false + +[[bench]] +name = "hmac_sha2_benches" +harness = false diff --git a/crypto/hkdf/benches/hkdf_benches.rs b/crypto/sha2/benches/hkdf_sha2_benches.rs similarity index 98% rename from crypto/hkdf/benches/hkdf_benches.rs rename to crypto/sha2/benches/hkdf_sha2_benches.rs index f9f3cb8e..6540a813 100644 --- a/crypto/hkdf/benches/hkdf_benches.rs +++ b/crypto/sha2/benches/hkdf_sha2_benches.rs @@ -2,8 +2,8 @@ use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::RNG; -use bouncycastle_hkdf::{HKDF_SHA256, HKDF_SHA512}; use bouncycastle_rng as rng; +use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA512}; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; diff --git a/crypto/hmac/benches/hmac_benches.rs b/crypto/sha2/benches/hmac_sha2_benches.rs similarity index 97% rename from crypto/hmac/benches/hmac_benches.rs rename to crypto/sha2/benches/hmac_sha2_benches.rs index 0e9dd039..d8ccf63b 100644 --- a/crypto/hmac/benches/hmac_benches.rs +++ b/crypto/sha2/benches/hmac_sha2_benches.rs @@ -1,7 +1,7 @@ use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; use bouncycastle_core::traits::{MAC, RNG}; -use bouncycastle_hmac::{HMAC_SHA256, HMAC_SHA512}; use bouncycastle_rng as rng; +use bouncycastle_sha2::hmac::{HMAC_SHA256, HMAC_SHA512}; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; diff --git a/crypto/sha2/src/hkdf.rs b/crypto/sha2/src/hkdf.rs new file mode 100644 index 00000000..7ca19650 --- /dev/null +++ b/crypto/sha2/src/hkdf.rs @@ -0,0 +1,251 @@ +//! HMAC-based Extract-and-Expand Key Derivation Function (HKDF) over the SHA-2 hashes, as per +//! RFC 5869, as allowed by NIST SP 800-56Cr2. +//! +//! Uses [`bouncycastle_hkdf`] to provide the HKDF-SHA2 instantiations: [`HKDF_SHA256`] and +//! [`HKDF_SHA512`]. Only those two are instantiated, matching what the KDF factory and the CLI +//! expose. +//! +//! HKDF is implemented generically in [`bouncycastle_hkdf`]; this module pins its const parameters +//! to the SHA-2 hashes and publishes the resulting type aliases, so that HKDF over a SHA-2 hash is +//! found in this crate, and [`bouncycastle_hkdf`] serves as a utility crate rather than as part of +//! the library's public API. +//! +//! # Usage +//! +//! Since HKDF uses HMAC as its underlying primitive, most of what is said in the [`crate::hmac`] +//! module docs about key material applies here as well. Unlike HMAC, an HKDF object is created +//! without an initial key, and will self-initialize the internal HMAC object as part of the +//! [`HKDF::extract`] phase. +//! +//! # Usage Examples +//! +//! ## Deriving a key via the [`KDF`] trait +//! +//! Being a Key Derivation Function (KDF), the objective of HKDF is to take input key material which is not +//! directly usable for its intended purpose and transform into a suitable output key. +//! Typically, this takes one or both of the following forms: +//! +//! * Starting with a seed and mixing in additional input to diversify the output key (ie make it unique). An example of this would be starting with a secret seed and mixing in a public ID or URL to generate keys which are unique per URL. +//! * Starting with a full-entropy seed which is at the correct security level for the application, but which is not long enough. An example could be starting with a 128-bit seed and mixing it with the strings "read" and "write" to produce one AES-128 key for each of the two directions of a communication channel. +//! +//! The simplest usage is via the one-shot functions provided by the [`KDF`] trait. +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::KDF; +//! use bouncycastle_sha2::hkdf::HKDF_SHA256; +//! +//! let key = KeyMaterial256::from_bytes_as_type( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::Seed).unwrap(); +//! +//! let hkdf = HKDF_SHA256::new(); +//! let key = hkdf.derive_key(&key, b"extra input").unwrap(); +//! ``` +//! +//! [`KDF::derive_key`] will produce a key the same length as the underlying hash function. +//! Longer output can be requested by instead using [`KDF::derive_key_out`] and providing a larger output buffer, +//! which will be filled. +//! +//! As with other uses of [`KeyMaterialTrait`], the [`KDF::derive_key`] function will track the entropy of the input +//! key material, and will set the entropy of the output key material accordingly. +//! +//! The [`KDF`] trait also provides the [`KDF::derive_key_from_multiple`] and [`KDF::derive_key_from_multiple_out`] +//! functions, which allows for multiple inputs to be mixed into a single output key, and which allows +//! for some advanced control of the underlying HKDF primitive. +//! +//! ## HKDF Extract-and-Expand +//! +//! The HKDF algorithm defined in RFC 5869 and SP 800-56Cr2 is a two-step KDF, broken into an Extract step +//! which essentially absorbs entropy from the input key material, +//! and an Expand step which produces the output key material of any requested size. +//! This interface is essentially a pre-cursor to the [`XOF`] API which was introduced with SHA3; the main +//! difference being that HKDF-Expand needs to be told up-front how much output to produce, whereas XOFs +//! can stream output as needed. +//! +//! Naturally, the full two-step HKDF-Extract and HKDF-Expand interface is provided by the [`HKDF`] struct, +//! and exposes additional HKDF-specific parameters beyond what is exposed by the functions of the [`KDF`] trait. +//! +//! The usage pattern here is flexible, but generally follows the pattern of first calling [`HKDF::extract`] +//! with a `salt` and an input key material `ikm`, which produces a pseudorandom key `prk`. +//! The `prk` will have a [`KeyType`] and [`SecurityStrength`] that results from combining the two provided input keys, +//! The `prk` may be used directly as a full-entropy cryptographic key. +//! +//! Since the extract step may be called with any number of input keys, a streaming interface is provided +//! whereby streaming mode in initialized with a call to [`HKDF::do_extract_init`], and then +//! repeated calls to [`HKDF::do_extract_update_key`] and [`HKDF::do_extract_update_bytes`] may be made. +//! Entropy from the inputs keys provided via [`HKDF::do_extract_update_key`] are credited towards the output key, +//! while bytes provided via [`HKDF::do_extract_update_bytes`] are not. +//! One restriction here is that once you start provided un-credited bytes via [`HKDF::do_extract_update_bytes`], +//! no more calls to [`HKDF::do_extract_update_key`] may be made. +//! The streaming API is completed with a call to either [`HKDF::do_extract_final`] or [`HKDF::do_extract_final_out`]. +//! +//! The second stage, [`HKDF::expand_out`] stretches the `prk` into a longer output key, still of the same [`KeyType`] +//! and [`SecurityStrength`]. +//! +//! A typical flow looks like this: +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; +//! use bouncycastle_sha2::hkdf::HKDF_SHA256; +//! +//! // setup variables +//! let salt = 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 ikm = KeyMaterial256::from_bytes_as_type( +//! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00", +//! KeyType::MACKey).unwrap(); +//! +//! let info = b"some extra context info"; +//! +//! // Use the streaming API to derive an output key of length 200 bytes. +//! let mut okm = KeyMaterial::<200>::new(); +//! let mut hkdf = HKDF_SHA256::default(); +//! hkdf.do_extract_init(&salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap(); +//! let prk = hkdf.do_extract_final().unwrap(); +//! HKDF_SHA256::expand_out(&prk, info, 200, &mut okm).unwrap(); +//! ``` +//! +//! Various convenience wrapper functions are provided which can reduce the amount of boilerplate code +//! for common cases. +//! For example, the above code can be condensed to: +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyType}; +//! use bouncycastle_sha2::hkdf::HKDF_SHA256; +//! +//! // setup variables +//! let salt = 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 ikm = KeyMaterial256::from_bytes_as_type( +//! b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00", +//! KeyType::MACKey).unwrap(); +//! +//! let info = b"some extra context info"; +//! +//! // Use the one-shot API to derive an output key of length 200 bytes. +//! let mut okm = KeyMaterial::<200>::new(); +//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap(); +//! ``` +//! +//! ## Suspending and resuming execution +//! +//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying +//! material can be provided either via [`HKDF::do_extract_update_key`] -- which will +//! credit the entropy of the provided [`KeyMaterial`] -- or as raw uncredited bytes via +//! [`HKDF::do_extract_update_bytes`]. +//! +//! As such, the *HKDF-Extract* phase can be suspended to a cache and resumed later via the +//! [`SuspendableKeyed`] trait. +//! +//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again +//! during finalization. Suspension and resumption are supported via the [`SuspendableKeyed`] trait +//! which requires the caller to store the salt securely and provide it again during resumption. +//! Note that providing a different salt during resumption cannot be detected by the library and +//! would silently produce a different PRK. +//! +//! ```rust +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::SuspendableKeyed; +//! use bouncycastle_sha2::hkdf::HKDF_SHA256; +//! +//! let salt = 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 ikm_part1 = b"input keying material part 1"; +//! let ikm_part2 = b" ...and part 2"; +//! +//! let mut hkdf = HKDF_SHA256::new(); +//! hkdf.do_extract_init(&salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part1).unwrap(); +//! +//! // suspend the in-progress extract (the salt is NOT included in the serialized state) +//! let serialized_state = hkdf.suspend(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same salt (make sure you store it securely!). +//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part2).unwrap(); +//! let _prk = hkdf.do_extract_final().unwrap(); +//! ``` +//! +//! # Memory Usage +//! +//! The HKDF object itself uses no heap memory; the `Vec`-returning and `Box` +//! -returning convenience methods of the [`KDF`] trait allocate their output, and the `*_out` +//! variants allocate nothing. +//! +//! | Object | Size (bytes) | +//! |---------------------------------------------------|--------------| +//! | `HKDF_SHA256` | 296 | +//! | `HKDF_SHA512` | 392 | +//! | Suspended `HKDF_SHA256` state | 122 | +//! | Suspended `HKDF_SHA512` state | 218 | +//! +//! The object is an `Option` of the inner extract-phase HMAC -- 272 bytes for SHA-256, 368 for +//! 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 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. +//! +//! # Security Considerations +//! +//! * Resuming a suspended HKDF with a different salt cannot be detected and silently produces a +//! different PRK; see the suspend/resume section above. +//! * Entropy is only credited for input supplied via [`HKDF::do_extract_update_key`]. Bytes supplied +//! via [`HKDF::do_extract_update_bytes`] are treated as uncredited context, so a PRK derived only +//! from raw bytes will not be tagged as full-entropy key material even if those bytes were in fact +//! random. +//! * The output key inherits the [`SecurityStrength`] of the inputs. HKDF stretches key material but +//! 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. +use crate::hmac::{SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN}; +use crate::{SHA256, SHA512}; +use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; +use bouncycastle_hkdf::HKDF; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +#[allow(unused_imports)] +use bouncycastle_core::traits::{KDF, SecurityStrength, SuspendableKeyed, XOF}; + +/*** String constants ***/ +/// +pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256"; +/// +pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512"; + +/*** Serialized-state length constants ***/ +// HKDF wraps the inner extract-phase HMAC's blob in 14 bytes of its own bookkeeping (a 3-byte library +// version header plus 11 bytes of present flag, state tag, entropy counter and security strength); +// see the `SuspendableKeyed` impl in `bouncycastle-hkdf` for the layout. +/// Length in bytes of the serialized state of [`HKDF_SHA256`]. +pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14; +/// Length in bytes of the serialized state of [`HKDF_SHA512`]. +pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14; + +/*** Type aliases ***/ +/// Public type for HKDF using SHA256. +#[allow(non_camel_case_types)] +pub type HKDF_SHA256 = HKDF; +/// Public type for HKDF using SHA512. +#[allow(non_camel_case_types)] +pub type HKDF_SHA512 = HKDF; diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs new file mode 100644 index 00000000..a94bb19b --- /dev/null +++ b/crypto/sha2/src/hmac.rs @@ -0,0 +1,334 @@ +//! HMAC over the SHA-2 hashes, as specified in RFC 2104, taking into account NIST Implementation +//! Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. +//! +//! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA2 instantiations: [`HMAC_SHA224`], +//! [`HMAC_SHA256`], [`HMAC_SHA384`] and [`HMAC_SHA512`]. +//! +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the +//! SHA-2-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! 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. +//! +//! # Usage +//! +//! An HMAC object (and the [`MAC`] trait in general) is used in three phases: +//! +//! * 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. +//! +//! # Usage Examples +//! +//! ## Constructing an HMAC object +//! +//! 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`]: +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::HashDRBG_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) +//! .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: +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! +//! let key = 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( +//! "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. +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::HashDRBG_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) +//! .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); +//! ``` +//! +//! 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: +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::HashDRBG_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) +//! .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"); +//! hmac.do_update(b"Hello,"); +//! hmac.do_update(b" world!"); +//! let output: Vec = hmac.do_final(); +//! ``` +//! +//! ## 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 simplest usage is via the one-shot functions. +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha2::hmac::HMAC_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( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).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" +//! ) +//! { +//! println!("MAC is valid!"); +//! } else { +//! println!("MAC is invalid!"); +//! } +//! ``` +//! +//! Similarly, a streaming version is available, which is identical to the streaming interface for +//! computing a mac value, but calls [`MAC::do_verify_final`] instead of [`MAC::do_final`]. +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha2::hmac::HMAC_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( +//! 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(); +//! 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" +//! ) +//! { +//! println!("MAC is valid!"); +//! } else { +//! println!("MAC is invalid!"); +//! } +//! ``` +//! +//! ## Suspending and resuming execution +//! +//! When MAC'ing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow +//! network connection. For this reason, all HMAC algorithms impl [`SuspendableKeyed`]. +//! +//! Note that since HMAC is a keyed algorithm and we do not want to serialize the private key into +//! the state, the trait structure forces you to re-provide the same key when you resume the +//! operation. Securely storing this key in the interim is the responsibility of the caller. Note +//! also that if you resume the HMAC with the wrong key, [`SuspendableKeyed::from_suspended`] has no +//! way to detect this, so the end result will be a broken MAC value computed with different keys in +//! the inner and outer pad. So make sure you resume with the same key! +//! +//! ```rust +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +//! use bouncycastle_sha2::hmac::HMAC_SHA256; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let key = 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(); +//! hmac.do_update(msg_part1); +//! +//! // suspend the in-progress mac (the key is NOT included in the serialized state) +//! let serialized_state = hmac.suspend(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same key (make sure you store it 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(); +//! ``` +//! +//! # Memory Usage +//! +//! 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_SHA224`, `HMAC_SHA256` | 184 | +//! | `HMAC_SHA384`, `HMAC_SHA512` | 344 | +//! | 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. +//! +//! # 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. +//! * 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. +//! * Truncating the MAC output below [`MIN_FIPS_DIGEST_LEN`] (4 bytes) is rejected, per FIPS 140-2 +//! IG A.8 / NIST SP 800-107-r1 Section 5.3.3. That 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". +//! * Resuming a suspended HMAC with the wrong key cannot be detected and silently produces a wrong +//! MAC; see the suspend/resume section above. +//! * A key longer than the hash's block length is pre-hashed down to the output length (RFC 2104 +//! Section 2), so very long keys add no strength beyond that point. +use crate::{SHA224, SHA256, SHA384, SHA512}; +use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::{HashAlgParams, SecurityStrength}; +use bouncycastle_hmac::{HMAC, HMACParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::KeyType; +#[allow(unused_imports)] +use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +#[allow(unused_imports)] +use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; + +/*** String constants ***/ +/// +pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224"; +/// +pub const HMAC_SHA256_NAME: &str = "HMAC-SHA256"; +/// +pub const HMAC_SHA384_NAME: &str = "HMAC-SHA384"; +/// +pub const HMAC_SHA512_NAME: &str = "HMAC-SHA512"; + +/*** Type aliases ***/ +/// Public type for HMAC using SHA224. +#[allow(non_camel_case_types)] +pub type HMAC_SHA224 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA224 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; +} + +/// Public type for HMAC using SHA256. +#[allow(non_camel_case_types)] +pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA256 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; +} + +/// Public type for HMAC using SHA384. +#[allow(non_camel_case_types)] +pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA384 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; +} + +/// Public type for HMAC using SHA512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA512 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; +} + +/*** Serialized-state length constants ***/ +// HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and +// must be re-supplied on resume -- so each of these is the underlying hash's own state length. +/// Length in bytes of the serialized state of [`HMAC_SHA224`]. +pub const SUSPENDED_HMAC_SHA224_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA256`]. +pub const SUSPENDED_HMAC_SHA256_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA384`]. +pub const SUSPENDED_HMAC_SHA384_STATE_LEN: usize = SUSPENDED_SHA512_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA512`]. +pub const SUSPENDED_HMAC_SHA512_STATE_LEN: usize = SUSPENDED_SHA512_STATE_LEN; diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 6906e0c6..c53544d7 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -1,5 +1,11 @@ //! Implements SHA2 as per NIST FIPS 180-4. //! +//! This crate provides the following primitives: +//! +//! * SHA2 [`Hash`] functions. +//! * HMAC_SHA2* [`MAC`] functions. +//! * HKDF-SHA2* [`KDF`] functions. +//! //! # Examples //! ## Hash //! Hash functionality is accessed via the [`bouncycastle_core::traits::Hash`] trait, @@ -14,7 +20,7 @@ //! let output: Vec = sha2::SHA256::new().hash(data); //! ``` //! -//! More advanced usage will require creating a SHA3 or SHAKE object to hold state between successive calls, +//! More advanced usage will require creating a SHA2 object to hold state between successive calls, //! for example if input is received in chunks and not all available at the same time: //! //! ``` @@ -34,6 +40,44 @@ //! let output: Vec = sha2.do_final(); //! ``` //! +//! ## HMAC +//! See [hmac]. +//! +//! ## HKDF +//! +//! See [hkdf] +//! +//! # Memory Usage +//! +//! No heap memory is used by the algorithms themselves; the `Vec`-returning convenience methods +//! allocate only the output buffer, and the `*_out` variants allocate nothing. +//! +//! | Object | Size (bytes) | +//! |----------------------------------------------------------|--------------| +//! | `SHA224`, `SHA256` | 112 | +//! | `SHA384`, `SHA512` | 208 | +//! | Suspended `SHA224`/`SHA256` state | 108 | +//! | Suspended `SHA384`/`SHA512` state | 204 | +//! +//! The object holds the 8-word chaining value plus one block of buffered input. The compression +//! function additionally uses a 64-word (SHA-256 family, 256 bytes) or 80-word (SHA-512 family, +//! 640 bytes) message schedule on the stack for the duration of a call. +//! +//! # Security Considerations +//! +//! * SHA-224/256/384/512 offer 112/128/192/256 bits of collision resistance respectively. +//! * SHA-2 is a Merkle–Damgård construction and is therefore subject to length-extension: +//! `H(k || m)` is not a secure MAC. Use HMAC ([`crate::hmac`]) for keyed hashing. +//! * SHA-224 and SHA-384 are truncations of SHA-256 and SHA-512 with distinct initial values, and +//! are not vulnerable to length extension in the same direct way, but should still not be used as +//! `H(k || m)` MACs. +//! * The chaining value and input buffer are held in [`bouncycastle_utils::secret::Secret`] and +//! zeroized on drop. Transient copies (working variables and message schedule) in registers/stack +//! locals during compression are not zeroized. +//! * The implementation contains no data-dependent branches or table lookups. +//! * Messages up to 2^64 bytes are supported (FIPS 180-4 permits 2^64 bits for SHA-224/256 and +//! 2^128 bits for SHA-384/512; the SHA-512 family limit here is 2^67 bits). +//! //! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation @@ -72,13 +116,16 @@ mod sha256; mod sha512; +pub mod hkdf; +pub mod hmac; + pub use self::sha256::SHA256Internal; pub use self::sha512::SHA512Internal; use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, SecurityStrength}; /*** Imports needed for docs ***/ #[allow(unused_imports)] -use bouncycastle_core::traits::Suspendable; +use bouncycastle_core::traits::{Hash, KDF, MAC, Suspendable}; /*** String constants ***/ /// diff --git a/crypto/sha3/Cargo.toml b/crypto/sha3/Cargo.toml index 0465fadc..60f5170b 100644 --- a/crypto/sha3/Cargo.toml +++ b/crypto/sha3/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true +bouncycastle-hmac.workspace = true bouncycastle-utils.workspace = true [dev-dependencies] @@ -16,3 +17,7 @@ bouncycastle-rng.workspace = true [[bench]] name = "sha3_benches" harness = false + +[[bench]] +name = "hmac_sha3_benches" +harness = false diff --git a/crypto/sha3/benches/hmac_sha3_benches.rs b/crypto/sha3/benches/hmac_sha3_benches.rs new file mode 100644 index 00000000..badb2b7c --- /dev/null +++ b/crypto/sha3/benches/hmac_sha3_benches.rs @@ -0,0 +1,55 @@ +use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterial512, KeyType}; +use bouncycastle_core::traits::{MAC, RNG}; +use bouncycastle_rng as rng; +use bouncycastle_sha3::hmac::{HMAC_SHA3_256, HMAC_SHA3_512}; +use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; + +fn bench_hmac_sha3_256(c: &mut Criterion) { + let mut data_block = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data_block).unwrap(); + + let mut big_data: Vec = vec![]; + for _ in 0..16 { + big_data.extend_from_slice(&data_block); + } + + let hmac_key = KeyMaterial256::from_bytes_as_type(&data_block[..32], KeyType::MACKey).unwrap(); + let mut out = [0u8; 32]; + + let mut group = c.benchmark_group("hmac::HMAC_SHA256::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| { + b.iter(|| { + HMAC_SHA3_256::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + black_box(&out); + }) + }); + group.finish(); +} + +fn bench_hmac_sha3_512(c: &mut Criterion) { + let mut data_block = [0_u8; 1024]; + rng::DefaultRNG::default().next_bytes_out(&mut data_block).unwrap(); + + let mut big_data: Vec = vec![]; + for _ in 0..16 { + big_data.extend_from_slice(&data_block); + } + + let hmac_key = KeyMaterial512::from_bytes_as_type(&data_block[..64], KeyType::MACKey).unwrap(); + let mut out = [0u8; 64]; + + let mut group = c.benchmark_group("hmac::HMAC_SHA512::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| { + b.iter(|| { + HMAC_SHA3_512::new(&hmac_key).unwrap().mac_out(black_box(&big_data), &mut out).unwrap(); + black_box(&out); + }) + }); + group.finish(); +} + +criterion_group!(benches, bench_hmac_sha3_256, bench_hmac_sha3_512); +criterion_main!(benches); diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs new file mode 100644 index 00000000..7a7240a4 --- /dev/null +++ b/crypto/sha3/src/hmac.rs @@ -0,0 +1,342 @@ +//! HMAC over the SHA-3 hashes, as specified in RFC 2104, taking into account NIST Implementation +//! Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. +//! +//! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA3 instantiations: [`HMAC_SHA3_224`], +//! [`HMAC_SHA3_256`], [`HMAC_SHA3_384`] and [`HMAC_SHA3_512`]. +//! +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the +//! SHA-3-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! 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. +//! +//! # Usage +//! +//! An HMAC object (and the [`MAC`] trait in general) is used in three phases: +//! +//! * 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. +//! +//! # Usage Examples +//! +//! ## Constructing an HMAC object +//! +//! 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`]: +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::DefaultRNG; +//! 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) +//! .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: +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! +//! let key = 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( +//! "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. +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::DefaultRNG; +//! 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) +//! .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); +//! ``` +//! +//! 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: +//! +//! ``` +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_rng::DefaultRNG; +//! 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) +//! .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"); +//! hmac.do_update(b"Hello,"); +//! hmac.do_update(b" world!"); +//! let output: Vec = hmac.do_final(); +//! ``` +//! +//! ## 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 simplest usage is via the one-shot functions. +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha3::hmac::HMAC_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( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).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" +//! ) +//! { +//! println!("MAC is valid!"); +//! } else { +//! println!("MAC is invalid!"); +//! } +//! ``` +//! +//! Similarly, a streaming version is available, which is identical to the streaming interface for +//! computing a mac value, but calls [`MAC::do_verify_final`] instead of [`MAC::do_final`]. +//! +//! ``` +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::MAC; +//! use bouncycastle_sha3::hmac::HMAC_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( +//! 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(); +//! 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" +//! ) +//! { +//! println!("MAC is valid!"); +//! } else { +//! println!("MAC is invalid!"); +//! } +//! ``` +//! +//! ## Suspending and resuming execution +//! +//! When MAC'ing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow +//! network connection. For this reason, all HMAC algorithms impl [`SuspendableKeyed`]. +//! +//! Note that since HMAC is a keyed algorithm and we do not want to serialize the private key into +//! the state, the trait structure forces you to re-provide the same key when you resume the +//! operation. Securely storing this key in the interim is the responsibility of the caller. Note +//! also that if you resume the HMAC with the wrong key, [`SuspendableKeyed::from_suspended`] has no +//! way to detect this, so the end result will be a broken MAC value computed with different keys in +//! the inner and outer pad. So make sure you resume with the same key! +//! +//! ```rust +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +//! use bouncycastle_sha3::hmac::HMAC_SHA3_256; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let key = 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(); +//! hmac.do_update(msg_part1); +//! +//! // suspend the in-progress mac (the key is NOT included in the serialized state) +//! let serialized_state = hmac.suspend(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same key (make sure you store it 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(); +//! ``` +//! +//! # Memory Usage +//! +//! 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 +//! 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. +//! * 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. +//! * Truncating the MAC output below [`MIN_FIPS_DIGEST_LEN`] (4 bytes) is rejected, per FIPS 140-2 +//! IG A.8 / NIST SP 800-107-r1 Section 5.3.3. That 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". +//! * Resuming a suspended HMAC with the wrong key cannot be detected and silently produces a wrong +//! MAC; see the suspend/resume section above. +//! * SHA-3 is a sponge and is not vulnerable to the length-extension attack that motivates HMAC for +//! Merkle-Damgard hashes, so a plain `SHA3(k || m)` is not broken the way `SHA256(k || m)` is. +//! HMAC-SHA3 remains the right choice for interoperability and for FIPS 198-1 conformance, and +//! KMAC (NIST SP 800-185) is the SHA-3-native alternative. + +use crate::SUSPENDED_SHA3_STATE_LEN; +use crate::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::{HashAlgParams, SecurityStrength}; +use bouncycastle_hmac::{HMAC, HMACParams}; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::key_material::KeyType; +#[allow(unused_imports)] +use bouncycastle_core::traits::{MAC, SuspendableKeyed}; +#[allow(unused_imports)] +use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; + +/*** String constants ***/ +/// +pub const HMAC_SHA3_224_NAME: &str = "HMAC-SHA3-224"; +/// +pub const HMAC_SHA3_256_NAME: &str = "HMAC-SHA3-256"; +/// +pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; +/// +pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; + +/*** Type aliases ***/ +/// Public type for HMAC using SHA3_224. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_224 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA3_224 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0d]; +} + +/// Public type for HMAC using SHA3_256. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA3_256 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0e]; +} + +/// Public type for HMAC using SHA3_384. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA3_384 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0f]; +} + +/// Public type for HMAC using SHA3_512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN }>; +impl HMACParams for SHA3_512 { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + const HMAC_ALG_NAME: &'static str = HMAC_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]; + const HMAC_OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x10]; +} + +/*** Serialized-state length constants ***/ +// HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and +// must be re-supplied on resume -- so each of these is the underlying hash's own state length. All +// four SHA-3 hashes share one Keccak state size, hence one constant. +/// Length in bytes of the serialized state of [`HMAC_SHA3_224`]. +pub const SUSPENDED_HMAC_SHA3_224_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA3_256`]. +pub const SUSPENDED_HMAC_SHA3_256_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA3_384`]. +pub const SUSPENDED_HMAC_SHA3_384_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; +/// Length in bytes of the serialized state of [`HMAC_SHA3_512`]. +pub const SUSPENDED_HMAC_SHA3_512_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 841451c0..22c1dc0b 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -1,5 +1,12 @@ //! Implements SHA3 as per NIST FIPS 202. //! +//! This crate provides the following primitives: +//! +//! * SHA3 [`Hash`] functions. +//! * SHAKE [`XOF`] functions. +//! * SHA3-based [`KDF`] functions. +//! * HMAC_SHA3_* [`MAC`] functions. +//! //! # Examples //! ## Hash //! Hash functionality is accessed via the [`Hash`] trait, @@ -108,30 +115,8 @@ //! [`KeyType::CryptographicRandom`] since the input [`KeyMaterial`] is 16 bytes but [`SHA3_256`] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. //! -//! # Memory Usage -//! -//! All SHA3 and SHAKE variants share the same Keccak-f\[1600\] sponge and so have identical memory -//! footprints. No heap memory is used by the algorithms themselves; the `Vec`-returning -//! convenience methods allocate only the output buffer, and the `*_out` variants allocate nothing. -//! -//! | Object | Size (bytes) | -//! |-----------------------------------------|--------------| -//! | `SHA3_224` .. `SHA3_512`, `SHAKE128/256` | 440 | -//! | Suspended state ([`Suspendable`]) | 415 | -//! -//! Sizes are `core::mem::size_of` values reported by `mem_usage_benches/bench_sha3_mem_usage.rs` -//! (`cargo run --release -p mem_usage_benches --bin bench_sha3_mem_usage`), which also has valgrind -//! massif entry points for measuring peak stack usage of the hash, XOF and suspend/resume paths. -//! -//! # Security Considerations -//! -//! * SHA3-224/256/384/512 offer 112/128/192/256 bits of collision resistance respectively; SHAKE128 -//! and SHAKE256 offer 128 and 256 bits of security for output lengths at least twice that size -//! (FIPS 202 Appendix A.1). -//! * SHAKE is an XOF, not a hash: `SHAKE128(m, 32)` is a prefix of `SHAKE128(m, 64)`. If the output -//! length must be bound to the digest, include it in the message (FIPS 202 Appendix A.2). -//! * The sponge state and queue are held in [`bouncycastle_utils::secret::Secret`] and zeroized on -//! drop. +//! ## HMAC +//! See [hmac]. //! //! # Suspending and resuming execution //! @@ -163,6 +148,31 @@ //! sha3_resumed.do_update(msg_part2); //! let h: Vec = sha3_resumed.do_final(); //! ``` +//! +//! # Memory Usage +//! +//! All SHA3 and SHAKE variants share the same Keccak-f\[1600\] sponge and so have identical memory +//! footprints. No heap memory is used by the algorithms themselves; the `Vec`-returning +//! convenience methods allocate only the output buffer, and the `*_out` variants allocate nothing. +//! +//! | Object | Size (bytes) | +//! |-----------------------------------------|--------------| +//! | `SHA3_224` .. `SHA3_512`, `SHAKE128/256` | 440 | +//! | Suspended state ([`Suspendable`]) | 415 | +//! +//! Sizes are `core::mem::size_of` values reported by `mem_usage_benches/bench_sha3_mem_usage.rs` +//! (`cargo run --release -p mem_usage_benches --bin bench_sha3_mem_usage`), which also has valgrind +//! massif entry points for measuring peak stack usage of the hash, XOF and suspend/resume paths. +//! +//! # Security Considerations +//! +//! * SHA3-224/256/384/512 offer 112/128/192/256 bits of collision resistance respectively; SHAKE128 +//! and SHAKE256 offer 128 and 256 bits of security for output lengths at least twice that size +//! (FIPS 202 Appendix A.1). +//! * SHAKE is an XOF, not a hash: `SHAKE128(m, 32)` is a prefix of `SHAKE128(m, 64)`. If the output +//! length must be bound to the digest, include it in the message (FIPS 202 Appendix A.2). +//! * The sponge state and queue are held in [`bouncycastle_utils::secret::Secret`] and zeroized on +//! drop. #![forbid(unsafe_code)] #![forbid(missing_docs)] @@ -177,13 +187,15 @@ use bouncycastle_core::errors::HashError; #[allow(unused_imports)] use bouncycastle_core::key_material::{KeyMaterial, KeyType}; #[allow(unused_imports)] -use bouncycastle_core::traits::{Hash, KDF, Suspendable, XOF}; +use bouncycastle_core::traits::{Hash, KDF, MAC, Suspendable, XOF}; // end of doc-only imports mod keccak; mod sha3; mod shake; +pub mod hmac; + /*** String constants ***/ /// Algorithm name string for SHA3-224, as used by the factories and CLI. pub const SHA3_224_NAME: &str = "SHA3-224";