Skip to content
Merged
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
4 changes: 2 additions & 2 deletions alpha_0.1.2_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
* Check out Megan's email May 13 about KeyMaterial: "I was wondering if there might be scope for a closure based
approach that could
guarantee encapsulation of the state change from safe to hazardous back to safe again."
* Anywhere that you have an `_out(.. out: &mut [u8])`, start by zeroizing it with .fill(0); .. a good task for Claude?
And should be documented in the style guide?
* Go back to previous algs and apply memory optimization tricks like internal functions. And add a docs section "Memory
Usage" that measures with valgrind.
* Ensure that all crates have `#![forbid(missing_docs)]`
Expand Down Expand Up @@ -54,5 +52,7 @@

* ML-DSA
* Low-Memory ML-DSA -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with only minor performance impact.
* 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:
* #2, or whatever
2 changes: 2 additions & 0 deletions crypto/base64/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ impl Base64Encoder {
assert!(inref.len() >= 3);
assert!(out.len() >= 4);

out.fill(0);

out[0] = Self::ct_bin_to_b64(inref[0] >> 2);
out[1] = Self::ct_bin_to_b64(((inref[0] & 0x03) << 4) | inref[1] >> 4);
out[2] = Self::ct_bin_to_b64(((inref[1] & 0x0F) << 2) | inref[2] >> 6);
Expand Down
20 changes: 20 additions & 0 deletions crypto/core/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub trait Hash : Default {

/// A static one-shot API that hashes the provided data into the provided output slice.
/// `data` can be of any length, including zero bytes.
/// The entire output buffer is zeroized before the hash output is written.
/// The return value is the number of bytes written.
fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize;

Expand All @@ -50,6 +51,8 @@ pub trait Hash : Default {
/// If the provided buffer is smaller than the hash's output length, the output will be truncated.
/// If the provided buffor is larger than the hash's output length, the output will be placed in
/// the first [Hash::output_len] bytes.
/// The entire output buffer is zeroized before the hash output is written, so any bytes past
/// [Hash::output_len] will be 0.
///
/// The return value is the number of bytes written.
fn do_final_out(self, output: &mut [u8]) -> usize;
Expand All @@ -65,6 +68,7 @@ pub trait Hash : Default {
/// 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.
/// The entire output buffer is zeroized before the hash output is written.
/// The return value is the number of bytes written.
fn do_final_partial_bits_out(
self,
Expand Down Expand Up @@ -208,6 +212,7 @@ pub trait KEMPublicKey<const PK_LEN: usize> : PartialEq + Eq + Clone + Debug + D
/// Write it out to bytes in its standard encoding.
fn encode(&self) -> [u8; PK_LEN];
/// Write it out to bytes in its standard encoding.
/// The entire output buffer is zeroized before the encoding is written.
fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize;
/// Read it in from bytes in its standard encoding.
fn from_bytes(bytes: &[u8]) -> Result<Self, KEMError>;
Expand All @@ -218,6 +223,7 @@ pub trait KEMPrivateKey<const SK_LEN: usize> : PartialEq + Eq + Clone + Secret +
/// Write it out to bytes in its standard encoding.
fn encode(&self) -> [u8; SK_LEN];
/// Write it out to bytes in its standard encoding.
/// The entire output buffer is zeroized before the encoding is written.
fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
/// Read it in from bytes in its standard encoding.
fn from_bytes(bytes: &[u8]) -> Result<Self, KEMError>;
Expand Down Expand Up @@ -293,6 +299,8 @@ pub trait MAC: Sized {
/// 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].
///
/// The entire output buffer is zeroized before the MAC value is written.
fn mac_out(self, data: &[u8],out: &mut [u8]) -> Result<usize, MACError>;

/// One-shot API that verifies a MAC for the provided data.
Expand All @@ -318,6 +326,8 @@ pub trait MAC: Sized {
/// 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].
///
/// The entire output buffer is zeroized before the MAC value is written.
fn do_final_out(self, out: &mut [u8]) -> Result<usize, MACError>;

/// Internally, this will re-compute the MAC value and then compare it to the provided mac value
Expand Down Expand Up @@ -392,6 +402,7 @@ pub trait RNG : Default {
fn next_bytes(&mut self, len: usize) -> Result<Vec<u8>, RNGError>;

/// Returns the number of bytes written.
/// The entire output buffer is zeroized before the random bytes are written.
fn next_bytes_out(&mut self, out: &mut [u8]) -> Result<usize, RNGError>;

fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result<usize, RNGError>;
Expand Down Expand Up @@ -443,6 +454,7 @@ pub trait PHSignature<
/// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way.
fn sign_ph(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>;
/// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
/// The entire output buffer is zeroized before the signature is written.
fn sign_ph_out(sk: &SK, ph: &[u8; PH_LEN], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError>;
/// On success, returns Ok(())
/// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs).
Expand Down Expand Up @@ -501,6 +513,7 @@ pub trait Signature<
fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError>;

/// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
/// The entire output buffer is zeroized before the signature is written.
fn sign_out(sk: &SK, msg: &[u8], ctx: Option<&[u8]>, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError>;

/* streaming signing API */
Expand All @@ -516,6 +529,7 @@ pub trait Signature<
fn sign_final(self) -> Result<[u8; SIG_LEN], SignatureError>;

/// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
/// The entire output buffer is zeroized before the signature is written.
fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result<usize, SignatureError>;

/// On success, returns Ok(())
Expand Down Expand Up @@ -543,6 +557,7 @@ pub trait SignaturePublicKey<const PK_LEN: usize> : PartialEq + Eq + Clone + Deb
/// Write it out to bytes in its standard encoding.
fn encode(&self) -> [u8; PK_LEN];
/// Write it out to bytes in its standard encoding.
/// The entire output buffer is zeroized before the encoding is written.
fn encode_out(&self, out: &mut [u8; PK_LEN]) -> usize;
/// Read it in from bytes in its standard encoding.
fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError>;
Expand All @@ -553,6 +568,7 @@ pub trait SignaturePrivateKey<const SK_LEN: usize> : PartialEq + Eq + Clone + Se
/// Write it out to bytes in its standard encoding.
fn encode(&self) -> [u8; SK_LEN];
/// Write it out to bytes in its standard encoding.
/// The entire output buffer is zeroized before the encoding is written.
fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
/// Read it in from bytes in its standard encoding.
fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError>;
Expand Down Expand Up @@ -583,6 +599,7 @@ pub trait XOF : Default {

/// A static one-shot API that digests the input data and produces `result_len` bytes of output.
/// Fills the provided output slice.
/// The entire output buffer is zeroized before the output is written.
fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize;

fn absorb(&mut self, data: &[u8]);
Expand All @@ -599,13 +616,16 @@ pub trait XOF : Default {

/// Can be called multiple times.
/// Fills the provided output slice.
/// The entire output buffer is zeroized before the output is written.
fn squeeze_out(&mut self, output: &mut [u8]) -> usize;

/// Squeezes a partial byte from the XOF.
/// Output will be in the top `num_bits` bits of the returned u8 (ie Big Endian).
/// This is a final call and consumes self.
fn squeeze_partial_byte_final(self, num_bits: usize) -> Result<u8, HashError>;

/// The same as [XOF::squeeze_partial_byte_final], but writes into the provided output byte.
/// The output byte is zeroized before the result is written.
fn squeeze_partial_byte_final_out(
self,
num_bits: usize,
Expand Down
4 changes: 4 additions & 0 deletions crypto/factory/src/hash_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ impl Hash for HashFactory {
}

fn hash_out(self, data: &[u8], output: &mut [u8]) -> usize {
output.fill(0);

match self {
Self::SHA224(h) => h.hash_out(data, output),
Self::SHA256(h) => h.hash_out(data, output),
Expand Down Expand Up @@ -168,6 +170,8 @@ impl Hash for HashFactory {
}

fn do_final_out(self, output: &mut [u8]) -> usize {
output.fill(0);

match self {
Self::SHA224(h) => h.do_final_out(output),
Self::SHA256(h) => h.do_final_out(output),
Expand Down
4 changes: 4 additions & 0 deletions crypto/factory/src/mac_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ impl MAC for MACFactory {
}

fn mac_out(self, data: &[u8], out: &mut [u8]) -> Result<usize, MACError> {
out.fill(0);

match self {
Self::HMAC_SHA224(h) => h.mac_out(data, out),
Self::HMAC_SHA256(h) => h.mac_out(data, out),
Expand Down Expand Up @@ -227,6 +229,8 @@ impl MAC for MACFactory {
}

fn do_final_out(self, mut out: &mut [u8]) -> Result<usize, MACError> {
out.fill(0);

match self {
Self::HMAC_SHA224(h) => h.do_final_out(&mut out),
Self::HMAC_SHA256(h) => h.do_final_out(&mut out),
Expand Down
2 changes: 2 additions & 0 deletions crypto/factory/src/rng_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ impl RNG for RNGFactory {
}

fn next_bytes_out(&mut self, out: &mut [u8]) -> Result<usize, RNGError> {
out.fill(0);

match self {
Self::HashDRBG_SHA256(rng) => {rng.next_bytes_out(out) },
Self::HashDRBG_SHA512(rng) => { rng.next_bytes_out(out) },
Expand Down
6 changes: 6 additions & 0 deletions crypto/factory/src/xof_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ impl XOF for XOFFactory {
}

fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize {
output.fill(0);

match self {
Self::SHAKE128(h) => h.hash_xof_out(data, output),
Self::SHAKE256(h) => h.hash_xof_out(data, output),
Expand Down Expand Up @@ -118,6 +120,8 @@ impl XOF for XOFFactory {
}

fn squeeze_out(&mut self, output: &mut [u8]) -> usize {
output.fill(0);

match self {
Self::SHAKE128(h) => h.squeeze_out(output),
Self::SHAKE256(h) => h.squeeze_out(output),
Expand All @@ -136,6 +140,8 @@ impl XOF for XOFFactory {
num_bits: usize,
output: &mut u8,
) -> Result<(), HashError> {
*output = 0;

match self {
Self::SHAKE128(h) => h.squeeze_partial_byte_final_out(num_bits, output),
Self::SHAKE256(h) => h.squeeze_partial_byte_final_out(num_bits, output),
Expand Down
4 changes: 4 additions & 0 deletions crypto/hex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub fn encode_out<T: AsRef<[u8]>>(input: T, out: &mut [u8]) -> Result<usize, Hex
return Err(HexError::InsufficientOutputBufferSize);
}

out.fill(0);

for i in 0..inref.len() {
out[2 * i] = ct_word_to_hex(inref[i] >> 4);
out[2 * i + 1] = ct_word_to_hex(inref[i] & 0x0F);
Expand Down Expand Up @@ -90,6 +92,8 @@ pub fn decode_out<T: AsRef<[u8]>>(input: T, out: &mut [u8]) -> Result<usize, Hex
return Err(HexError::InsufficientOutputBufferSize);
}

out.fill(0);

let mut b = 0u8;
let mut b_i = 0u8;
let mut out_i = 0_usize;
Expand Down
6 changes: 6 additions & 0 deletions crypto/hmac/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ impl<HASH: Hash + Default> HMAC<HASH> {
));
}

out.fill(0);

// Per RFC 2104 Section 2, save our inner digest to calculate our
// outer digest. Note that we can't (necessarily) reuse out as a
// scratch pad here: if we're truncating the output but not
Expand Down Expand Up @@ -378,6 +380,8 @@ impl<HASH: Hash + Default> MAC for HMAC<HASH> {
}

fn mac_out(mut self, data: &[u8], mut out: &mut [u8]) -> Result<usize, MACError> {
out.fill(0);

self.do_update(data);
self.do_final_out(&mut out)
}
Expand All @@ -398,6 +402,8 @@ impl<HASH: Hash + Default> MAC for HMAC<HASH> {
}

fn do_final_out(self, mut out: &mut [u8]) -> Result<usize, MACError> {
out.fill(0);

self.do_final_internal_out(&mut out)
}

Expand Down
2 changes: 2 additions & 0 deletions crypto/mldsa/src/aux_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ pub(crate) fn sig_encode<
h: &Vector<k>,
output: &mut [u8; SIG_LEN],
) -> usize {
output.fill(0);

let mut pos = 0;

output[..LAMBDA_over_4].copy_from_slice(c_tilde);
Expand Down
12 changes: 12 additions & 0 deletions crypto/mldsa/src/hash_mldsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ impl<
ctx: Option<&[u8]>,
output: &mut [u8; SIG_LEN],
) -> Result<usize, SignatureError> {
output.fill(0);

let mut ph_m = [0u8; PH_LEN];
_ = HASH::default().hash_out(msg, &mut ph_m);
Self::sign_ph_with_expanded_key_out(sk, &ph_m, ctx, output)
Expand All @@ -500,6 +502,8 @@ impl<
ctx: Option<&[u8]>,
output: &mut [u8; SIG_LEN],
) -> Result<usize, SignatureError> {
output.fill(0);

let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
Self::sign_ph_deterministic_out(&sk.sk, Some(&sk.A_hat), ctx, ph, rnd, output)
Expand Down Expand Up @@ -556,6 +560,8 @@ impl<
return Err(SignatureError::LengthError("ctx value is longer than 255 bytes"));
}

output.fill(0);

// Algorithm 7
// 6: 𝜇 ← H(BytesToBits(𝑡𝑟)||𝑀', 64)
let mu = {
Expand Down Expand Up @@ -860,6 +866,8 @@ impl<
ctx: Option<&[u8]>,
output: &mut [u8; SIG_LEN],
) -> Result<usize, SignatureError> {
output.fill(0);

let mut ph_m = [0u8; PH_LEN];
_ = HASH::default().hash_out(msg, &mut ph_m);
Self::sign_ph_out(sk, &ph_m, ctx, output)
Expand Down Expand Up @@ -898,6 +906,8 @@ impl<
));
}

output.fill(0);

if self.sk.is_some() {
if self.signer_rnd.is_none() {
Self::sign_ph_out(&self.sk.unwrap(), &ph, Some(&self.ctx[..self.ctx_len]), output)
Expand Down Expand Up @@ -1045,6 +1055,8 @@ impl<
ctx: Option<&[u8]>,
output: &mut [u8; SIG_LEN],
) -> Result<usize, SignatureError> {
output.fill(0);

let mut rnd: [u8; MLDSA_RND_LEN] = [0u8; MLDSA_RND_LEN];
HashDRBG_SHA512::new_from_os().next_bytes_out(&mut rnd)?;
Self::sign_ph_deterministic_out(sk, None, ctx, ph, rnd, output)
Expand Down
Loading