Merge pull request #10305 from holtrop-wolfssl/rust-crate-updates

Rust wrapper: add password-hash, kem, mac traits; fix a few Fenrir findings
This commit is contained in:
David Garske
2026-05-05 12:05:29 -07:00
committed by GitHub
19 changed files with 1470 additions and 35 deletions
+71
View File
@@ -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",
+10 -1
View File
@@ -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"
+1 -1
View File
@@ -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
@@ -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 {
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>) {
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
}
+5 -2
View File
@@ -1219,6 +1219,9 @@ impl ECC {
/// }
/// ```
pub fn rs_hex_to_sig(r: &[u8], s: &[u8], dout: &mut [u8]) -> Result<usize, i32> {
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)
}
}
@@ -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 {
Self {
hmac: crate::hmac::HMAC::new($hmac_type, key.as_slice())
.expect("wolfCrypt HMAC init failed"),
}
}
fn new_from_slice(key: &[u8]) -> Result<Self, digest::InvalidLength> {
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<Self>) {
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
}
@@ -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;
+4 -4
View File
@@ -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<bool, i32> {
pub fn has_sigs_left(&mut self) -> Result<bool, i32> {
let rc = unsafe { sys::wc_LmsKey_SigsLeft(&mut self.ws_key) };
if rc < 0 {
return Err(rc);
+10 -14
View File
@@ -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<usize, i32> {
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<usize, i32> {
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.
@@ -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<u8, $pk_tn>,
}
impl kem::KeySizeUser for $ek {
type KeySize = $pk_tn;
}
impl kem::TryKeyInit for $ek {
fn new(key: &kem::Key<Self>) -> Result<Self, kem::InvalidKey> {
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> {
self.pk.clone()
}
}
impl kem::Encapsulate for $ek {
type Kem = $kem;
fn encapsulate_with_rng<R: kem::common::rand_core::CryptoRng + ?Sized>(
&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<u8, $sk_tn>,
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<R: kem::common::rand_core::TryCryptoRng + ?Sized>(
rng: &mut R,
) -> Result<Self, R::Error> {
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,
}
@@ -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$<salt>$<hash>
```
# 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<Ident> for Algorithm {
type Error = Error;
fn try_from(ident: Ident) -> Result<Self> {
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<Self> {
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<PasswordHash> for Pbkdf2 {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, self.params.clone())
}
}
impl password_hash::CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
algorithm: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
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),
})
}
}
+4 -3
View File
@@ -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<T>(nonce: &mut [T]) -> Result<Self, i32> {
pub fn new_with_nonce<T: PrimInt>(nonce: &mut [T]) -> Result<Self, i32> {
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<T>(nonce: &mut [T], heap: Option<*mut core::ffi::c_void>, dev_id: Option<i32>) -> Result<Self, i32> {
pub fn new_with_nonce_ex<T: PrimInt>(nonce: &mut [T], heap: Option<*mut core::ffi::c_void>, dev_id: Option<i32>) -> Result<Self, i32> {
#[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<T>(&mut self, buf: &mut [T]) -> Result<(), i32> {
pub fn generate_block<T: PrimInt>(&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) };
@@ -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());
}
@@ -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<u8> = 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<u8> = 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());
}
@@ -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());
}
@@ -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;
}
@@ -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];
@@ -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::<MlKem768EncapsulationKey>::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);
}
@@ -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);
}