diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index ea88736bdf..12bfd102df 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -22,6 +22,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bindgen" version = "0.72.1" @@ -105,6 +117,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "crypto-common" version = "0.1.7" @@ -123,6 +141,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", + "rand_core 0.10.0", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", ] [[package]] @@ -134,6 +162,7 @@ dependencies = [ "blobby 0.4.0", "block-buffer", "crypto-common 0.2.1", + "ctutils", ] [[package]] @@ -185,6 +214,16 @@ dependencies = [ "either", ] +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.1", + "rand_core 0.10.0", +] + [[package]] name = "libc" version = "0.2.175" @@ -229,6 +268,34 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "password-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +dependencies = [ + "phc", +] + +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -424,6 +491,10 @@ dependencies = [ "bindgen", "cipher", "digest", + "hybrid-array", + "kem", + "num-traits", + "password-hash", "rand_core 0.10.0", "regex", "signature", diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index 9defa79fee..0e3ceed443 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -15,8 +15,11 @@ 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"] +kem = ["dep:kem", "hybrid-array/extra-sizes"] [dependencies] rand_core = { version = "0.10", optional = true, default-features = false } @@ -24,13 +27,19 @@ aead = { version = "0.5", optional = true, default-features = false } cipher = { version = "0.5", optional = true, default-features = false } digest = { version = "0.11", optional = true, default-features = false, features = ["block-api"] } signature = { version = "2.2", optional = true, default-features = false } +num-traits = { version = "0.2", default-features = false } zeroize = { version = "1.3", default-features = false, features = ["derive"] } +password-hash = { version = "0.6.1", optional = true, default-features = false } +kem = { version = "0.3", optional = true, default-features = false } +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" [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 51dc4c801e..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 +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/ecc.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs index 4fc2cd8921..d154654a96 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs @@ -1219,6 +1219,9 @@ impl ECC { /// } /// ``` pub fn rs_hex_to_sig(r: &[u8], s: &[u8], dout: &mut [u8]) -> Result { + if r.is_empty() || s.is_empty() || r[r.len() - 1] != 0 || s[s.len() - 1] != 0 { + return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG); + } let mut dout_size = crate::buffer_len_to_u32(dout.len())?; let r_ptr = r.as_ptr() as *const core::ffi::c_char; let s_ptr = s.as_ptr() as *const core::ffi::c_char; @@ -1820,7 +1823,7 @@ impl ECC { sys::wc_ecc_shared_secret(&mut self.wc_ecc_key, &mut peer_key.wc_ecc_key, dout.as_mut_ptr(), &mut out_len) }; - if rc < 0 { + if rc != 0 { return Err(rc); } Ok(out_len as usize) @@ -1961,7 +1964,7 @@ impl ECC { if rc != 0 { return Err(rc); } - Ok(res != 0) + Ok(res == 1) } } 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 ec817dcf4c..1c6a23dcdd 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,15 +57,21 @@ 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; +#[cfg(all(feature = "kem", mlkem))] +pub mod mlkem_kem; pub mod prf; pub mod random; pub mod rsa; #[cfg(feature = "signature")] pub mod rsa_pkcs1v15; pub mod sha; +#[cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))] +pub mod pbkdf2_password_hash; #[cfg(feature = "digest")] pub mod sha_digest; diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs index f76f82a7db..d3be50dc87 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lms.rs @@ -545,17 +545,17 @@ impl Lms { Ok(sig_sz as usize) } - /// Return the number of signatures remaining for this key. + /// Return whether there are more signatures remaining for this key. /// /// Returns `Ok(true)` if at least one signature remains, `Ok(false)` if /// exhausted, or `Err(e)` on error. This is a conservative check only. /// /// # Returns /// - /// Returns either Ok(count) on success or Err(e) containing the wolfSSL - /// library error code value. + /// Returns either Ok(true) if any signatures remain, Ok(false) if + /// exhausted, or Err(e) containing the wolfSSL library error code value. #[cfg(lms_make_key)] - pub fn sigs_left(&mut self) -> Result { + pub fn has_sigs_left(&mut self) -> Result { let rc = unsafe { sys::wc_LmsKey_SigsLeft(&mut self.ws_key) }; if rc < 0 { return Err(rc); diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs b/wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs index bedd6f2d8e..e63709dea5 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs @@ -637,8 +637,8 @@ impl MlKem { /// /// # Returns /// - /// Returns either Ok(size) containing the number of bytes written or Err(e) - /// containing the wolfSSL library error code value. + /// Returns either Ok(()) or Err(e) containing the wolfSSL library error + /// code value. /// /// # Example /// @@ -652,12 +652,10 @@ impl MlKem { /// .expect("Error with generate()"); /// let pub_size = key.public_key_size().unwrap(); /// let mut pub_buf = vec![0u8; pub_size]; - /// let written = key.encode_public_key(&mut pub_buf) - /// .expect("Error with encode_public_key()"); - /// assert_eq!(written, pub_size); + /// key.encode_public_key(&mut pub_buf).expect("Error with encode_public_key()"); /// } /// ``` - pub fn encode_public_key(&self, out: &mut [u8]) -> Result { + pub fn encode_public_key(&self, out: &mut [u8]) -> Result<(), i32> { let out_size = crate::buffer_len_to_u32(out.len())?; let rc = unsafe { sys::wc_MlKemKey_EncodePublicKey(self.ws_key, out.as_mut_ptr(), out_size) @@ -665,7 +663,7 @@ impl MlKem { if rc != 0 { return Err(rc); } - Ok(out.len()) + Ok(()) } /// Encode (export) the private key to a byte buffer. @@ -678,8 +676,8 @@ impl MlKem { /// /// # Returns /// - /// Returns either Ok(size) containing the number of bytes written or Err(e) - /// containing the wolfSSL library error code value. + /// Returns either Ok(()) or Err(e) containing the wolfSSL library error + /// code value. /// /// # Example /// @@ -693,12 +691,10 @@ impl MlKem { /// .expect("Error with generate()"); /// let priv_size = key.private_key_size().unwrap(); /// let mut priv_buf = vec![0u8; priv_size]; - /// let written = key.encode_private_key(&mut priv_buf) - /// .expect("Error with encode_private_key()"); - /// assert_eq!(written, priv_size); + /// key.encode_private_key(&mut priv_buf).expect("Error with encode_private_key()"); /// } /// ``` - pub fn encode_private_key(&self, out: &mut [u8]) -> Result { + pub fn encode_private_key(&self, out: &mut [u8]) -> Result<(), i32> { let out_size = crate::buffer_len_to_u32(out.len())?; let rc = unsafe { sys::wc_MlKemKey_EncodePrivateKey(self.ws_key, out.as_mut_ptr(), out_size) @@ -706,7 +702,7 @@ impl MlKem { if rc != 0 { return Err(rc); } - Ok(out.len()) + Ok(()) } /// Decode (import) a public key from a byte buffer. diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/mlkem_kem.rs b/wrapper/rust/wolfssl-wolfcrypt/src/mlkem_kem.rs new file mode 100644 index 0000000000..177c13558f --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/mlkem_kem.rs @@ -0,0 +1,249 @@ +/* + * 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 `kem` trait implementations for the wolfCrypt ML-KEM types. + +Provides [`kem::Kem`] marker types and associated encapsulation/decapsulation +key types for ML-KEM-512, ML-KEM-768, and ML-KEM-1024: + +| Marker | Encapsulation key | Decapsulation key | +|-----------------|---------------------------------|---------------------------------| +| [`MlKem512`] | [`MlKem512EncapsulationKey`] | [`MlKem512DecapsulationKey`] | +| [`MlKem768`] | [`MlKem768EncapsulationKey`] | [`MlKem768DecapsulationKey`] | +| [`MlKem1024`] | [`MlKem1024EncapsulationKey`] | [`MlKem1024DecapsulationKey`] | + +Each encapsulation key implements [`kem::Encapsulate`] (with +[`kem::TryKeyInit`] and [`kem::KeyExport`] for key serialization). + +Each decapsulation key implements [`kem::Decapsulate`] and +[`kem::Generate`] (for key generation from a [`rand_core::CryptoRng`]). + +Key generation and encapsulation bridge a caller-supplied +[`rand_core::CryptoRng`] to wolfCrypt's deterministic APIs by extracting the +required random bytes from the RNG. + +# Examples + +```rust +#[cfg(all(mlkem, random, feature = "kem", feature = "rand_core"))] +{ +use kem::{Kem, Encapsulate, Decapsulate}; +use kem::Generate; +use wolfssl_wolfcrypt::random::RNG; +use wolfssl_wolfcrypt::mlkem_kem::*; + +let mut rng = RNG::new().expect("RNG creation failed"); + +let (dk, ek) = MlKem768::generate_keypair_from_rng(&mut rng); +let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); +let k_recv = dk.decapsulate(&ct); +assert_eq!(k_send, k_recv); +} +``` +*/ + +#![cfg(all(feature = "kem", mlkem))] + +use kem::common::array::Array; +use kem::common::typenum::{U32, U768, U800}; +use hybrid_array::sizes::{U1088, U1184, U1568, U1632, U2400, U3168}; + +macro_rules! impl_mlkem_kem { + ( + kem = $kem:ident, + ek = $ek:ident, + dk = $dk:ident, + pk_typenum = $pk_tn:ty, + sk_typenum = $sk_tn:ty, + ct_typenum = $ct_tn:ty, + pk_len = $pk_len:expr, + sk_len = $sk_len:expr, + ct_len = $ct_len:expr, + key_type = $key_type:expr $(,)? + ) => { + /// ML-KEM parameter set marker implementing [`kem::Kem`]. + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct $kem; + + impl kem::Kem for $kem { + type DecapsulationKey = $dk; + type EncapsulationKey = $ek; + type SharedKeySize = U32; + type CiphertextSize = $ct_tn; + } + + /// ML-KEM encapsulation (public) key implementing [`kem::Encapsulate`]. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct $ek { + pk: Array, + } + + impl kem::KeySizeUser for $ek { + type KeySize = $pk_tn; + } + + impl kem::TryKeyInit for $ek { + fn new(key: &kem::Key) -> Result { + let mut wc_key = crate::mlkem::MlKem::new($key_type) + .map_err(|_| kem::InvalidKey)?; + wc_key.decode_public_key(key.as_ref()) + .map_err(|_| kem::InvalidKey)?; + Ok(Self { pk: key.clone() }) + } + } + + impl kem::KeyExport for $ek { + fn to_bytes(&self) -> kem::Key { + self.pk.clone() + } + } + + impl kem::Encapsulate for $ek { + type Kem = $kem; + + fn encapsulate_with_rng( + &self, + rng: &mut R, + ) -> (kem::Ciphertext<$kem>, kem::SharedKey<$kem>) { + let mut rand = [0u8; crate::mlkem::MlKem::ENC_RAND_SIZE]; + rng.fill_bytes(&mut rand); + + let mut wc_key = crate::mlkem::MlKem::new($key_type) + .expect("MlKem::new failed"); + wc_key.decode_public_key(self.pk.as_ref()) + .expect("decode_public_key failed"); + + let mut ct = [0u8; $ct_len]; + let mut ss = [0u8; crate::mlkem::MlKem::SHARED_SECRET_SIZE]; + wc_key.encapsulate_with_random(&mut ct, &mut ss, &rand) + .expect("encapsulate_with_random failed"); + + (ct.into(), ss.into()) + } + } + + /// ML-KEM decapsulation (private) key implementing [`kem::Decapsulate`]. + /// + /// The private key bytes are securely zeroized on drop. + pub struct $dk { + sk: Array, + ek: $ek, + } + + impl kem::Decapsulator for $dk { + type Kem = $kem; + + fn encapsulation_key(&self) -> &$ek { + &self.ek + } + } + + impl kem::Decapsulate for $dk { + fn decapsulate( + &self, + ct: &kem::Ciphertext<$kem>, + ) -> kem::SharedKey<$kem> { + let mut wc_key = crate::mlkem::MlKem::new($key_type) + .expect("MlKem::new failed"); + wc_key.decode_private_key(self.sk.as_ref()) + .expect("decode_private_key failed"); + + let mut ss = [0u8; crate::mlkem::MlKem::SHARED_SECRET_SIZE]; + wc_key.decapsulate(&mut ss, ct.as_ref()) + .expect("decapsulate failed"); + + ss.into() + } + } + + impl kem::Generate for $dk { + fn try_generate_from_rng( + rng: &mut R, + ) -> Result { + let mut rand = [0u8; crate::mlkem::MlKem::MAKEKEY_RAND_SIZE]; + rng.try_fill_bytes(&mut rand)?; + + let wc_key = crate::mlkem::MlKem::generate_with_random( + $key_type, &rand, + ).expect("generate_with_random failed"); + + let mut pk = [0u8; $pk_len]; + let mut sk = [0u8; $sk_len]; + wc_key.encode_public_key(&mut pk) + .expect("encode_public_key failed"); + wc_key.encode_private_key(&mut sk) + .expect("encode_private_key failed"); + + Ok(Self { + sk: sk.into(), + ek: $ek { pk: pk.into() }, + }) + } + } + + impl Drop for $dk { + fn drop(&mut self) { + use zeroize::Zeroize; + let sk_bytes: &mut [u8] = self.sk.as_mut(); + sk_bytes.zeroize(); + } + } + }; +} + +impl_mlkem_kem! { + kem = MlKem512, + ek = MlKem512EncapsulationKey, + dk = MlKem512DecapsulationKey, + pk_typenum = U800, + sk_typenum = U1632, + ct_typenum = U768, + pk_len = 800, + sk_len = 1632, + ct_len = 768, + key_type = crate::mlkem::MlKem::TYPE_512, +} + +impl_mlkem_kem! { + kem = MlKem768, + ek = MlKem768EncapsulationKey, + dk = MlKem768DecapsulationKey, + pk_typenum = U1184, + sk_typenum = U2400, + ct_typenum = U1088, + pk_len = 1184, + sk_len = 2400, + ct_len = 1088, + key_type = crate::mlkem::MlKem::TYPE_768, +} + +impl_mlkem_kem! { + kem = MlKem1024, + ek = MlKem1024EncapsulationKey, + dk = MlKem1024DecapsulationKey, + pk_typenum = U1568, + sk_typenum = U3168, + ct_typenum = U1568, + pk_len = 1568, + sk_len = 3168, + ct_len = 1568, + key_type = crate::mlkem::MlKem::TYPE_1024, +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/pbkdf2_password_hash.rs b/wrapper/rust/wolfssl-wolfcrypt/src/pbkdf2_password_hash.rs new file mode 100644 index 0000000000..42bbbcca3c --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/pbkdf2_password_hash.rs @@ -0,0 +1,246 @@ +/* + * 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 `password-hash` trait implementations for wolfCrypt PBKDF2. + +This module provides [`Pbkdf2`], a type that implements the +[`PasswordHasher`] and [`CustomizedPasswordHasher`] traits from the +`password-hash` crate, backed by the wolfCrypt PBKDF2 implementation. +The blanket [`PasswordVerifier`] implementation is also available, +allowing verification of existing password hashes. + +Password hashes are represented in the +[PHC string format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md): + +```text +$pbkdf2-sha256$i=600000$$ +``` + +# Supported algorithms + +| Algorithm ID | Hash function | +|-----------------|---------------| +| `pbkdf2-sha256` | HMAC-SHA-256 | +| `pbkdf2-sha384` | HMAC-SHA-384 | +| `pbkdf2-sha512` | HMAC-SHA-512 | + +[`PasswordHasher`]: password_hash::PasswordHasher +[`CustomizedPasswordHasher`]: password_hash::CustomizedPasswordHasher +[`PasswordVerifier`]: password_hash::PasswordVerifier +*/ + +#![cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))] + +use password_hash::phc::{Ident, Output, ParamsString, PasswordHash, Salt}; +use password_hash::{CustomizedPasswordHasher, Error, Result, Version}; + +use crate::hmac::HMAC; +use crate::kdf; + +const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha256"); +const PBKDF2_SHA384_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha384"); +const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512"); + +/// Minimum number of PBKDF2 rounds. +pub const MIN_ROUNDS: u32 = 1_000; + +/// Default number of PBKDF2 rounds (OWASP recommendation for SHA-256). +pub const DEFAULT_ROUNDS: u32 = 600_000; + +/// Default output length in bytes. +pub const DEFAULT_OUTPUT_LEN: usize = 32; + +/// PBKDF2 algorithm variant. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub enum Algorithm { + /// PBKDF2 with HMAC-SHA-256. + #[default] + Pbkdf2Sha256, + /// PBKDF2 with HMAC-SHA-384. + Pbkdf2Sha384, + /// PBKDF2 with HMAC-SHA-512. + Pbkdf2Sha512, +} + +impl Algorithm { + /// Get the PHC string format identifier for this algorithm. + pub fn ident(self) -> Ident { + match self { + Algorithm::Pbkdf2Sha256 => PBKDF2_SHA256_IDENT, + Algorithm::Pbkdf2Sha384 => PBKDF2_SHA384_IDENT, + Algorithm::Pbkdf2Sha512 => PBKDF2_SHA512_IDENT, + } + } + + fn hmac_type(self) -> i32 { + match self { + Algorithm::Pbkdf2Sha256 => HMAC::TYPE_SHA256, + Algorithm::Pbkdf2Sha384 => HMAC::TYPE_SHA384, + Algorithm::Pbkdf2Sha512 => HMAC::TYPE_SHA512, + } + } +} + +impl TryFrom for Algorithm { + type Error = Error; + + fn try_from(ident: Ident) -> Result { + if ident == PBKDF2_SHA256_IDENT { + Ok(Algorithm::Pbkdf2Sha256) + } else if ident == PBKDF2_SHA384_IDENT { + Ok(Algorithm::Pbkdf2Sha384) + } else if ident == PBKDF2_SHA512_IDENT { + Ok(Algorithm::Pbkdf2Sha512) + } else { + Err(Error::Algorithm) + } + } +} + +/// PBKDF2 parameters. +#[derive(Clone, Debug)] +pub struct Params { + /// Number of iterations (rounds). + pub rounds: u32, + /// Desired output hash length in bytes. + pub output_len: usize, +} + +impl Default for Params { + fn default() -> Self { + Params { + rounds: DEFAULT_ROUNDS, + output_len: DEFAULT_OUTPUT_LEN, + } + } +} + +impl TryFrom<&PasswordHash> for Params { + type Error = Error; + + fn try_from(hash: &PasswordHash) -> Result { + let rounds = hash + .params + .get_decimal("i") + .ok_or(Error::ParamInvalid { name: "i" })?; + + if rounds < MIN_ROUNDS { + return Err(Error::ParamInvalid { name: "i" }); + } + + let output_len = if let Some(ref h) = hash.hash { + h.len() + } else if let Some(l) = hash.params.get_decimal("l") && + 0 < l && (l as usize) <= Output::MAX_LENGTH { + l as usize + } else { + return Err(Error::ParamInvalid { name: "l" }); + }; + + Ok(Params { rounds, output_len }) + } +} + +/// PBKDF2 password hasher backed by wolfCrypt. +/// +/// Implements the [`PasswordHasher`](password_hash::PasswordHasher) and +/// [`CustomizedPasswordHasher`] traits. A blanket +/// [`PasswordVerifier`](password_hash::PasswordVerifier) implementation is +/// provided by the `password-hash` crate. +/// +/// # Example +/// +/// ```rust +/// #[cfg(all(hmac, kdf_pbkdf2))] +/// { +/// use password_hash::PasswordHasher; +/// use wolfssl_wolfcrypt::pbkdf2_password_hash::Pbkdf2; +/// +/// let hasher = Pbkdf2::default(); +/// let salt = b"0123456789abcdef"; // 16 bytes +/// let hash = hasher.hash_password_with_salt(b"password", salt) +/// .expect("hashing failed"); +/// } +/// ``` +#[derive(Clone, Debug, Default)] +pub struct Pbkdf2 { + /// Algorithm to use for hashing. + pub algorithm: Algorithm, + /// Default parameters. + pub params: Params, +} + +impl password_hash::PasswordHasher for Pbkdf2 { + fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result { + self.hash_password_customized(password, salt, None, None, self.params.clone()) + } +} + +impl password_hash::CustomizedPasswordHasher for Pbkdf2 { + type Params = Params; + + fn hash_password_customized( + &self, + password: &[u8], + salt: &[u8], + algorithm: Option<&str>, + version: Option, + params: Params, + ) -> Result { + if version.is_some() { + return Err(Error::Version); + } + + let algorithm = match algorithm { + Some(s) => { + let ident = Ident::new(s).map_err(|_| Error::Algorithm)?; + Algorithm::try_from(ident)? + } + None => self.algorithm, + }; + + if params.rounds < MIN_ROUNDS || params.output_len > Output::MAX_LENGTH { + return Err(Error::ParamInvalid { name: "i" }); + } + + let iterations = i32::try_from(params.rounds) + .map_err(|_| Error::ParamInvalid { name: "i" })?; + + let salt = Salt::new(salt)?; + + let mut out_buf = [0u8; Output::MAX_LENGTH]; + let out_slice = &mut out_buf[..params.output_len]; + kdf::pbkdf2(password, salt.as_ref(), iterations, algorithm.hmac_type(), out_slice) + .map_err(|_| Error::Crypto)?; + let output = Output::new(out_slice)?; + + let mut phc_params = ParamsString::new(); + phc_params.add_decimal("i", params.rounds)?; + + Ok(PasswordHash { + algorithm: algorithm.ident(), + version: None, + params: phc_params, + salt: Some(salt), + hash: Some(output), + }) + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs index 5fb5429446..a5904daa40 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs @@ -46,6 +46,7 @@ rng.generate_block(&mut buffer).expect("Failed to generate a block"); use crate::sys; use core::mem::{size_of_val, MaybeUninit}; +use num_traits::PrimInt; /// A cryptographically secure random number generator based on the wolfSSL /// library. @@ -127,7 +128,7 @@ impl RNG { /// /// A Result which is Ok(RNG) on success or an Err containing the wolfSSL /// library return code on failure. - pub fn new_with_nonce(nonce: &mut [T]) -> Result { + pub fn new_with_nonce(nonce: &mut [T]) -> Result { RNG::new_with_nonce_ex(nonce, None, None) } @@ -146,7 +147,7 @@ impl RNG { /// /// A Result which is Ok(RNG) on success or an Err containing the wolfSSL /// library return code on failure. - pub fn new_with_nonce_ex(nonce: &mut [T], heap: Option<*mut core::ffi::c_void>, dev_id: Option) -> Result { + pub fn new_with_nonce_ex(nonce: &mut [T], heap: Option<*mut core::ffi::c_void>, dev_id: Option) -> Result { #[cfg(fips)] { let rc = unsafe { @@ -338,7 +339,7 @@ impl RNG { /// /// A `Result` which is `Ok(())` on success or an `Err` with the wolfssl /// library return code on failure. - pub fn generate_block(&mut self, buf: &mut [T]) -> Result<(), i32> { + pub fn generate_block(&mut self, buf: &mut [T]) -> Result<(), i32> { let ptr = buf.as_mut_ptr() as *mut u8; let size = crate::buffer_len_to_u32(size_of_val(buf))?; let rc = unsafe { sys::wc_RNG_GenerateBlock(&mut self.wc_rng, ptr, size) }; 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_ecc.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecc.rs index 82f5369fbe..30d2d5b3fc 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecc.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecc.rs @@ -11,6 +11,8 @@ use wolfssl_wolfcrypt::random::RNG; #[test] #[cfg(random)] fn test_ecc_generate() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let mut ecc = ECC::generate(32, &mut rng, None, None).expect("Error with generate()"); ecc.check().expect("Error with check()"); @@ -19,6 +21,8 @@ fn test_ecc_generate() { #[test] #[cfg(random)] fn test_ecc_generate_ex() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -30,6 +34,8 @@ fn test_ecc_generate_ex() { #[test] #[cfg(all(ecc_import, ecc_export, random))] fn test_ecc_import_x963() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -47,6 +53,8 @@ fn test_ecc_import_x963() { #[test] #[cfg(random)] fn test_ecc_generate_ex2() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -58,6 +66,8 @@ fn test_ecc_generate_ex2() { #[test] #[cfg(all(ecc_import, ecc_export, ecc_sign, ecc_verify, random))] fn test_ecc_import_export_sign_verify() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let key_path = "../../../certs/ecc-client-key.der"; let der: Vec = fs::read(key_path).expect("Error reading key file"); @@ -242,6 +252,8 @@ fn test_ecc_import_export_private_ex() { #[test] #[cfg(all(ecc_export, random))] fn test_ecc_export_public() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let mut ecc = ECC::generate(32, &mut rng, None, None).expect("Error with generate()"); let mut qx = [0u8; 32]; @@ -281,6 +293,8 @@ fn test_ecc_import_unsigned() { #[test] #[cfg(random)] fn test_ecc_make_pub() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let key_path = "../../../certs/ecc-client-key.der"; let der: Vec = fs::read(key_path).expect("Error reading key file"); @@ -294,6 +308,8 @@ fn test_ecc_make_pub() { #[test] #[cfg(all(ecc_export, random))] fn test_ecc_point() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -308,6 +324,8 @@ fn test_ecc_point() { #[test] #[cfg(all(all(ecc_import, ecc_export, random)))] fn test_ecc_point_import() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -323,6 +341,8 @@ fn test_ecc_point_import() { #[test] #[cfg(all(ecc_import, ecc_export, ecc_comp_key, random))] fn test_ecc_point_import_compressed() { + common::setup(); + let mut rng = RNG::new().expect("Failed to create RNG"); let curve_id = ECC::SECP256R1; let curve_size = ECC::get_curve_size_from_id(curve_id).expect("Error with get_curve_size_from_id()"); @@ -336,9 +356,34 @@ fn test_ecc_point_import_compressed() { #[test] #[cfg(ecc_import)] fn test_ecc_import() { + common::setup(); + let qx = b"7a4e287890a1a47ad3457e52f2f76a83ce46cbc947616d0cbaa82323818a793d\0"; let qy = b"eec4084f5b29ebf29c44cce3b3059610922f8b30ea6e8811742ac7238fe87308\0"; let d = b"8c14b793cb19137e323a6d2e2a870bca2e7a493ec1153b3a95feb8a4873f8d08\0"; ECC::import_raw(qx, qy, d, b"SECP256R1\0", None, None).expect("Error with import_raw()"); ECC::import_raw_ex(qx, qy, d, ECC::SECP256R1, None, None).expect("Error with import_raw_ex()"); } + +#[test] +fn test_ecc_rs_hex_to_sig_not_null_terminated() { + common::setup(); + + let r_hex = b"AABB\0"; + let s_hex = b"CCDD\0"; + let r_hex_no_nul = b"AABB"; + let s_hex_no_nul = b"CCDD"; + let mut sig_out = [0u8; 128]; + + // Both null-terminated should succeed + assert!(ECC::rs_hex_to_sig(r_hex, s_hex, &mut sig_out).is_ok()); + + // r not null-terminated should fail + assert!(ECC::rs_hex_to_sig(r_hex_no_nul, s_hex, &mut sig_out).is_err()); + + // s not null-terminated should fail + assert!(ECC::rs_hex_to_sig(r_hex, s_hex_no_nul, &mut sig_out).is_err()); + + // Both not null-terminated should fail + assert!(ECC::rs_hex_to_sig(r_hex_no_nul, s_hex_no_nul, &mut sig_out).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()); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs index 842c435669..ba82a174f8 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_lms.rs @@ -350,11 +350,11 @@ fn test_export_pub_from() { let _ = store; } -/// Verify that `sigs_left()` indicates signatures are available immediately +/// Verify that `has_sigs_left()` indicates signatures are available immediately /// after `make_key()`. #[test] #[cfg(all(lms_make_key, random))] -fn test_sigs_left_after_make_key() { +fn test_has_sigs_left_after_make_key() { common::setup(); let mut rng = RNG::new().expect("Error creating RNG"); let mut store = Box::new(KeyStore { buf: [0u8; 16384] }); @@ -365,8 +365,8 @@ fn test_sigs_left_after_make_key() { setup_callbacks(&mut key, ctx); key.make_key(&mut rng).expect("Error with make_key()"); - let remaining = key.sigs_left().expect("Error with sigs_left()"); - assert!(remaining, "sigs_left must be true immediately after make_key()"); + let remaining = key.has_sigs_left().expect("Error with has_sigs_left()"); + assert!(remaining, "has_sigs_left must be true immediately after make_key()"); let _ = store; } diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem.rs index ee25fd38d6..528356d635 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem.rs @@ -244,9 +244,7 @@ fn test_encode_decode_public_key() { let ss_size = key.shared_secret_size().expect("Error with shared_secret_size()"); let mut pub_buf = vec![0u8; pub_size]; - let written = key.encode_public_key(&mut pub_buf) - .expect("Error with encode_public_key()"); - assert_eq!(written, pub_size); + key.encode_public_key(&mut pub_buf).expect("Error with encode_public_key()"); // Re-import public key and encapsulate. let mut pub_key = MlKem::new(MlKem::TYPE_768).expect("Error with new()"); @@ -280,9 +278,7 @@ fn test_encode_decode_private_key() { let ss_size = key.shared_secret_size().expect("Error with shared_secret_size()"); let mut priv_buf = vec![0u8; priv_size]; - let written = key.encode_private_key(&mut priv_buf) - .expect("Error with encode_private_key()"); - assert_eq!(written, priv_size); + key.encode_private_key(&mut priv_buf).expect("Error with encode_private_key()"); // Encapsulate with the original key. let mut ct = vec![0u8; ct_size]; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem_kem.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem_kem.rs new file mode 100644 index 0000000000..6b15be1365 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_mlkem_kem.rs @@ -0,0 +1,167 @@ +/* + * 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 + */ + +#![cfg(all(mlkem, random, feature = "kem", feature = "rand_core"))] + +mod common; + +use kem::{Decapsulate, Decapsulator, Encapsulate, Kem, TryKeyInit, KeyExport}; +use kem::Generate; +use wolfssl_wolfcrypt::mlkem::MlKem; +use wolfssl_wolfcrypt::mlkem_kem::*; +use wolfssl_wolfcrypt::random::RNG; + +/// Verify that the compile-time sizes used by the kem types match the runtime +/// sizes reported by wolfCrypt. +#[test] +fn test_sizes_match_runtime() { + common::setup(); + + let key512 = MlKem::new(MlKem::TYPE_512).expect("new TYPE_512"); + assert_eq!(key512.public_key_size().unwrap(), 800); + assert_eq!(key512.private_key_size().unwrap(), 1632); + assert_eq!(key512.cipher_text_size().unwrap(), 768); + + let key768 = MlKem::new(MlKem::TYPE_768).expect("new TYPE_768"); + assert_eq!(key768.public_key_size().unwrap(), 1184); + assert_eq!(key768.private_key_size().unwrap(), 2400); + assert_eq!(key768.cipher_text_size().unwrap(), 1088); + + let key1024 = MlKem::new(MlKem::TYPE_1024).expect("new TYPE_1024"); + assert_eq!(key1024.public_key_size().unwrap(), 1568); + assert_eq!(key1024.private_key_size().unwrap(), 3168); + assert_eq!(key1024.cipher_text_size().unwrap(), 1568); +} + +/// Generate, encapsulate, and decapsulate with ML-KEM-512 via the kem traits. +#[test] +fn test_kem_512_round_trip() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let (dk, ek) = MlKem512::generate_keypair_from_rng(&mut rng); + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} + +/// Generate, encapsulate, and decapsulate with ML-KEM-768 via the kem traits. +#[test] +fn test_kem_768_round_trip() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let (dk, ek) = MlKem768::generate_keypair_from_rng(&mut rng); + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} + +/// Generate, encapsulate, and decapsulate with ML-KEM-1024 via the kem traits. +#[test] +fn test_kem_1024_round_trip() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let (dk, ek) = MlKem1024::generate_keypair_from_rng(&mut rng); + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} + +/// Verify that `Generate::generate_from_rng` produces a usable decapsulation +/// key and that the associated encapsulation key is consistent. +#[test] +fn test_generate_from_rng() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let dk = MlKem768DecapsulationKey::generate_from_rng(&mut rng); + let ek = dk.encapsulation_key(); + + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} + +/// Verify that a tampered ciphertext produces a different shared secret +/// (ML-KEM implicit rejection). +#[test] +fn test_implicit_rejection() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let (dk, ek) = MlKem768::generate_keypair_from_rng(&mut rng); + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + + let mut ct_tampered = ct.clone(); + ct_tampered[0] ^= 0xFF; + let k_tampered = dk.decapsulate(&ct_tampered); + + assert_eq!(k_send, dk.decapsulate(&ct)); + assert_ne!(k_send, k_tampered); +} + +/// Verify that `TryKeyInit` and `KeyExport` round-trip the encapsulation key. +#[test] +fn test_ek_export_import() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let (dk, ek) = MlKem768::generate_keypair_from_rng(&mut rng); + + // Export and re-import the encapsulation key. + let exported = ek.to_bytes(); + let ek2 = MlKem768EncapsulationKey::new(&exported) + .expect("TryKeyInit failed"); + assert_eq!(ek, ek2); + + // Encapsulate with the re-imported key; the original DK must decapsulate. + let (ct, k_send) = ek2.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} + +/// Verify that `TryKeyInit` doesn't panic on a zeroed key. +#[test] +fn test_ek_try_new_zeroed_key() { + common::setup(); + + // A zero-filled buffer of the correct size. Whether this succeeds or fails + // depends on wolfCrypt's decode_public_key validation. The key point is it + // shouldn't panic. + let zeroed = kem::Key::::default(); + let _ = MlKem768EncapsulationKey::new(&zeroed); +} + +/// Verify the `Decapsulator::encapsulation_key` method returns a key that +/// can be used for encapsulation. +#[test] +fn test_decapsulator_encapsulation_key() { + common::setup(); + let mut rng = RNG::new().expect("RNG creation failed"); + + let dk = MlKem512DecapsulationKey::generate_from_rng(&mut rng); + let ek = dk.encapsulation_key().clone(); + + let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_pbkdf2_password_hash.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_pbkdf2_password_hash.rs new file mode 100644 index 0000000000..7ca30ca259 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_pbkdf2_password_hash.rs @@ -0,0 +1,270 @@ +#![cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))] + +mod common; + +use password_hash::phc::PasswordHash; +use password_hash::{CustomizedPasswordHasher, PasswordHasher, PasswordVerifier}; +use wolfssl_wolfcrypt::pbkdf2_password_hash::*; + +#[test] +fn test_hash_and_verify() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha256, + params: Params { + rounds: 4096, + output_len: 32, + }, + }; + + let salt = b"0123456789abcdef"; // 16 bytes + let password = b"hunter2"; + + let hash = hasher + .hash_password_with_salt(password, salt) + .expect("hashing failed"); + + assert_eq!(hash.algorithm, Algorithm::Pbkdf2Sha256.ident()); + assert!(hash.salt.is_some()); + assert!(hash.hash.is_some()); + assert_eq!(hash.hash.as_ref().unwrap().len(), 32); + + // Verify correct password succeeds + hasher + .verify_password(password, &hash) + .expect("verification of correct password failed"); + + // Verify wrong password fails + let result = hasher.verify_password(b"wrong_password", &hash); + assert!(result.is_err()); +} + +#[test] +fn test_hash_roundtrip_phc_string() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha256, + params: Params { + rounds: 4096, + output_len: 32, + }, + }; + + let salt = b"0123456789abcdef"; + let password = b"password"; + + let hash = hasher + .hash_password_with_salt(password, salt) + .expect("hashing failed"); + + // Serialize to PHC string and parse back + let phc_string = hash.to_string(); + assert!(phc_string.starts_with("$pbkdf2-sha256$")); + + let parsed = PasswordHash::new(&phc_string).expect("parsing PHC string failed"); + + // Verify with the parsed hash + hasher + .verify_password(password, &parsed) + .expect("verification of parsed hash failed"); +} + +#[test] +fn test_default_params() { + common::setup(); + + let hasher = Pbkdf2::default(); + assert_eq!(hasher.algorithm, Algorithm::Pbkdf2Sha256); + assert_eq!(hasher.params.rounds, DEFAULT_ROUNDS); + assert_eq!(hasher.params.output_len, DEFAULT_OUTPUT_LEN); +} + +#[test] +#[cfg(sha384)] +fn test_sha384_algorithm() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha384, + params: Params { + rounds: 4096, + output_len: 48, + }, + }; + + let salt = b"0123456789abcdef"; + let password = b"password"; + + let hash = hasher + .hash_password_with_salt(password, salt) + .expect("hashing with SHA-384 failed"); + assert_eq!(hash.algorithm, Algorithm::Pbkdf2Sha384.ident()); + assert_eq!(hash.hash.as_ref().unwrap().len(), 48); + + hasher + .verify_password(password, &hash) + .expect("SHA-384 verification failed"); +} + +#[test] +#[cfg(sha512)] +fn test_sha512_algorithm() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha512, + params: Params { + rounds: 4096, + output_len: 64, + }, + }; + + let salt = b"0123456789abcdef"; + let password = b"password"; + + let hash = hasher + .hash_password_with_salt(password, salt) + .expect("hashing with SHA-512 failed"); + assert_eq!(hash.algorithm, Algorithm::Pbkdf2Sha512.ident()); + assert_eq!(hash.hash.as_ref().unwrap().len(), 64); + + hasher + .verify_password(password, &hash) + .expect("SHA-512 verification failed"); +} + +#[test] +fn test_customized_hash() { + common::setup(); + + let hasher = Pbkdf2::default(); + + let salt = b"0123456789abcdef"; + let password = b"password"; + let custom_params = Params { + rounds: 8192, + output_len: 48, + }; + + let hash = hasher + .hash_password_with_params(password, salt, custom_params) + .expect("customized hashing failed"); + + assert_eq!(hash.hash.as_ref().unwrap().len(), 48); + assert_eq!(hash.params.get_decimal("i"), Some(8192)); + + hasher + .verify_password(password, &hash) + .expect("customized hash verification failed"); +} + +#[test] +#[cfg(sha512)] +fn test_customized_hash_with_algorithm_override() { + common::setup(); + + let hasher = Pbkdf2::default(); + + let salt = b"0123456789abcdef"; + let password = b"password"; + let params = Params { + rounds: 4096, + output_len: 64, + }; + + let hash = hasher + .hash_password_customized(password, salt, Some("pbkdf2-sha512"), None, params) + .expect("algorithm override failed"); + + assert_eq!(hash.algorithm, Algorithm::Pbkdf2Sha512.ident()); + assert_eq!(hash.hash.as_ref().unwrap().len(), 64); + + // Verify with a Pbkdf2 instance using the matching algorithm + let verifier = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha512, + ..Pbkdf2::default() + }; + verifier + .verify_password(password, &hash) + .expect("verification with algorithm override failed"); +} + +#[test] +fn test_version_rejected() { + common::setup(); + + let hasher = Pbkdf2::default(); + let salt = b"0123456789abcdef"; + + let result = + hasher.hash_password_customized(b"password", salt, None, Some(1), Params::default()); + assert!(result.is_err()); +} + +#[test] +fn test_unknown_algorithm_rejected() { + common::setup(); + + let hasher = Pbkdf2::default(); + let salt = b"0123456789abcdef"; + + let result = hasher.hash_password_customized( + b"password", + salt, + Some("argon2id"), + None, + Params::default(), + ); + assert!(result.is_err()); +} + +#[test] +fn test_deterministic_output() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha256, + params: Params { + rounds: 4096, + output_len: 32, + }, + }; + + let salt = b"0123456789abcdef"; + let password = b"password"; + + let hash1 = hasher + .hash_password_with_salt(password, salt) + .expect("first hash failed"); + let hash2 = hasher + .hash_password_with_salt(password, salt) + .expect("second hash failed"); + + assert_eq!(hash1.hash, hash2.hash); +} + +#[test] +fn test_different_salts_produce_different_hashes() { + common::setup(); + + let hasher = Pbkdf2 { + algorithm: Algorithm::Pbkdf2Sha256, + params: Params { + rounds: 4096, + output_len: 32, + }, + }; + + let password = b"password"; + + let hash1 = hasher + .hash_password_with_salt(password, b"salt_aaaaaaaaaa01") + .expect("first hash failed"); + let hash2 = hasher + .hash_password_with_salt(password, b"salt_aaaaaaaaaa02") + .expect("second hash failed"); + + assert_ne!(hash1.hash, hash2.hash); +}