Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions alpha_0.1.2_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
* Ensure that all crates have `#![forbid(missing_docs)]`
* Apply Secret trait consistently across the library --> study the `Zeroize` trait in RustCrypto
* Change all "[u8;0]" to "[]" throughout the code and docs ... or better yet, change the APIs to take an Option<>
* Change all `-> Vec<u8>` to `-> [u8; CONST_LEN]`, and the `output: &mut [u8]` to `output: &mut [u8; CONST_LEN]` where
appropriate.
* Change the `output: &mut [u8]` to `output: &mut [u8; CONST_LEN]` where appropriate. (The `-> Vec<u8>` half of this
item is done, see changelog. The `*_out` slice parameters were deliberately left as `&mut [u8]` because the
documented truncation / oversized-buffer semantics depend on them; revisit per-API.)
* Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses
fixed-sized blocks?
* Create a cargo feature #[cfg(feature='rng')] and put it around things like keygen that takes an rng so that the build
Expand Down Expand Up @@ -52,6 +53,15 @@

* ML-DSA
* Low-Memory ML-DSA -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with only minor performance impact.
* (Breaking, progress on #14) Removed all heap-allocating `-> Vec<u8>` functions from the `Hash` and `MAC` traits
(`hash`, `do_final`, `do_final_partial_bits`, `mac`). They are replaced by the new `HashFixedOutput<const OUTPUT_LEN>`
and `MACFixedOutput<const OUTPUT_LEN>` traits which return `[u8; OUTPUT_LEN]` stack arrays, following the same
const-generic pattern as the `KEM` and `Signature` traits. All concrete algorithms (SHA2, SHA3, HMAC) implement the
new traits; the factory enums keep only the `*_out` functions because their output length is a runtime property.
The XOF functions (`hash_xof`, `squeeze`) keep returning `Vec<u8>` because their output length is inherently a
runtime parameter.
* HMAC no longer heap-allocates its intermediate inner digest (was an internal `vec!`, now a fixed stack buffer),
and the inner digest buffer is zeroized after use.
* All public `*_out(.., out: &mut [u8])` functions now begin by zeroizing the entire output buffer with `.fill(0)`,
preventing exposure of stale data in oversized output buffers or on early error returns.
* Github issues resolved:
Expand Down
7 changes: 5 additions & 2 deletions cli/src/mac_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@ fn do_mac(mut mac: impl MAC, verify_val: &Option<String>, output_hex: bool) {

if verify_val.is_none() {
// compute a MAC value
let out = mac.do_final();
let mut out = [0u8; 64];
let bytes_written =
mac.do_final_out(&mut out).expect("Failed to compute the MAC value");
let out = &out[..bytes_written];

if output_hex {
for b in out.iter() {
print!("{b:02x}");
}
} else {
io::stdout().write(&out).unwrap();
io::stdout().write(out).unwrap();
}
println!();
} else {
Expand Down
6 changes: 4 additions & 2 deletions cli/src/sha2_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ fn do_sha2(mut sha2: impl Hash, output_hex: bool) {
bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
}

let out = sha2.do_final();
let mut out = [0u8; 64];
let bytes_written = sha2.do_final_out(&mut out);
let out = &out[..bytes_written];

if output_hex {
for b in out.iter() {
print!("{b:02x}");
}
} else { io::stdout().write(&out).unwrap(); }
} else { io::stdout().write(out).unwrap(); }
println!();
}
6 changes: 4 additions & 2 deletions cli/src/sha3_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ fn do_sha3(mut sha3: impl Hash, output_hex: bool) {
bytes_read = io::stdin().read(&mut buf).expect("Failed to read from stdin");
}

let out = sha3.do_final();
let mut out = [0u8; 64];
let bytes_written = sha3.do_final_out(&mut out);
let out = &out[..bytes_written];

if output_hex {
for b in out.iter() {
print!("{b:02x}");
}
} else { io::stdout().write(&out).unwrap(); }
} else { io::stdout().write(out).unwrap(); }
println!();
}

Expand Down
17 changes: 9 additions & 8 deletions crypto/core-test-framework/src/hash.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bouncycastle_core::traits::{Hash, HashAlgParams};
use bouncycastle_core::traits::{HashAlgParams, HashFixedOutput};

