rust/util: add unit tests for rust_hmac_sha256/rust_hmac_sha512
What changed, and why it matters
This commit adds unit tests for two cryptographic helper functions and slightly reorders their internal steps so the output buffer is only written after the HMAC calculation is complete. The change makes it safe for the output buffer to overlap with the input or key buffers, and documents that overlap is allowed. There is no new vulnerability here; it is a defensive hardening and testing improvement.
No action required. This is a benign hardening and test-coverage commit. Reviewers may verify the new overlap tests pass and that the reordering is consistent across all three functions.
Security signals we found
Defensive reordering of unsafe slice creation to avoid potential aliasing/overlap issues
Documentation update explicitly permitting output/input/key overlap
Addition of unit tests covering overlap scenarios
Evidence from the diff
The patch modifies rust/util/src/sha2.rs for rust_sha256, rust_hmac_sha256, and rust_hmac_sha512. Previously, rust_hmac_sha256/512 created the mutable output slice before reading key/data, which could be problematic if out overlapped with key/data. The patch computes the HMAC into a stack-allocated fixed-size array first, then writes to out only after the input slices are dropped. Safety comments are updated to state that out may overlap with data/key. Unit tests are added for HMAC-SHA256 and HMAC-SHA512, including overlap cases. A dev-dependency on hex_lit is added for test literals.
Changed components
src/rust/util/src/sha2.rsrust_hmac_sha256rust_hmac_sha512rust_sha256Inspect captured patch +195 / −21
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 68821cd..6f59fc8 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -1068,6 +1068,7 @@ dependencies = [
"cortex-m",
"critical-section",
"hex",
+ "hex_lit",
"num-bigint",
"p256",
"rtt-target",
diff --git a/src/rust/util/Cargo.toml b/src/rust/util/Cargo.toml
index 4441ee2..31b57f1 100644
--- a/src/rust/util/Cargo.toml
+++ b/src/rust/util/Cargo.toml
@@ -30,6 +30,9 @@ p256 = { version = "0.13.2", default-features = false, features = ["arithmetic",
bitcoin = {workspace = true}
critical-section = { version = "1.2.0", default-features = false, features = [] }
+[dev-dependencies]
+hex_lit = { workspace = true }
+
[features]
rtt = ["dep:rtt-target"]
testing = ["critical-section/std"]
diff --git a/src/rust/util/src/sha2.rs b/src/rust/util/src/sha2.rs
index 953b571..e7ac976 100644
--- a/src/rust/util/src/sha2.rs
+++ b/src/rust/util/src/sha2.rs
@@ -50,17 +50,23 @@ pub unsafe extern "C" fn rust_sha256_finish(ctx: *mut *mut c_void, out: *mut c_u
unsafe { *ctx = core::ptr::null_mut() };
}
-/// Safety: data must be a valid buffer for `len` bytes. `out` must be 32 bytes long.
+/// Safety: data must be valid buffer for `len` bytes. `out` must be 32 bytes long.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_sha256(data: *const c_void, len: usize, out: *mut c_uchar) {
+ let hash = {
+ let data = unsafe { core::slice::from_raw_parts(data as *const u8, len) };
+ Sha256::digest(data)
+ };
+
let out = unsafe { core::slice::from_raw_parts_mut(out, 32) };
- let data = unsafe { core::slice::from_raw_parts(data as *const u8, len) };
- let hash = Sha256::digest(data);
out.copy_from_slice(&hash[..]);
}
-/// Safety: `key` and `data` must be a valid buffers of the corresponding sizes. `out` must be 32
+/// Safety: `key` and `data` must be valid buffers of the corresponding sizes. `out` must be 32
/// bytes long.
+///
+/// `out` may overlap with `data` (and/or `key`). This is supported safely: the HMAC is computed
+/// first and only then written to `out`.
#[cfg(feature = "firmware")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_hmac_sha256(
@@ -70,19 +76,27 @@ pub unsafe extern "C" fn rust_hmac_sha256(
data_len: usize,
out: *mut c_uchar,
) {
- let out = unsafe { core::slice::from_raw_parts_mut(out, 32) };
- let key = unsafe { core::slice::from_raw_parts(key as *const u8, key_len) };
- let data = unsafe { core::slice::from_raw_parts(data as *const u8, data_len) };
-
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
- let mut engine = HmacEngine::<sha256::Hash>::new(key);
- engine.input(data);
- let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- out.as_mut().copy_from_slice(hmac_result.as_byte_array());
+
+ let result: [u8; 32] = {
+ let key = unsafe { core::slice::from_raw_parts(key as *const u8, key_len) };
+ let data = unsafe { core::slice::from_raw_parts(data as *const u8, data_len) };
+
+ let mut engine = HmacEngine::<sha256::Hash>::new(key);
+ engine.input(data);
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ hmac_result.to_byte_array()
+ };
+
+ let out = unsafe { core::slice::from_raw_parts_mut(out, 32) };
+ out.copy_from_slice(&result);
}
/// Safety: `key` and `data` must be a valid buffers of the corresponding sizes. `out` must be 64
/// bytes long.
+///
+/// `out` may overlap with `data` (and/or `key`). This is supported safely: the HMAC is computed
+/// first and only then written to `out`.
#[cfg(feature = "firmware")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_hmac_sha512(
@@ -92,20 +106,26 @@ pub unsafe extern "C" fn rust_hmac_sha512(
data_len: usize,
out: *mut c_uchar,
) {
- let out = unsafe { core::slice::from_raw_parts_mut(out, 64) };
- let key = unsafe { core::slice::from_raw_parts(key as *const u8, key_len) };
- let data = unsafe { core::slice::from_raw_parts(data as *const u8, data_len) };
-
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha512};
- let mut engine = HmacEngine::<sha512::Hash>::new(key);
- engine.input(data);
- let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(engine);
- out.as_mut().copy_from_slice(hmac_result.as_byte_array());
+
+ let result: [u8; 64] = {
+ let key = unsafe { core::slice::from_raw_parts(key as *const u8, key_len) };
+ let data = unsafe { core::slice::from_raw_parts(data as *const u8, data_len) };
+
+ let mut engine = HmacEngine::<sha512::Hash>::new(key);
+ engine.input(data);
+ let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(engine);
+ hmac_result.to_byte_array()
+ };
+
+ let out = unsafe { core::slice::from_raw_parts_mut(out, 64) };
+ out.copy_from_slice(&result);
}
#[cfg(test)]
mod tests {
use super::*;
+ use hex_lit::hex;
use std::prelude::v1::*;
#[test]
@@ -144,7 +164,7 @@ mod tests {
/// Test that input and output can be the same buffer.
#[test]
- fn test_overlapping() {
+ fn test_sha256_overlapping() {
let mut input_and_output = *b"12345678901234567890123456789012";
unsafe {
rust_sha256(
@@ -158,4 +178,154 @@ mod tests {
&Sha256::digest(b"12345678901234567890123456789012")[..],
);
}
+
+ #[test]
+ fn test_hmac_sha256() {
+ let key = [0x0b_u8; 20];
+ let data = b"Hi There";
+ let mut out = [0u8; 32];
+ unsafe {
+ rust_hmac_sha256(
+ key.as_ptr() as *const _,
+ key.len(),
+ data.as_ptr() as *const _,
+ data.len(),
+ out.as_mut_ptr(),
+ );
+ }
+ let expected = hex!("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
+ assert_eq!(out, expected);
+ }
+
+ #[test]
+ fn test_hmac_sha256_overlapping() {
+ let key = [0x0b_u8; 20];
+ let data = b"Hi There";
+ let expected = hex!("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
+
+ // out == data
+ let mut input_and_output = [0u8; 32];
+ input_and_output[..data.len()].copy_from_slice(data);
+ unsafe {
+ rust_hmac_sha256(
+ key.as_ptr() as *const _,
+ key.len(),
+ input_and_output.as_ptr() as *const _,
+ data.len(),
+ input_and_output.as_mut_ptr(),
+ );
+ }
+ assert_eq!(input_and_output, expected);
+
+ // out overlaps with data, but is not the same start address.
+ let mut buf = [0u8; 64];
+ buf[1..1 + data.len()].copy_from_slice(data);
+ unsafe {
+ rust_hmac_sha256(
+ key.as_ptr() as *const _,
+ key.len(),
+ buf[1..].as_ptr() as *const _,
+ data.len(),
+ buf.as_mut_ptr(),
+ );
+ }
+ assert_eq!(&buf[..32], &expected);
+ }
+
+ #[test]
+ fn test_hmac_sha256_overlapping_key() {
+ let mut key = [0x0b_u8; 32];
+ let data = b"Hi There";
+ let expected = hex!("198a607eb44bfbc69903a0f1cf2bbdc5ba0aa3f3d9ae3c1c7a3b1696a0b68cf7");
+
+ // out == key
+ unsafe {
+ rust_hmac_sha256(
+ key.as_ptr() as *const _,
+ key.len(),
+ data.as_ptr() as *const _,
+ data.len(),
+ key.as_mut_ptr(),
+ );
+ }
+ assert_eq!(key, expected);
+ }
+
+ #[test]
+ fn test_hmac_sha512() {
+ let key = [0x0b_u8; 20];
+ let data = b"Hi There";
+ let mut out = [0u8; 64];
+ unsafe {
+ rust_hmac_sha512(
+ key.as_ptr() as *const _,
+ key.len(),
+ data.as_ptr() as *const _,
+ data.len(),
+ out.as_mut_ptr(),
+ );
+ }
+ let expected = hex!(
+ "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
+ );
+ assert_eq!(out, expected);
+ }
+
+ #[test]
+ fn test_hmac_sha512_overlapping() {
+ let key = [0x0b_u8; 20];
+ let data = b"Hi There";
+ let expected = hex!(
+ "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
+ );
+
+ // out == data
+ let mut input_and_output = [0u8; 64];
+ input_and_output[..data.len()].copy_from_slice(data);
+ unsafe {
+ rust_hmac_sha512(
+ key.as_ptr() as *const _,
+ key.len(),
+ input_and_output.as_ptr() as *const _,
+ data.len(),
+ input_and_output.as_mut_ptr(),
+ );
+ }
+ assert_eq!(input_and_output, expected);
+
+ // out overlaps with data, but is not the same start address.
+ let mut buf = [0u8; 96];
+ buf[1..1 + data.len()].copy_from_slice(data);
+ unsafe {
+ rust_hmac_sha512(
+ key.as_ptr() as *const _,
+ key.len(),
+ buf[1..].as_ptr() as *const _,
+ data.len(),
+ buf.as_mut_ptr(),
+ );
+ }
+ assert_eq!(&buf[..64], &expected);
+ }
+
+ #[test]
+ fn test_hmac_sha512_overlapping_key() {
+ let mut key = [0x0b_u8; 64];
+ let data = b"Hi There";
+ let expected = hex!(
+ "637edc6e01dce7e6742a99451aae82df23da3e92439e590e43e761b33e910fb8ac2878ebd5803f6f0b61dbce5e251ff8789a4722c1be65aea45fd464e89f8f5b"
+ );
+
+ // out == key
+ unsafe {
+ rust_hmac_sha512(
+ key.as_ptr() as *const _,
+ key.len(),
+ data.as_ptr() as *const _,
+ data.len(),
+ key.as_mut_ptr(),
+ );
+ }
+ assert_eq!(key, expected);
+ }
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.