1use serde::{Deserialize, Serialize};
8use thiserror::Error;
9use tracing::{debug, info};
10
11#[derive(Debug, Error)]
13pub enum KeyGenError {
14 #[error("unsupported key type: {0}")]
16 UnsupportedKeyType(String),
17
18 #[error("{algorithm} key size {bits}-bit is below minimum {min_bits}-bit")]
20 KeyTooSmall {
21 algorithm: String,
22 bits: u32,
23 min_bits: u32,
24 },
25
26 #[error("software key generation failed: {0}")]
28 SoftwareError(String),
29
30 #[error("HSM key generation failed: {0}")]
32 HsmError(String),
33
34 #[error("key archival failed: {0}")]
36 ArchivalError(String),
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "lowercase")]
46pub enum KeyType {
47 Rsa(u32),
49 Ecdsa(EcCurve),
51 MlDsa(MlDsaLevel),
54 MlKem(MlKemLevel),
58 CompositeMlDsa {
61 ml_dsa: MlDsaLevel,
62 classical: ClassicalSigningAlg,
63 },
64}
65
66#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
68pub enum MlDsaLevel {
69 #[serde(rename = "44")]
71 MlDsa44,
72 #[serde(rename = "65")]
74 MlDsa65,
75 #[serde(rename = "87")]
77 MlDsa87,
78}
79
80#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
85pub enum MlKemLevel {
86 #[serde(rename = "512")]
88 MlKem512,
89 #[serde(rename = "768")]
91 MlKem768,
92 #[serde(rename = "1024")]
94 MlKem1024,
95}
96
97#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
99pub enum ClassicalSigningAlg {
100 Rsa2048,
101 Rsa3072,
102 Rsa4096,
103 EcP256,
104 EcP384,
105 Ed25519,
106 Ed448,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub enum EcCurve {
112 P256,
114 P384,
116}
117
118impl std::fmt::Display for MlDsaLevel {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 match self {
121 MlDsaLevel::MlDsa44 => write!(f, "ML-DSA-44"),
122 MlDsaLevel::MlDsa65 => write!(f, "ML-DSA-65"),
123 MlDsaLevel::MlDsa87 => write!(f, "ML-DSA-87"),
124 }
125 }
126}
127
128impl std::fmt::Display for MlKemLevel {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 match self {
131 MlKemLevel::MlKem512 => write!(f, "ML-KEM-512"),
132 MlKemLevel::MlKem768 => write!(f, "ML-KEM-768"),
133 MlKemLevel::MlKem1024 => write!(f, "ML-KEM-1024"),
134 }
135 }
136}
137
138impl std::fmt::Display for EcCurve {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 match self {
141 EcCurve::P256 => write!(f, "P-256"),
142 EcCurve::P384 => write!(f, "P-384"),
143 }
144 }
145}
146
147impl std::fmt::Display for ClassicalSigningAlg {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 ClassicalSigningAlg::Rsa2048 => write!(f, "RSA-2048"),
151 ClassicalSigningAlg::Rsa3072 => write!(f, "RSA-3072"),
152 ClassicalSigningAlg::Rsa4096 => write!(f, "RSA-4096"),
153 ClassicalSigningAlg::EcP256 => write!(f, "EC-P-256"),
154 ClassicalSigningAlg::EcP384 => write!(f, "EC-P-384"),
155 ClassicalSigningAlg::Ed25519 => write!(f, "Ed25519"),
156 ClassicalSigningAlg::Ed448 => write!(f, "Ed448"),
157 }
158 }
159}
160
161pub struct KeyGenResult {
163 pub public_key_der: Vec<u8>,
165 pub private_key_der: Vec<u8>,
169 pub key_type: KeyType,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct KeyGenConfig {
176 pub use_hsm: bool,
178 pub archive_key: bool,
180 pub allowed_types: Vec<KeyType>,
182}
183
184impl Default for KeyGenConfig {
185 fn default() -> Self {
186 Self {
187 use_hsm: false,
188 archive_key: false,
189 allowed_types: vec![
190 KeyType::Rsa(2048),
192 KeyType::Rsa(3072),
193 KeyType::Rsa(4096),
194 KeyType::Ecdsa(EcCurve::P256),
195 KeyType::Ecdsa(EcCurve::P384),
196 KeyType::MlDsa(MlDsaLevel::MlDsa44),
198 KeyType::MlDsa(MlDsaLevel::MlDsa65),
199 KeyType::MlDsa(MlDsaLevel::MlDsa87),
200 KeyType::MlKem(MlKemLevel::MlKem512),
202 KeyType::MlKem(MlKemLevel::MlKem768),
203 KeyType::MlKem(MlKemLevel::MlKem1024),
204 KeyType::CompositeMlDsa {
206 ml_dsa: MlDsaLevel::MlDsa44,
207 classical: ClassicalSigningAlg::EcP256,
208 },
209 KeyType::CompositeMlDsa {
210 ml_dsa: MlDsaLevel::MlDsa65,
211 classical: ClassicalSigningAlg::EcP384,
212 },
213 KeyType::CompositeMlDsa {
214 ml_dsa: MlDsaLevel::MlDsa87,
215 classical: ClassicalSigningAlg::EcP384,
216 },
217 ],
218 }
219 }
220}
221
222pub fn generate_key_pair(
238 key_type: &KeyType,
239 config: &KeyGenConfig,
240) -> Result<KeyGenResult, KeyGenError> {
241 validate_key_type(key_type)?;
242
243 if config.use_hsm {
244 generate_hsm_key(key_type)
245 } else {
246 generate_software_key(key_type)
247 }
248}
249
250fn validate_key_type(key_type: &KeyType) -> Result<(), KeyGenError> {
252 match key_type {
253 KeyType::Rsa(bits) => {
254 const MIN_RSA_BITS: u32 = 2048;
255 if *bits < MIN_RSA_BITS {
256 return Err(KeyGenError::KeyTooSmall {
257 algorithm: "RSA".into(),
258 bits: *bits,
259 min_bits: MIN_RSA_BITS,
260 });
261 }
262 if !matches!(*bits, 2048 | 3072 | 4096) {
263 return Err(KeyGenError::UnsupportedKeyType(format!(
264 "RSA {bits}-bit (use 2048, 3072, or 4096)"
265 )));
266 }
267 }
268 KeyType::Ecdsa(curve) => {
269 debug!(curve = %curve, "ECDSA key type validated");
270 }
271 KeyType::MlDsa(level) => {
272 debug!(level = %level, "ML-DSA key type validated (FIPS 204)");
273 }
274 KeyType::MlKem(level) => {
275 debug!(level = %level, "ML-KEM key type validated (FIPS 203)");
276 }
277 KeyType::CompositeMlDsa { ml_dsa, classical } => {
278 debug!(
279 ml_dsa = %ml_dsa,
280 classical = %classical,
281 "composite ML-DSA key type validated (draft-ietf-lamps-pq-composite-sigs-19)"
282 );
283 }
284 }
285 Ok(())
286}
287
288fn generate_software_key(key_type: &KeyType) -> Result<KeyGenResult, KeyGenError> {
297 info!(key_type = ?key_type, "generating software key pair");
298
299 match key_type {
305 KeyType::Rsa(bits) => generate_classical_key(key_type, c"RSA", |pb| {
306 pb.push_uint(c"bits", *bits)?.push_uint(c"e", 65537u32)
307 }),
308 KeyType::Ecdsa(curve) => {
309 let curve_cstr: &std::ffi::CStr = match curve {
310 EcCurve::P256 => c"P-256",
311 EcCurve::P384 => c"P-384",
312 };
313 generate_classical_key(key_type, c"EC", |pb| {
314 pb.push_utf8_string(c"group", curve_cstr)
315 })
316 }
317 KeyType::MlDsa(level) => {
318 let param = match level {
319 MlDsaLevel::MlDsa44 => "ML-DSA-44",
320 MlDsaLevel::MlDsa65 => "ML-DSA-65",
321 MlDsaLevel::MlDsa87 => "ML-DSA-87",
322 };
323 generate_pqc_key(key_type, || {
324 synta_certificate::BackendPrivateKey::generate_ml_dsa(param)
325 })
326 }
327 KeyType::MlKem(level) => {
328 let param = match level {
329 MlKemLevel::MlKem512 => "ML-KEM-512",
330 MlKemLevel::MlKem768 => "ML-KEM-768",
331 MlKemLevel::MlKem1024 => "ML-KEM-1024",
332 };
333 generate_pqc_key(key_type, || {
334 synta_certificate::BackendPrivateKey::generate_ml_kem(param)
335 })
336 }
337 KeyType::CompositeMlDsa { ml_dsa, classical } => {
338 let sub_arc = composite_sub_arc(ml_dsa, classical).ok_or_else(|| {
339 KeyGenError::UnsupportedKeyType(format!(
340 "unsupported composite ML-DSA combination: {ml_dsa}+{classical}"
341 ))
342 })?;
343 generate_pqc_key(key_type, || {
344 synta_certificate::BackendPrivateKey::generate_composite_ml_dsa(sub_arc)
345 })
346 }
347 }
348}
349
350fn generate_classical_key<F>(
355 key_type: &KeyType,
356 algorithm: &std::ffi::CStr,
357 configure: F,
358) -> Result<KeyGenResult, KeyGenError>
359where
360 F: FnOnce(
361 native_ossl::params::ParamBuilder,
362 ) -> Result<native_ossl::params::ParamBuilder, native_ossl::error::ErrorStack>,
363{
364 use native_ossl::params::ParamBuilder;
365 use native_ossl::pkey::KeygenCtx;
366
367 let pb = ParamBuilder::new()
368 .map_err(|e| KeyGenError::SoftwareError(format!("param builder init: {e}")))?;
369
370 let pb =
371 configure(pb).map_err(|e| KeyGenError::SoftwareError(format!("param configure: {e}")))?;
372
373 let params = pb
374 .build()
375 .map_err(|e| KeyGenError::SoftwareError(format!("param build: {e}")))?;
376
377 let mut kgen = KeygenCtx::new(algorithm)
378 .map_err(|e| KeyGenError::SoftwareError(format!("keygen ctx init: {e}")))?;
379
380 kgen.set_params(¶ms)
381 .map_err(|e| KeyGenError::SoftwareError(format!("keygen set params: {e}")))?;
382
383 let pkey = kgen
384 .generate()
385 .map_err(|e| KeyGenError::SoftwareError(format!("key generation: {e}")))?;
386
387 let public_key_der = pkey
389 .public_key_to_der()
390 .map_err(|e| KeyGenError::SoftwareError(format!("SPKI extraction: {e}")))?;
391
392 let private_key_der = pkey
394 .to_pkcs8_der()
395 .map_err(|e| KeyGenError::SoftwareError(format!("PKCS#8 extraction: {e}")))?;
396
397 info!(
398 key_type = ?key_type,
399 public_key_len = public_key_der.len(),
400 private_key_len = private_key_der.len(),
401 "classical software key pair generated"
402 );
403
404 Ok(KeyGenResult {
405 public_key_der,
406 private_key_der,
407 key_type: key_type.clone(),
408 })
409}
410
411fn generate_pqc_key<F>(key_type: &KeyType, generator: F) -> Result<KeyGenResult, KeyGenError>
415where
416 F: FnOnce() -> Result<synta_certificate::BackendPrivateKey, synta_certificate::PrivateKeyError>,
417{
418 use synta_certificate::PrivateKey as _;
419
420 let backend_key =
421 generator().map_err(|e| KeyGenError::SoftwareError(format!("PQC key generation: {e}")))?;
422
423 let public_key_der = backend_key
425 .public_key_spki_der()
426 .map_err(|e| KeyGenError::SoftwareError(format!("PQC SPKI extraction: {e}")))?;
427
428 let private_key_der = backend_key
430 .to_der()
431 .map_err(|e| KeyGenError::SoftwareError(format!("PQC PKCS#8 extraction: {e}")))?;
432
433 info!(
434 key_type = ?key_type,
435 public_key_len = public_key_der.len(),
436 private_key_len = private_key_der.len(),
437 "PQC software key pair generated"
438 );
439
440 Ok(KeyGenResult {
441 public_key_der,
442 private_key_der,
443 key_type: key_type.clone(),
444 })
445}
446
447fn generate_hsm_key(key_type: &KeyType) -> Result<KeyGenResult, KeyGenError> {
460 info!(key_type = ?key_type, "HSM key generation requested");
461
462 Err(KeyGenError::HsmError(
463 "PKCS#11 PQC key generation pending kipuka-hsm integration".into(),
464 ))
465}
466
467pub fn composite_sub_arc(ml_dsa: &MlDsaLevel, classical: &ClassicalSigningAlg) -> Option<u32> {
472 match (ml_dsa, classical) {
473 (MlDsaLevel::MlDsa44, ClassicalSigningAlg::Rsa2048) => Some(37),
474 (MlDsaLevel::MlDsa44, ClassicalSigningAlg::EcP256) => Some(38),
475 (MlDsaLevel::MlDsa44, ClassicalSigningAlg::Rsa3072) => Some(39),
476 (MlDsaLevel::MlDsa44, ClassicalSigningAlg::Ed25519) => Some(40),
477 (MlDsaLevel::MlDsa65, ClassicalSigningAlg::Rsa3072) => Some(41),
478 (MlDsaLevel::MlDsa65, ClassicalSigningAlg::EcP384) => Some(42),
479 (MlDsaLevel::MlDsa65, ClassicalSigningAlg::Rsa4096) => Some(43),
480 (MlDsaLevel::MlDsa65, ClassicalSigningAlg::Ed25519) => Some(44),
481 (MlDsaLevel::MlDsa87, ClassicalSigningAlg::EcP384) => Some(45),
482 (MlDsaLevel::MlDsa87, ClassicalSigningAlg::Ed448) => Some(46),
483 _ => None,
484 }
485}