pub struct TestFrameworkHash {
pub enable_partial_final_input_tests: bool,
Expand All @@ -13,30 +13,31 @@ impl TestFrameworkHash {
/// This gives good baseline test coverage, but is not exhaustive; for example it does not test
/// do_final_partial_bits() or do_final_partial_bits_out()
/// because those require different input-output pairs.
pub fn test_hash<H: Hash + HashAlgParams + Default>(
pub fn test_hash<H: HashFixedOutput<N> + HashAlgParams + Default, const N: usize>(
&self,
input: &[u8],
expected_output: &[u8],
) {
/*** fn result_len() -> usize ***/
assert_eq!(H::default().output_len(), H::OUTPUT_LEN);
assert_eq!(N, H::OUTPUT_LEN);

/*** fn hash(self, data: &[u8]) -> Vec<u8> **/
let output_vec = H::default().hash(input);
assert_eq!(output_vec, expected_output);
/*** fn hash(self, data: &[u8]) -> [u8; N] **/
let output_arr = H::default().hash(input);
assert_eq!(&output_arr[..], expected_output);

/*** fn hash_out(self, data: &[u8], output: &mut [u8]) -> Result<usize, HashError> ***/
let mut output_buf = vec![0_u8; H::OUTPUT_LEN];
H::default().hash_out(input, &mut output_buf);
assert_eq!(output_buf, expected_output);

/*** fn do_update(&mut self, data: &[u8]) -> Result<(), HashError> ***/
/*** fn do_final(self) -> Result<Vec<u8>, HashError> **/
/*** fn do_final(self) -> [u8; N] **/

let mut message_digest = H::default();
message_digest.do_update(input);
let output_buf = message_digest.do_final();
assert_eq!(expected_output, output_buf, "Incorrect output for input (update_bytes)");
assert_eq!(expected_output, &output_buf[..], "Incorrect output for input (update_bytes)");

for length in 1..output_buf.len() {
let mut truncated = vec![0_u8; length];
Expand All @@ -58,7 +59,7 @@ impl TestFrameworkHash {
message_digest.do_update(chunk);
}
let output_buf = message_digest.do_final();
assert_eq!(expected_output, output_buf, "Incorrect output for input (update_bytes)");
assert_eq!(expected_output, &output_buf[..], "Incorrect output for input (update_bytes)");

/*** fn do_update(&mut self, data: &[u8]) -> Result<(), HashError> ***/
/*** fn do_final_out(self, output: &mut [u8]) -> Result<usize, HashError> ***/
Expand Down
8 changes: 4 additions & 4 deletions crypto/core-test-framework/src/mac.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::DUMMY_SEED_512;
use bouncycastle_core::errors::{KeyMaterialError, MACError};
use bouncycastle_core::key_material::{KeyMaterial512, KeyType, KeyMaterialTrait};
use bouncycastle_core::traits::MAC;
use bouncycastle_core::traits::MACFixedOutput;
use bouncycastle_core::traits::{SecurityStrength};

pub struct TestFrameworkMAC {
Expand All @@ -15,15 +15,15 @@ impl TestFrameworkMAC {

/// Test all the members of trait Hash against the given input-output pair.
/// This gives good baseline test coverage, but is not exhaustive.
pub fn test_mac<M: MAC>(
pub fn test_mac<M: MACFixedOutput<N>, const N: usize>(
&self,
key: &impl KeyMaterialTrait,
input: &[u8],
expected_output: &[u8],
) {
// Test ::mac()
let out = M::new_allow_weak_key(key).unwrap().mac(input);
assert_eq!(out, expected_output);
assert_eq!(&out[..], expected_output);

// Test ::mac_out
let mut out = vec![0u8; expected_output.len()];
Expand Down Expand Up @@ -53,7 +53,7 @@ impl TestFrameworkMAC {
let output_len = mac.output_len();
mac.do_update(input);
let out = mac.do_final();
assert_eq!(out, expected_output);
assert_eq!(&out[..], expected_output);

// Test .output_len()
assert_eq!(output_len, out.len());
Expand Down
6 changes: 4 additions & 2 deletions crypto/core-test-framework/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,16 @@ impl TestFrameworkSignature {

// sign_ph
let (pk, sk) = SigAlg::keygen().unwrap();
let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap();
let mut ph = [0u8; PH_LEN];
HASH::default().hash_out(msg, &mut ph);
let sig_val = SigAlg::sign_ph(&sk, &ph, None).unwrap();
SigAlg::verify(&pk, msg, None, &sig_val).unwrap();
SigAlg::verify_ph(&pk, &ph, None, &sig_val).unwrap();

// sign_ph_out
let (pk, sk) = SigAlg::keygen().unwrap();
let ph: [u8; PH_LEN] = HASH::default().hash(msg)[..PH_LEN].try_into().unwrap();
let mut ph = [0u8; PH_LEN];
HASH::default().hash_out(msg, &mut ph);
let mut sig_val = [0u8; SIG_LEN];
let bytes_written = SigAlg::sign_ph_out(&sk, &ph, None, &mut sig_val).unwrap();
assert_eq!(bytes_written, SIG_LEN);
Expand Down
116 changes: 83 additions & 33 deletions crypto/core/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ pub trait Hash : Default {
/// The size of the output in bytes.
fn output_len(&self) -> usize;

/// A static one-shot API that hashes the provided data.
/// `data` can be of any length, including zero bytes.
fn hash(self, data: &[u8]) -> Vec<u8>;

/// A static one-shot API that hashes the provided data into the provided output slice.
/// `data` can be of any length, including zero bytes.
/// The entire output buffer is zeroized before the hash output is written.
Expand All @@ -40,11 +36,6 @@ pub trait Hash : Default {
// fn do_update(&mut self, data: &[u8]) -> Result<(), HashError>;
fn do_update(&mut self, data: &[u8]);

/// Finish absorbing input and produce the hashes output.
/// Consumes self, so this must be the final call to this object.
// fn do_final(self) -> Result<Vec<u8>, HashError>;
fn do_final(self) -> Vec<u8>;

/// Finish absorbing input and produce the hashes output.
/// Consumes self, so this must be the final call to this object.
///
Expand All @@ -57,14 +48,6 @@ pub trait Hash : Default {
/// The return value is the number of bytes written.
fn do_final_out(self, output: &mut [u8]) -> usize;

/// The same as [Hash::do_final], but allows for supplying a partial byte as the last input.
/// Assumes that the input is in the least significant bits (big endian).
fn do_final_partial_bits(
self,
partial_byte: u8,
num_partial_bits: usize,
) -> Result<Vec<u8>, HashError>;

/// The same as [Hash::do_final_out], but allows for supplying a partial byte as the last input.
/// Assumes that the input is in the least significant bits (big endian).
/// will be placed in the first [Hash::output_len] bytes.
Expand All @@ -81,6 +64,49 @@ pub trait Hash : Default {
fn max_security_strength(&self) -> SecurityStrength;
}

/// An extension to [Hash] for algorithms whose output length is known at compile time.
///
/// `OUTPUT_LEN` is the output length of the hash in bytes and must equal [Hash::output_len];
/// implementors are expected to declare it via the matching [HashAlgParams::OUTPUT_LEN] constant,
/// for example `impl HashFixedOutput<{ SHA256Params::OUTPUT_LEN }> for SHA256 {}`.
///
/// The provided methods return fixed-size arrays instead of heap allocations, so they are
/// usable in `no_std` environments and keep all intermediate state on the stack.
/// Runtime-dispatching wrappers (such as factory enums) whose output length varies by variant
/// cannot implement this trait and offer only the `*_out` functions of [Hash].
pub trait HashFixedOutput<const OUTPUT_LEN: usize>: Hash {
/// A static one-shot API that hashes the provided data.
/// `data` can be of any length, including zero bytes.
fn hash(self, data: &[u8]) -> [u8; OUTPUT_LEN] {
let mut output = [0u8; OUTPUT_LEN];
let written = self.hash_out(data, &mut output);
debug_assert_eq!(written, OUTPUT_LEN);
output
}

/// Finish absorbing input and produce the hashes output.
/// Consumes self, so this must be the final call to this object.
fn do_final(self) -> [u8; OUTPUT_LEN] {
let mut output = [0u8; OUTPUT_LEN];
let written = self.do_final_out(&mut output);
debug_assert_eq!(written, OUTPUT_LEN);
output
}

/// The same as [HashFixedOutput::do_final], but allows for supplying a partial byte as the last input.
/// Assumes that the input is in the least significant bits (big endian).
fn do_final_partial_bits(
self,
partial_byte: u8,
num_partial_bits: usize,
) -> Result<[u8; OUTPUT_LEN], HashError> {
let mut output = [0u8; OUTPUT_LEN];
let written = self.do_final_partial_bits_out(partial_byte, num_partial_bits, &mut output)?;
debug_assert_eq!(written, OUTPUT_LEN);
Ok(output)
}
}

pub trait HashAlgParams: Algorithm {
const OUTPUT_LEN: usize;
const BLOCK_LEN: usize;
Expand Down Expand Up @@ -234,13 +260,14 @@ pub trait KEMPrivateKey<const SK_LEN: usize> : PartialEq + Eq + Clone + Secret +
/// A MAC algorithm takes in a key and some data, and produces a MAC (message authentication code) that
/// can be used to verify the integrity of data.
///
/// This trait provides one-shot functions [MAC::mac], [MAC::mac_out], and [MAC::verify].
/// It also provides streaming functions [MAC::do_update], [MAC::do_final], [MAC::do_final_out],
/// This trait provides one-shot functions [MAC::mac_out] and [MAC::verify].
/// It also provides streaming functions [MAC::do_update], [MAC::do_final_out],
/// and [MAC::do_verify_final].
/// Fixed-size variants that return the MAC value as an array are provided by [MACFixedOutput].
/// The workflow is that a MAC object is initialized with a key with [MAC::new] -- or [MAC::new_allow_weak_key] if you
/// need to disable the library's safety mechanism to prevent the use of weak keys -- then data is
/// processed into one or more calls to [MAC::do_update],
/// after that the object can either create a MAC with [MAC::do_final] or [MAC::do_final_out] (which are final functions, and so consume the object),
/// after that the object can either create a MAC with [MACFixedOutput::do_final] or [MAC::do_final_out] (which are final functions, and so consume the object),
/// or the object can be used to verify a MAC.
///
/// For varifying an existing MAC, it is functionally equivalent to use the provided [MAC::verify] and [MAC::do_verify_final]
Expand Down Expand Up @@ -282,17 +309,6 @@ pub trait MAC: Sized {
/// The size of the output in bytes.
fn output_len(&self) -> usize;

/// One-shot API that computes a MAC for the provided data.
/// `data` can be of any length, including zero bytes.
///
/// Note about the security strength of the provided key:
/// If the provided key is tagged at a lower [SecurityStrength] than the instantiated MAC algorithm,
/// this will fail with an error:
/// ```text
/// MACError::KeyMaterialError(KeyMaterialError::SecurityStrength("HMAC::init(): provided key has a lower security strength than the instantiated HMAC")
/// ```
fn mac(self, data: &[u8]) -> Vec<u8>;

/// One-shot API that computes a MAC for the provided data and writes it into the provided output slice.
/// `data` can be of any length, including zero bytes.
///
Expand Down Expand Up @@ -321,8 +337,6 @@ pub trait MAC: Sized {
/// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times.
fn do_update(&mut self, data: &[u8]);

fn do_final(self) -> Vec<u8>;

/// Depending on the underlying MAC implementation, NIST may require that the library enforce
/// a minimum length on the mac output value. See documentation for the underlying implementation
/// to see conditions under which it throws [MACError::InvalidLength].
Expand All @@ -344,6 +358,42 @@ pub trait MAC: Sized {
fn max_security_strength(&self) -> SecurityStrength;
}

/// An extension to [MAC] for algorithms whose output length is known at compile time.
///
/// `OUTPUT_LEN` is the full (untruncated) MAC output length in bytes and must equal [MAC::output_len].
///
/// The provided methods return fixed-size arrays instead of heap allocations, so they are
/// usable in `no_std` environments and keep all intermediate state on the stack.
/// Runtime-dispatching wrappers (such as factory enums) whose output length varies by variant
/// cannot implement this trait and offer only the `*_out` functions of [MAC].
pub trait MACFixedOutput<const OUTPUT_LEN: usize>: MAC {
/// One-shot API that computes a MAC for the provided data.
/// `data` can be of any length, including zero bytes.
fn mac(self, data: &[u8]) -> [u8; OUTPUT_LEN] {
let mut output = [0u8; OUTPUT_LEN];
// Infallible: OUTPUT_LEN is the full MAC output length, so the minimum-length
// check on truncated MAC values in mac_out() cannot fail.
let written = self
.mac_out(data, &mut output)
.expect("MACFixedOutput::mac(): full-length output buffer was rejected");
debug_assert_eq!(written, OUTPUT_LEN);
output
}

/// Finish absorbing input and produce the MAC value.
/// Consumes self, so this must be the final call to this object.
fn do_final(self) -> [u8; OUTPUT_LEN] {
let mut output = [0u8; OUTPUT_LEN];
// Infallible: OUTPUT_LEN is the full MAC output length, so the minimum-length
// check on truncated MAC values in do_final_out() cannot fail.
let written = self
.do_final_out(&mut output)
.expect("MACFixedOutput::do_final(): full-length output buffer was rejected");
debug_assert_eq!(written, OUTPUT_LEN);
output
}
}

#[derive(Eq, PartialEq, PartialOrd, Clone, Debug)]
pub enum SecurityStrength {
None,
Expand Down
Loading