Rust wrapper: implement kem traits

This commit is contained in:
Josh Holtrop
2026-04-23 10:49:44 -04:00
parent c08c16ee8f
commit 84f8b5fa13
6 changed files with 436 additions and 1 deletions
+13
View File
@@ -135,6 +135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
"rand_core 0.10.0",
]
[[package]]
@@ -206,6 +207,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"
@@ -464,6 +475,8 @@ dependencies = [
"bindgen",
"cipher",
"digest",
"hybrid-array",
"kem",
"password-hash",
"rand_core 0.10.0",
"regex",
@@ -18,6 +18,7 @@ cipher = ["dep:cipher"]
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 }
@@ -27,6 +28,8 @@ digest = { version = "0.11", optional = true, default-features = false, features
signature = { version = "2.2", optional = true, 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"] }
@@ -34,6 +37,7 @@ cipher = "0.5"
digest = { version = "0.11", features = ["dev"] }
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,password-hash
FEATURES := rand_core,aead,cipher,digest,signature,password-hash,kem
CARGO_FEATURE_FLAGS := --features $(FEATURES)
.PHONY: all
@@ -58,6 +58,8 @@ pub mod hmac;
pub mod kdf;
pub mod lms;
pub mod mlkem;
#[cfg(feature = "kem")]
pub mod mlkem_kem;
pub mod prf;
pub mod random;
pub mod rsa;
@@ -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,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);
}