mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 10:50:53 +02:00
Rust wrapper: implement kem traits
This commit is contained in:
+13
@@ -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,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);
|
||||
}
|
||||
Reference in New Issue
Block a user