From 79358fea803a9d14a2b42b091aee75ad057834ef Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Fri, 24 Apr 2026 08:39:20 -0400 Subject: [PATCH] Rust wrapper: add mac feature and implement digest/mac traits --- wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 1 + wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 3 +- wrapper/rust/wolfssl-wolfcrypt/Makefile | 2 +- .../rust/wolfssl-wolfcrypt/src/cmac_mac.rs | 99 ++++++++++++ .../rust/wolfssl-wolfcrypt/src/hmac_mac.rs | 151 ++++++++++++++++++ wrapper/rust/wolfssl-wolfcrypt/src/lib.rs | 4 + .../wolfssl-wolfcrypt/tests/test_cmac_mac.rs | 71 ++++++++ .../wolfssl-wolfcrypt/tests/test_hmac_mac.rs | 53 ++++++ 8 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 wrapper/rust/wolfssl-wolfcrypt/src/cmac_mac.rs create mode 100644 wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs create mode 100644 wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac_mac.rs create mode 100644 wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index fc07fcd75f..88ea317f0b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -156,6 +156,7 @@ dependencies = [ "blobby 0.4.0", "block-buffer", "crypto-common 0.2.1", + "ctutils", ] [[package]] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index c8f67144d6..283d087d0b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -15,6 +15,7 @@ std = [] rand_core = ["dep:rand_core"] aead = ["dep:aead"] cipher = ["dep:cipher"] +mac = ["digest/mac"] digest = ["dep:digest"] signature = ["dep:signature"] password-hash = ["dep:password-hash", "password-hash/phc"] @@ -34,7 +35,7 @@ hybrid-array = { version = "0.4.7", optional = true, default-features = false } [dev-dependencies] aead = { version = "0.5", features = ["alloc", "dev"] } cipher = "0.5" -digest = { version = "0.11", features = ["dev"] } +digest = { version = "0.11", features = ["dev", "mac"] } signature = "2.2" password-hash = { version = "0.6.1", features = ["phc"] } kem = "0.3" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 1d58c4befc..f705c4f8c0 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,4 +1,4 @@ -FEATURES := rand_core,aead,cipher,digest,signature,password-hash,kem +FEATURES := rand_core,aead,cipher,digest,mac,signature,password-hash,kem CARGO_FEATURE_FLAGS := --features $(FEATURES) .PHONY: all diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/cmac_mac.rs b/wrapper/rust/wolfssl-wolfcrypt/src/cmac_mac.rs new file mode 100644 index 0000000000..4b732f6568 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/cmac_mac.rs @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +RustCrypto `digest::Mac` trait implementations for the wolfCrypt CMAC types. + +This module provides typed AES-CMAC wrappers with implementations of the +traits from the `digest` crate (`MacMarker`, `KeyInit`, `Update`, +`FixedOutput`) for each AES key size (128, 192, 256). With these +implementations the `digest::Mac` trait becomes available via its blanket +implementation, allowing these CMAC types to be used anywhere a RustCrypto +`Mac` is accepted. + +Any failure returned by the underlying wolfCrypt call in a trait method will +result in a panic, matching the infallible signatures required by the +RustCrypto traits. +*/ + +use digest::consts::{U16, U24, U32}; + +macro_rules! impl_cmac_mac { + ( + $(#[$attr:meta])* + $name:ident, key = $key_size:ty + ) => { + $(#[$attr])* + pub struct $name { + cmac: crate::cmac::CMAC, + } + + $(#[$attr])* + impl digest::MacMarker for $name {} + + $(#[$attr])* + impl digest::OutputSizeUser for $name { + type OutputSize = U16; + } + + $(#[$attr])* + impl digest::common::KeySizeUser for $name { + type KeySize = $key_size; + } + + $(#[$attr])* + impl digest::KeyInit for $name { + fn new(key: &digest::Key) -> Self { + Self { + cmac: crate::cmac::CMAC::new(key.as_slice()) + .expect("wolfCrypt CMAC init failed"), + } + } + } + + $(#[$attr])* + impl digest::Update for $name { + fn update(&mut self, data: &[u8]) { + crate::cmac::CMAC::update(&mut self.cmac, data) + .expect("wolfCrypt CMAC update failed"); + } + } + + $(#[$attr])* + impl digest::FixedOutput for $name { + fn finalize_into(self, out: &mut digest::Output) { + self.cmac.finalize(out.as_mut_slice()) + .expect("wolfCrypt CMAC finalize failed"); + } + } + }; +} + +impl_cmac_mac! { + CmacAes128, key = U16 +} + +impl_cmac_mac! { + CmacAes192, key = U24 +} + +impl_cmac_mac! { + CmacAes256, key = U32 +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs b/wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs new file mode 100644 index 0000000000..452debaa22 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +RustCrypto `digest::Mac` trait implementations for the wolfCrypt HMAC types. + +This module provides typed HMAC wrappers with implementations of the traits +from the `digest` crate (`MacMarker`, `KeyInit`, `Update`, `FixedOutput`) +for each supported hash algorithm. With these implementations the +`digest::Mac` trait becomes available via its blanket implementation, +allowing these HMAC types to be used anywhere a RustCrypto `Mac` is accepted. + +Any failure returned by the underlying wolfCrypt call in a trait method will +result in a panic, matching the infallible signatures required by the +RustCrypto traits. +*/ + +use digest::consts::{ + U20, U28, U32, U48, U64, U72, U104, U128, U136, U144, +}; + +macro_rules! impl_hmac_mac { + ( + $(#[$attr:meta])* + $name:ident, hmac_type = $hmac_type:expr, key = $key_size:ty, out = $out_size:ty + ) => { + $(#[$attr])* + pub struct $name { + hmac: crate::hmac::HMAC, + } + + $(#[$attr])* + impl digest::MacMarker for $name {} + + $(#[$attr])* + impl digest::OutputSizeUser for $name { + type OutputSize = $out_size; + } + + $(#[$attr])* + impl digest::common::KeySizeUser for $name { + type KeySize = $key_size; + } + + $(#[$attr])* + impl digest::KeyInit for $name { + fn new(key: &digest::Key) -> Self { + Self { + hmac: crate::hmac::HMAC::new($hmac_type, key.as_slice()) + .expect("wolfCrypt HMAC init failed"), + } + } + + fn new_from_slice(key: &[u8]) -> Result { + crate::hmac::HMAC::new($hmac_type, key) + .map(|hmac| Self { hmac }) + .map_err(|_| digest::InvalidLength) + } + } + + $(#[$attr])* + impl digest::Update for $name { + fn update(&mut self, data: &[u8]) { + crate::hmac::HMAC::update(&mut self.hmac, data) + .expect("wolfCrypt HMAC update failed"); + } + } + + $(#[$attr])* + impl digest::FixedOutput for $name { + fn finalize_into(mut self, out: &mut digest::Output) { + crate::hmac::HMAC::finalize(&mut self.hmac, out.as_mut_slice()) + .expect("wolfCrypt HMAC finalize failed"); + } + } + }; +} + +impl_hmac_mac! { + #[cfg(sha)] + HmacSha, hmac_type = crate::hmac::HMAC::TYPE_SHA, key = U64, out = U20 +} + +impl_hmac_mac! { + #[cfg(sha224)] + HmacSha224, hmac_type = crate::hmac::HMAC::TYPE_SHA224, key = U64, out = U28 +} + +impl_hmac_mac! { + #[cfg(sha256)] + HmacSha256, hmac_type = crate::hmac::HMAC::TYPE_SHA256, key = U64, out = U32 +} + +impl_hmac_mac! { + #[cfg(sha384)] + HmacSha384, hmac_type = crate::hmac::HMAC::TYPE_SHA384, key = U128, out = U48 +} + +impl_hmac_mac! { + #[cfg(sha512)] + HmacSha512, hmac_type = crate::hmac::HMAC::TYPE_SHA512, key = U128, out = U64 +} + +#[cfg(sha512_224)] +impl_hmac_mac! { + #[cfg(sha512_224)] + HmacSha512_224, hmac_type = crate::hmac::HMAC::TYPE_SHA512_224, key = U128, out = U28 +} + +#[cfg(sha512_256)] +impl_hmac_mac! { + #[cfg(sha512_256)] + HmacSha512_256, hmac_type = crate::hmac::HMAC::TYPE_SHA512_256, key = U128, out = U32 +} + +impl_hmac_mac! { + #[cfg(sha3)] + HmacSha3_224, hmac_type = crate::hmac::HMAC::TYPE_SHA3_224, key = U144, out = U28 +} + +impl_hmac_mac! { + #[cfg(sha3)] + HmacSha3_256, hmac_type = crate::hmac::HMAC::TYPE_SHA3_256, key = U136, out = U32 +} + +impl_hmac_mac! { + #[cfg(sha3)] + HmacSha3_384, hmac_type = crate::hmac::HMAC::TYPE_SHA3_384, key = U104, out = U48 +} + +impl_hmac_mac! { + #[cfg(sha3)] + HmacSha3_512, hmac_type = crate::hmac::HMAC::TYPE_SHA3_512, key = U72, out = U64 +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index 73d31fdb61..5404ee4d5e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -44,6 +44,8 @@ pub mod aes; pub mod blake2; pub mod chacha20_poly1305; pub mod cmac; +#[cfg(all(cmac, feature = "mac"))] +pub mod cmac_mac; pub mod curve25519; pub mod dh; pub mod dilithium; @@ -55,6 +57,8 @@ pub mod ed448; pub mod fips; pub mod hkdf; pub mod hmac; +#[cfg(all(hmac, feature = "mac"))] +pub mod hmac_mac; pub mod kdf; pub mod lms; pub mod mlkem; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac_mac.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac_mac.rs new file mode 100644 index 0000000000..4715814ab5 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_cmac_mac.rs @@ -0,0 +1,71 @@ +#![cfg(all(cmac, feature = "mac"))] + +use digest::{KeyInit, Mac}; +use wolfssl_wolfcrypt::cmac_mac::CmacAes128; + +#[test] +fn test_cmac_aes128_mac_trait() { + let key = [ + 0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + ]; + let message = [ + 0x6bu8, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + ]; + let expected = [ + 0x07u8, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, + 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c + ]; + + let mut mac = CmacAes128::new_from_slice(&key) + .expect("CMAC init failed"); + mac.update(&message); + mac.verify_slice(&expected).expect("CMAC verification failed"); +} + +#[test] +fn test_cmac_aes128_mac_finalize() { + let key = [ + 0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + ]; + let message = [ + 0x6bu8, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + ]; + let expected: &[u8] = &[ + 0x07u8, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, + 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c + ]; + + let mac = CmacAes128::new_from_slice(&key) + .expect("CMAC init failed") + .chain_update(&message); + let result = mac.finalize(); + assert_eq!(result.as_bytes().as_slice(), expected); +} + +#[test] +fn test_cmac_aes128_mac_verify_fail() { + let key = [ + 0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + ]; + let message = [ + 0x6bu8, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + ]; + let wrong_tag = [0u8; 16]; + + let mut mac = CmacAes128::new_from_slice(&key) + .expect("CMAC init failed"); + mac.update(&message); + assert!(mac.verify_slice(&wrong_tag).is_err()); +} + +#[test] +fn test_cmac_aes128_wrong_key_size() { + let bad_key = [0u8; 15]; // wrong size for AES-128 + assert!(CmacAes128::new_from_slice(&bad_key).is_err()); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs new file mode 100644 index 0000000000..6a7067b0ee --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs @@ -0,0 +1,53 @@ +#![cfg(all(hmac, sha256, feature = "mac"))] + +use digest::{KeyInit, Mac}; +use wolfssl_wolfcrypt::hmac_mac::HmacSha256; + +#[test] +fn test_hmac_sha256_mac_trait() { + let key = b"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + let input = b"Hi There"; + let expected = b"\xb0\x34\x4c\x61\xd8\xdb\x38\x53\x5c\xa8\xaf\xce\xaf\x0b\xf1\x2b\x88\x1d\xc2\x00\xc9\x83\x3d\xa7\x26\xe9\x37\x6c\x2e\x32\xcf\xf7"; + + let mut mac = HmacSha256::new_from_slice(key) + .expect("HMAC init failed"); + mac.update(input); + mac.verify_slice(expected).expect("HMAC verification failed"); +} + +#[test] +fn test_hmac_sha256_mac_chain() { + let key = b"Jefe"; + let input = b"what do ya want for nothing?"; + let expected = b"\x5b\xdc\xc1\x46\xbf\x60\x75\x4e\x6a\x04\x24\x26\x08\x95\x75\xc7\x5a\x00\x3f\x08\x9d\x27\x39\x83\x9d\xec\x58\xb9\x64\xec\x38\x43"; + + let mac = HmacSha256::new_from_slice(key) + .expect("HMAC init failed") + .chain_update(input); + mac.verify_slice(expected).expect("HMAC verification failed"); +} + +#[test] +fn test_hmac_sha256_mac_finalize() { + let key = b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"; + let input = b"\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"; + let expected: &[u8] = b"\x77\x3e\xa9\x1e\x36\x80\x0e\x46\x85\x4d\xb8\xeb\xd0\x91\x81\xa7\x29\x59\x09\x8b\x3e\xf8\xc1\x22\xd9\x63\x55\x14\xce\xd5\x65\xfe"; + + let mut mac = HmacSha256::new_from_slice(key) + .expect("HMAC init failed"); + mac.update(input); + let result = mac.finalize(); + assert_eq!(result.as_bytes().as_slice(), expected); +} + +#[test] +fn test_hmac_sha256_mac_verify_fail() { + let key = b"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + let input = b"Hi There"; + let wrong_tag = [0u8; 32]; + + let mut mac = HmacSha256::new_from_slice(key) + .expect("HMAC init failed"); + mac.update(input); + assert!(mac.verify_slice(&wrong_tag).is_err()); +}