1use foreign_types::ForeignType;
2use openssl::ec::{EcGroup, EcKey};
3use openssl::error::ErrorStack;
4use openssl::nid::Nid;
5use openssl::pkey::{Id, PKey, Private};
6use openssl::rsa::Rsa;
7use openssl::x509::{X509, X509Req};
8
9unsafe extern "C" {
10 pub fn X509_sign(
11 x: *mut openssl_sys::X509,
12 pkey: *mut openssl_sys::EVP_PKEY,
13 md: *const openssl_sys::EVP_MD,
14 ) -> ::std::os::raw::c_int;
15 pub fn X509_sign_ctx(
16 x: *mut openssl_sys::X509,
17 ctx: *mut openssl_sys::EVP_MD_CTX,
18 ) -> ::std::os::raw::c_int;
19}
20
21unsafe extern "C" {
22 pub fn X509_REQ_sign(
23 req: *mut openssl_sys::X509_REQ,
24 pkey: *mut openssl_sys::EVP_PKEY,
25 md: *const openssl_sys::EVP_MD,
26 ) -> ::std::os::raw::c_int;
27 pub fn X509_REQ_sign_ctx(
28 req: *mut openssl_sys::X509_REQ,
29 ctx: *mut openssl_sys::EVP_MD_CTX,
30 ) -> ::std::os::raw::c_int;
31}
32pub(crate) fn sign_certificate_digestless(
43 cert: &X509,
44 pkey: &PKey<openssl::pkey::Private>,
45) -> Result<(), String> {
46 if !is_digestless_key(pkey) {
47 return Err("sign_certificate_digestless called with non-digestless key".to_string());
48 }
49 let cert_ptr = cert.as_ptr();
50 let pkey_ptr = pkey.as_ptr();
51
52 if pkey.id() == Id::ED25519 {
53 let result = unsafe { X509_sign(cert_ptr, pkey_ptr, std::ptr::null()) };
54 return if result > 0 {
55 Ok(())
56 } else {
57 Err("Failed to sign certificate with Ed25519".to_string())
58 };
59 }
60
61 let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
69 if ctx.0.is_null() {
70 return Err("EVP_MD_CTX_new returned NULL".to_string());
71 }
72 let init = unsafe {
73 openssl_sys::EVP_DigestSignInit(
74 ctx.0,
75 std::ptr::null_mut(),
76 std::ptr::null(),
77 std::ptr::null_mut(),
78 pkey_ptr,
79 )
80 };
81 if init <= 0 {
82 return Err("EVP_DigestSignInit failed for PQC key".to_string());
83 }
84 let result = unsafe { X509_sign_ctx(cert_ptr, ctx.0) };
85
86 if result > 0 {
87 Ok(())
88 } else {
89 Err("X509_sign_ctx failed for PQC key".to_string())
90 }
91}
92
93pub(crate) fn sign_x509_req_digestless(req: &X509Req, pkey: &PKey<Private>) -> Result<(), String> {
97 if !is_digestless_key(pkey) {
98 return Err("sign_x509_req_digestless called with non-digestless key".to_string());
99 }
100 let req_ptr = req.as_ptr();
101 let pkey_ptr = pkey.as_ptr();
102
103 if pkey.id() == Id::ED25519 {
104 let result = unsafe { X509_REQ_sign(req_ptr, pkey_ptr, std::ptr::null()) };
105 return if result > 0 {
106 Ok(())
107 } else {
108 Err("Failed to sign X509Req with Ed25519".to_string())
109 };
110 }
111
112 let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
115 if ctx.0.is_null() {
116 return Err("EVP_MD_CTX_new returned NULL".to_string());
117 }
118 let init = unsafe {
119 openssl_sys::EVP_DigestSignInit(
120 ctx.0,
121 std::ptr::null_mut(),
122 std::ptr::null(),
123 std::ptr::null_mut(),
124 pkey_ptr,
125 )
126 };
127 if init <= 0 {
128 return Err("EVP_DigestSignInit failed for PQC key".to_string());
129 }
130 let result = unsafe { X509_REQ_sign_ctx(req_ptr, ctx.0) };
131
132 if result > 0 {
133 Ok(())
134 } else {
135 Err("X509_REQ_sign_ctx failed for PQC key".to_string())
136 }
137}
138
139#[cfg(feature = "pqc")]
145#[allow(dead_code)]
146const ML_KEM_OIDS: [&str; 3] = [
147 "2.16.840.1.101.3.4.4.1", "2.16.840.1.101.3.4.4.2", "2.16.840.1.101.3.4.4.3", ];
151
152struct MdCtx(*mut openssl_sys::EVP_MD_CTX);
162
163impl Drop for MdCtx {
164 fn drop(&mut self) {
165 if !self.0.is_null() {
166 unsafe { openssl_sys::EVP_MD_CTX_free(self.0) }
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq)]
175pub enum KeyType {
176 RSA2048,
178 RSA4096,
180 P224,
182 P256,
184 P384,
186 P521,
188 Ed25519,
190 #[cfg(feature = "pqc")]
192 MlDsa44,
193 #[cfg(feature = "pqc")]
195 MlDsa65,
196 #[cfg(feature = "pqc")]
198 MlDsa87,
199 #[cfg(feature = "pqc")]
201 SlhDsaSha2_128s,
202 #[cfg(feature = "pqc")]
204 SlhDsaSha2_192s,
205 #[cfg(feature = "pqc")]
207 SlhDsaSha2_256s,
208 #[cfg(feature = "pqc")]
213 MlKem512,
214 #[cfg(feature = "pqc")]
217 MlKem768,
218 #[cfg(feature = "pqc")]
221 MlKem1024,
222}
223
224pub(crate) fn select_key(key_type: &Option<KeyType>) -> Result<PKey<Private>, ErrorStack> {
225 match key_type {
226 Some(KeyType::P224) => {
227 let group = EcGroup::from_curve_name(Nid::SECP224R1)?;
228 let ec_key = EcKey::generate(&group)?;
229 PKey::from_ec_key(ec_key)
230 }
231 Some(KeyType::P256) => {
232 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
233 let ec_key = EcKey::generate(&group)?;
234 PKey::from_ec_key(ec_key)
235 }
236 Some(KeyType::P384) => {
237 let group = EcGroup::from_curve_name(Nid::SECP384R1)?;
238 let ec_key = EcKey::generate(&group)?;
239 PKey::from_ec_key(ec_key)
240 }
241 Some(KeyType::P521) => {
242 let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
243 let ec_key = EcKey::generate(&group)?;
244 PKey::from_ec_key(ec_key)
245 }
246 Some(KeyType::Ed25519) => PKey::generate_ed25519(),
247 #[cfg(feature = "pqc")]
248 Some(KeyType::MlDsa44) => generate_pqc_key("ML-DSA-44"),
249 #[cfg(feature = "pqc")]
250 Some(KeyType::MlDsa65) => generate_pqc_key("ML-DSA-65"),
251 #[cfg(feature = "pqc")]
252 Some(KeyType::MlDsa87) => generate_pqc_key("ML-DSA-87"),
253 #[cfg(feature = "pqc")]
254 Some(KeyType::SlhDsaSha2_128s) => generate_pqc_key("SLH-DSA-SHA2-128s"),
255 #[cfg(feature = "pqc")]
256 Some(KeyType::SlhDsaSha2_192s) => generate_pqc_key("SLH-DSA-SHA2-192s"),
257 #[cfg(feature = "pqc")]
258 Some(KeyType::SlhDsaSha2_256s) => generate_pqc_key("SLH-DSA-SHA2-256s"),
259 #[cfg(feature = "pqc")]
260 Some(KeyType::MlKem512) => generate_pqc_key("ML-KEM-512"),
261 #[cfg(feature = "pqc")]
262 Some(KeyType::MlKem768) => generate_pqc_key("ML-KEM-768"),
263 #[cfg(feature = "pqc")]
264 Some(KeyType::MlKem1024) => generate_pqc_key("ML-KEM-1024"),
265 Some(KeyType::RSA4096) => {
266 let rsa = Rsa::generate(4096)?;
267 PKey::from_rsa(rsa)
268 }
269 _ => {
270 let rsa = Rsa::generate(2048)?;
271 PKey::from_rsa(rsa)
272 }
273 }
274}
275
276#[cfg(feature = "pqc")]
277mod pqc {
278 use foreign_types::ForeignType;
279 use openssl::error::ErrorStack;
280 use openssl::pkey::{PKey, Private};
281 use std::ffi::CString;
282
283 unsafe extern "C" {
284 fn EVP_PKEY_CTX_new_from_name(
285 libctx: *mut std::ffi::c_void,
286 name: *const std::os::raw::c_char,
287 propquery: *const std::os::raw::c_char,
288 ) -> *mut openssl_sys::EVP_PKEY_CTX;
289 fn EVP_PKEY_keygen_init(ctx: *mut openssl_sys::EVP_PKEY_CTX) -> std::os::raw::c_int;
290 fn EVP_PKEY_generate(
291 ctx: *mut openssl_sys::EVP_PKEY_CTX,
292 ppkey: *mut *mut openssl_sys::EVP_PKEY,
293 ) -> std::os::raw::c_int;
294 fn EVP_PKEY_CTX_free(ctx: *mut openssl_sys::EVP_PKEY_CTX);
295 pub fn EVP_PKEY_is_a(
299 pkey: *mut openssl_sys::EVP_PKEY,
300 name: *const std::os::raw::c_char,
301 ) -> std::os::raw::c_int;
302 }
303
304 struct PkeyCtx(*mut openssl_sys::EVP_PKEY_CTX);
306
307 impl Drop for PkeyCtx {
308 fn drop(&mut self) {
309 if !self.0.is_null() {
310 unsafe { EVP_PKEY_CTX_free(self.0) }
311 }
312 }
313 }
314
315 pub(crate) fn generate_pqc_key(alg_name: &str) -> Result<PKey<Private>, ErrorStack> {
325 let cname = CString::new(alg_name).expect("alg_name contains interior NUL");
326
327 let ctx_ptr = unsafe {
330 EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), cname.as_ptr(), std::ptr::null())
331 };
332 if ctx_ptr.is_null() {
333 return Err(ErrorStack::get());
334 }
335 let ctx = PkeyCtx(ctx_ptr);
336
337 if unsafe { EVP_PKEY_keygen_init(ctx.0) } <= 0 {
338 return Err(ErrorStack::get());
339 }
340
341 let mut pkey_ptr: *mut openssl_sys::EVP_PKEY = std::ptr::null_mut();
342 if unsafe { EVP_PKEY_generate(ctx.0, &mut pkey_ptr) } <= 0 {
343 return Err(ErrorStack::get());
344 }
345 if pkey_ptr.is_null() {
346 return Err(ErrorStack::get());
347 }
348
349 Ok(unsafe { PKey::<Private>::from_ptr(pkey_ptr) })
352 }
353}
354#[cfg(feature = "pqc")]
355pub(crate) use pqc::generate_pqc_key;
356
357#[cfg(feature = "pqc")]
358pub(crate) fn is_pqc_pkey<T>(pkey: &PKey<T>) -> bool {
359 use std::ffi::CString;
360 use std::sync::OnceLock;
361
362 static NAMES: OnceLock<[CString; 6]> = OnceLock::new();
364 let names = NAMES.get_or_init(|| {
365 [
366 CString::new("ML-DSA-44").unwrap(),
367 CString::new("ML-DSA-65").unwrap(),
368 CString::new("ML-DSA-87").unwrap(),
369 CString::new("SLH-DSA-SHA2-128s").unwrap(),
370 CString::new("SLH-DSA-SHA2-192s").unwrap(),
371 CString::new("SLH-DSA-SHA2-256s").unwrap(),
372 ]
373 });
374 use foreign_types::ForeignType;
375 let ptr = pkey.as_ptr();
376 names
377 .iter()
378 .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
381}
382
383#[cfg(feature = "pqc")]
392pub(crate) fn is_mlkem_pkey<T>(pkey: &PKey<T>) -> bool {
393 use std::ffi::CString;
394 use std::sync::OnceLock;
395
396 static NAMES: OnceLock<[CString; 3]> = OnceLock::new();
398 let names = NAMES.get_or_init(|| {
399 [
400 CString::new("ML-KEM-512").unwrap(),
401 CString::new("ML-KEM-768").unwrap(),
402 CString::new("ML-KEM-1024").unwrap(),
403 ]
404 });
405 use foreign_types::ForeignType;
406 let ptr = pkey.as_ptr();
407 names
408 .iter()
409 .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
412}
413
414pub(crate) fn is_digestless_key(pkey: &PKey<Private>) -> bool {
418 if pkey.id() == Id::ED25519 {
419 return true;
420 }
421 #[cfg(feature = "pqc")]
422 {
423 return is_pqc_pkey(pkey);
424 }
425 #[allow(unreachable_code)]
426 false
427}
428
429#[cfg(feature = "pqc")]
435pub(crate) fn reject_mlkem_signing(
436 pkey: &PKey<Private>,
437 message: &'static str,
438) -> Result<(), Box<dyn std::error::Error>> {
439 if is_mlkem_pkey(pkey) {
440 return Err(message.into());
441 }
442 Ok(())
443}