Skip to content

Commit a3984e7

Browse files
committed
Add support for ML-KEM.
See sfackler#2393.
1 parent d26c91b commit a3984e7

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

openssl/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ fn main() {
4646
println!("cargo:rustc-check-cfg=cfg(ossl310)");
4747
println!("cargo:rustc-check-cfg=cfg(ossl320)");
4848
println!("cargo:rustc-check-cfg=cfg(ossl330)");
49+
println!("cargo:rustc-check-cfg=cfg(ossl350)");
4950

5051
if env::var("DEP_OPENSSL_LIBRESSL").is_ok() {
5152
println!("cargo:rustc-cfg=libressl");
@@ -169,5 +170,8 @@ fn main() {
169170
if version >= 0x3_03_00_00_0 {
170171
println!("cargo:rustc-cfg=ossl330");
171172
}
173+
if version >= 0x3_05_00_00_0 {
174+
println!("cargo:rustc-cfg=ossl350");
175+
}
172176
}
173177
}

openssl/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ pub mod pkcs5;
185185
pub mod pkcs7;
186186
pub mod pkey;
187187
pub mod pkey_ctx;
188+
#[cfg(ossl350)]
189+
pub mod pkey_ml_kem;
188190
#[cfg(ossl300)]
189191
pub mod provider;
190192
pub mod rand;

openssl/src/pkey_ml_kem.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//! Module-Lattice-Based Key-Encapsulation Mechanism.
2+
//!
3+
//! ML-KEM is a Key-Encapsulation Mechanism that is believed to be
4+
//! secure against adversaries with quantum computers. It has been
5+
//! standardized by NIST as [FIPS 203].
6+
//!
7+
//! [FIPS 203]: https://csrc.nist.gov/pubs/fips/203/final
8+
9+
use foreign_types::ForeignType;
10+
use libc::c_int;
11+
use std::ptr;
12+
13+
use crate::error::ErrorStack;
14+
use crate::ossl_param::{OsslParam, OsslParamBuilder};
15+
use crate::pkey::{PKey, Private, Public};
16+
use crate::pkey_ctx::PkeyCtx;
17+
use crate::{cvt, cvt_p};
18+
use openssl_macros::corresponds;
19+
20+
const OSSL_PKEY_PARAM_SEED: &[u8; 5] = b"seed\0";
21+
const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0";
22+
const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0";
23+
24+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
25+
pub enum Variant {
26+
MlKem512,
27+
MlKem768,
28+
MlKem1024,
29+
}
30+
31+
impl Variant {
32+
pub(crate) fn as_str(&self) -> &'static str {
33+
match self {
34+
Variant::MlKem512 => "ML-KEM-512",
35+
Variant::MlKem768 => "ML-KEM-768",
36+
Variant::MlKem1024 => "ML-KEM-1024",
37+
}
38+
}
39+
}
40+
41+
pub struct PKeyMlKemBuilder<T> {
42+
bld: OsslParamBuilder,
43+
variant: Variant,
44+
_m: ::std::marker::PhantomData<T>,
45+
}
46+
47+
impl<T> PKeyMlKemBuilder<T> {
48+
/// Creates a new `PKeyMlKemBuilder` to build ML-KEM private or
49+
/// public keys.
50+
pub fn new(
51+
variant: Variant,
52+
public: &[u8],
53+
private: Option<&[u8]>,
54+
) -> Result<PKeyMlKemBuilder<T>, ErrorStack> {
55+
let bld = OsslParamBuilder::new()?;
56+
bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?;
57+
if let Some(private) = private {
58+
bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)?
59+
};
60+
Ok(PKeyMlKemBuilder::<T> {
61+
bld,
62+
variant,
63+
_m: ::std::marker::PhantomData,
64+
})
65+
}
66+
67+
/// Creates a new `PKeyMlKemBuilder` to build ML-KEM private keys
68+
/// from a seed.
69+
pub fn from_seed(
70+
variant: Variant,
71+
seed: &[u8],
72+
) -> Result<PKeyMlKemBuilder<T>, ErrorStack> {
73+
let bld = OsslParamBuilder::new()?;
74+
bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?;
75+
Ok(PKeyMlKemBuilder::<T> {
76+
bld,
77+
variant,
78+
_m: ::std::marker::PhantomData,
79+
})
80+
}
81+
82+
/// Build PKey. Internal.
83+
#[corresponds(EVP_PKEY_fromdata)]
84+
fn build_internal(self, selection: c_int) -> Result<PKey<T>, ErrorStack> {
85+
let mut ctx = PkeyCtx::new_from_name(
86+
None, self.variant.as_str(), None)?;
87+
ctx.fromdata_init()?;
88+
let params = self.bld.to_param()?;
89+
unsafe {
90+
let evp = cvt_p(ffi::EVP_PKEY_new())?;
91+
let pkey = PKey::from_ptr(evp);
92+
cvt(ffi::EVP_PKEY_fromdata(
93+
ctx.as_ptr(),
94+
&mut pkey.as_ptr(),
95+
selection,
96+
params.as_ptr(),
97+
))?;
98+
Ok(pkey)
99+
}
100+
}
101+
}
102+
103+
impl PKeyMlKemBuilder<Private> {
104+
/// Returns the Private ML-KEM PKey from the provided parameters.
105+
#[corresponds(EVP_PKEY_fromdata)]
106+
pub fn build(self) -> Result<PKey<Private>, ErrorStack> {
107+
self.build_internal(ffi::EVP_PKEY_KEYPAIR)
108+
}
109+
110+
/// Creates a new `PKeyRsaBuilder` to generate a new ML-KEM key
111+
/// pair.
112+
pub fn new_generate(variant: Variant)
113+
-> Result<PKeyMlKemBuilder<Private>, ErrorStack>
114+
{
115+
let bld = OsslParamBuilder::new()?;
116+
Ok(PKeyMlKemBuilder::<Private> {
117+
bld,
118+
variant,
119+
_m: ::std::marker::PhantomData,
120+
})
121+
}
122+
123+
/// Generate an ML-KEM PKey.
124+
pub fn generate(self) -> Result<PKey<Private>, ErrorStack> {
125+
let mut ctx = PkeyCtx::new_from_name(
126+
None, self.variant.as_str(), None)?;
127+
ctx.keygen_init()?;
128+
let params = self.bld.to_param()?;
129+
unsafe {
130+
cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?;
131+
}
132+
ctx.generate()
133+
}
134+
}
135+
136+
impl PKeyMlKemBuilder<Public> {
137+
/// Returns the Public ML-KEM PKey from the provided parameters.
138+
#[corresponds(EVP_PKEY_fromdata)]
139+
pub fn build(self) -> Result<PKey<Public>, ErrorStack> {
140+
self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY)
141+
}
142+
}
143+
144+
pub struct PKeyMlKemParams<T> {
145+
params: OsslParam,
146+
_m: ::std::marker::PhantomData<T>,
147+
}
148+
149+
impl<T> PKeyMlKemParams<T> {
150+
/// Creates a new `PKeyMlKemParams` from existing ML-KEM PKey. Internal.
151+
#[corresponds(EVP_PKEY_todata)]
152+
fn _new_from_pkey<S>(pkey: &PKey<S>, selection: c_int)
153+
-> Result<PKeyMlKemParams<T>, ErrorStack>
154+
{
155+
unsafe {
156+
let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut();
157+
cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?;
158+
Ok(PKeyMlKemParams::<T> {
159+
params: OsslParam::from_ptr(params),
160+
_m: ::std::marker::PhantomData,
161+
})
162+
}
163+
}
164+
165+
/// Returns a reference to the public key.
166+
pub fn public_key(&self) -> Result<&[u8], ErrorStack> {
167+
self.params
168+
.locate(OSSL_PKEY_PARAM_PUB_KEY)?
169+
.get_octet_string()
170+
}
171+
}
172+
173+
impl PKeyMlKemParams<Public> {
174+
/// Creates a new `PKeyMlKemParams` from existing Public ML-KEM PKey.
175+
#[corresponds(EVP_PKEY_todata)]
176+
pub fn from_pkey<S>(pkey: &PKey<S>) -> Result<PKeyMlKemParams<Public>, ErrorStack> {
177+
Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY)
178+
}
179+
}
180+
181+
impl PKeyMlKemParams<Private> {
182+
/// Creates a new `PKeyMlKemParams` from existing Private ML-KEM PKey.
183+
#[corresponds(EVP_PKEY_todata)]
184+
pub fn from_pkey(pkey: &PKey<Private>) -> Result<PKeyMlKemParams<Private>, ErrorStack> {
185+
Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR)
186+
}
187+
188+
/// Returns the private key seed.
189+
pub fn private_key_seed(&self) -> Result<&[u8], ErrorStack> {
190+
self.params.locate(OSSL_PKEY_PARAM_SEED)?.get_octet_string()
191+
}
192+
193+
/// Returns the private key.
194+
pub fn private_key(&self) -> Result<&[u8], ErrorStack> {
195+
self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string()
196+
}
197+
}
198+
199+
#[cfg(test)]
200+
mod tests {
201+
202+
use super::*;
203+
204+
#[test]
205+
fn test_generate_ml_kem_512() {
206+
test_generate(Variant::MlKem512);
207+
}
208+
209+
#[test]
210+
fn test_generate_ml_kem_768() {
211+
test_generate(Variant::MlKem768);
212+
}
213+
214+
#[test]
215+
fn test_generate_ml_kem_1024() {
216+
test_generate(Variant::MlKem1024);
217+
}
218+
219+
fn test_generate(variant: Variant) {
220+
let bld = PKeyMlKemBuilder::<Private>::new_generate(variant).unwrap();
221+
let key = bld.generate().unwrap();
222+
223+
// Encapsulate with the original PKEY.
224+
let (mut wrappedkey, mut genkey0) = (vec![], vec![]);
225+
let mut ctx = PkeyCtx::new(&key).unwrap();
226+
ctx.encapsulate_init().unwrap();
227+
ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap();
228+
229+
let mut genkey1 = vec![];
230+
let mut ctx = PkeyCtx::new(&key).unwrap();
231+
ctx.decapsulate_init().unwrap();
232+
ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap();
233+
234+
assert_eq!(genkey0, genkey1);
235+
236+
// Encapsulate with a PKEY derived from the public parameters.
237+
let public_params =
238+
PKeyMlKemParams::<Public>::from_pkey(&key).unwrap();
239+
let key_pub = PKeyMlKemBuilder::<Public>::new(
240+
variant, public_params.public_key().unwrap(), None).unwrap()
241+
.build().unwrap();
242+
243+
let (mut wrappedkey, mut genkey0) = (vec![], vec![]);
244+
let mut ctx = PkeyCtx::new(&key_pub).unwrap();
245+
ctx.encapsulate_init().unwrap();
246+
ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap();
247+
248+
let mut genkey1 = vec![];
249+
let mut ctx = PkeyCtx::new(&key).unwrap();
250+
ctx.decapsulate_init().unwrap();
251+
ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap();
252+
253+
assert_eq!(genkey0, genkey1);
254+
255+
// Note that we can get the public parameter from the
256+
// PKeyMlKemParams::<Private> as well. The same is not true
257+
// for ML-DSA, for example.
258+
let private_params =
259+
PKeyMlKemParams::<Private>::from_pkey(&key).unwrap();
260+
assert_eq!(public_params.public_key().unwrap(),
261+
private_params.public_key().unwrap());
262+
}
263+
}

0 commit comments

Comments
 (0)