1use chrono::{NaiveDate, NaiveDateTime, TimeZone, Utc};
2use foreign_types::ForeignType;
3use openssl::asn1::{Asn1Object, Asn1OctetString, Asn1Time};
4use openssl::bn::BigNum;
5use openssl::ec::{EcGroup, EcKey};
6use openssl::error::ErrorStack;
7use openssl::hash::{MessageDigest, hash};
8use openssl::nid::Nid;
9use openssl::pkey::{Id, PKey, Private};
10use openssl::rsa::Rsa;
11use openssl::stack::Stack;
12use openssl::x509::extension::{
13 AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
14};
15use openssl::x509::{
16 X509, X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509StoreContext,
17 store::X509StoreBuilder,
18};
19use std::collections::{HashMap, HashSet};
20use std::fs::{File, create_dir_all};
21use std::io::Write;
22use std::path::Path;
23use x509_parser::certification_request::X509CertificationRequest;
24use x509_parser::extensions::ParsedExtension;
25use x509_parser::parse_x509_certificate;
26use x509_parser::prelude::FromDer;
27use yasna::models::ObjectIdentifier;
28
29unsafe extern "C" {
30 pub fn X509_sign(
31 x: *mut openssl_sys::X509,
32 pkey: *mut openssl_sys::EVP_PKEY,
33 md: *const openssl_sys::EVP_MD,
34 ) -> ::std::os::raw::c_int;
35 pub fn X509_sign_ctx(
36 x: *mut openssl_sys::X509,
37 ctx: *mut openssl_sys::EVP_MD_CTX,
38 ) -> ::std::os::raw::c_int;
39}
40
41unsafe extern "C" {
42 pub fn X509_REQ_sign(
43 req: *mut openssl_sys::X509_REQ,
44 pkey: *mut openssl_sys::EVP_PKEY,
45 md: *const openssl_sys::EVP_MD,
46 ) -> ::std::os::raw::c_int;
47 pub fn X509_REQ_sign_ctx(
48 req: *mut openssl_sys::X509_REQ,
49 ctx: *mut openssl_sys::EVP_MD_CTX,
50 ) -> ::std::os::raw::c_int;
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum CertificatePolicy {
61 DomainValidated, OrganizationValidated, IndividualValidated, ExtendedValidation, AnyPolicy, Other(String), }
68
69impl CertificatePolicy {
70 pub fn oid(&self) -> &str {
75 match self {
76 Self::DomainValidated => "2.23.140.1.2.1",
77 Self::OrganizationValidated => "2.23.140.1.2.2",
78 Self::IndividualValidated => "2.23.140.1.2.3",
79 Self::ExtendedValidation => "2.23.140.1.1",
80 Self::AnyPolicy => "2.5.29.32.0",
81 Self::Other(oid) => oid,
82 }
83 }
84}
85struct MdCtx(*mut openssl_sys::EVP_MD_CTX);
95
96impl Drop for MdCtx {
97 fn drop(&mut self) {
98 if !self.0.is_null() {
99 unsafe { openssl_sys::EVP_MD_CTX_free(self.0) }
102 }
103 }
104}
105
106fn sign_certificate_digestless(
117 cert: &X509,
118 pkey: &PKey<openssl::pkey::Private>,
119) -> Result<(), String> {
120 if !is_digestless_key(pkey) {
121 return Err("sign_certificate_digestless called with non-digestless key".to_string());
122 }
123 let cert_ptr = cert.as_ptr();
124 let pkey_ptr = pkey.as_ptr();
125
126 if pkey.id() == Id::ED25519 {
127 let result = unsafe { X509_sign(cert_ptr, pkey_ptr, std::ptr::null()) };
128 return if result > 0 {
129 Ok(())
130 } else {
131 Err("Failed to sign certificate with Ed25519".to_string())
132 };
133 }
134
135 let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
143 if ctx.0.is_null() {
144 return Err("EVP_MD_CTX_new returned NULL".to_string());
145 }
146 let init = unsafe {
147 openssl_sys::EVP_DigestSignInit(
148 ctx.0,
149 std::ptr::null_mut(),
150 std::ptr::null(),
151 std::ptr::null_mut(),
152 pkey_ptr,
153 )
154 };
155 if init <= 0 {
156 return Err("EVP_DigestSignInit failed for PQC key".to_string());
157 }
158 let result = unsafe { X509_sign_ctx(cert_ptr, ctx.0) };
159
160 if result > 0 {
161 Ok(())
162 } else {
163 Err("X509_sign_ctx failed for PQC key".to_string())
164 }
165}
166
167fn sign_x509_req_digestless(req: &X509Req, pkey: &PKey<Private>) -> Result<(), String> {
171 if !is_digestless_key(pkey) {
172 return Err("sign_x509_req_digestless called with non-digestless key".to_string());
173 }
174 let req_ptr = req.as_ptr();
175 let pkey_ptr = pkey.as_ptr();
176
177 if pkey.id() == Id::ED25519 {
178 let result = unsafe { X509_REQ_sign(req_ptr, pkey_ptr, std::ptr::null()) };
179 return if result > 0 {
180 Ok(())
181 } else {
182 Err("Failed to sign X509Req with Ed25519".to_string())
183 };
184 }
185
186 let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
189 if ctx.0.is_null() {
190 return Err("EVP_MD_CTX_new returned NULL".to_string());
191 }
192 let init = unsafe {
193 openssl_sys::EVP_DigestSignInit(
194 ctx.0,
195 std::ptr::null_mut(),
196 std::ptr::null(),
197 std::ptr::null_mut(),
198 pkey_ptr,
199 )
200 };
201 if init <= 0 {
202 return Err("EVP_DigestSignInit failed for PQC key".to_string());
203 }
204 let result = unsafe { X509_REQ_sign_ctx(req_ptr, ctx.0) };
205
206 if result > 0 {
207 Ok(())
208 } else {
209 Err("X509_REQ_sign_ctx failed for PQC key".to_string())
210 }
211}
212
213pub(crate) fn is_digestless_key(pkey: &PKey<Private>) -> bool {
217 if pkey.id() == Id::ED25519 {
218 return true;
219 }
220 #[cfg(feature = "pqc")]
221 {
222 return is_pqc_pkey(pkey);
223 }
224 #[allow(unreachable_code)]
225 false
226}
227
228#[cfg(feature = "pqc")]
229fn is_pqc_pkey(pkey: &PKey<Private>) -> bool {
230 use std::ffi::CString;
231 use std::sync::OnceLock;
232
233 static NAMES: OnceLock<[CString; 6]> = OnceLock::new();
235 let names = NAMES.get_or_init(|| {
236 [
237 CString::new("ML-DSA-44").unwrap(),
238 CString::new("ML-DSA-65").unwrap(),
239 CString::new("ML-DSA-87").unwrap(),
240 CString::new("SLH-DSA-SHA2-128s").unwrap(),
241 CString::new("SLH-DSA-SHA2-192s").unwrap(),
242 CString::new("SLH-DSA-SHA2-256s").unwrap(),
243 ]
244 });
245 use foreign_types::ForeignType;
246 let ptr = pkey.as_ptr();
247 names
248 .iter()
249 .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
252}
253
254#[cfg(feature = "pqc")]
260#[allow(dead_code)]
261const ML_KEM_OIDS: [&str; 3] = [
262 "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", ];
266
267#[cfg(feature = "pqc")]
276fn is_mlkem_pkey(pkey: &PKey<Private>) -> bool {
277 use std::ffi::CString;
278 use std::sync::OnceLock;
279
280 static NAMES: OnceLock<[CString; 3]> = OnceLock::new();
282 let names = NAMES.get_or_init(|| {
283 [
284 CString::new("ML-KEM-512").unwrap(),
285 CString::new("ML-KEM-768").unwrap(),
286 CString::new("ML-KEM-1024").unwrap(),
287 ]
288 });
289 use foreign_types::ForeignType;
290 let ptr = pkey.as_ptr();
291 names
292 .iter()
293 .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
296}
297
298#[cfg(feature = "pqc")]
299mod pqc {
300 use foreign_types::ForeignType;
301 use openssl::error::ErrorStack;
302 use openssl::pkey::{PKey, Private};
303 use std::ffi::CString;
304
305 unsafe extern "C" {
306 fn EVP_PKEY_CTX_new_from_name(
307 libctx: *mut std::ffi::c_void,
308 name: *const std::os::raw::c_char,
309 propquery: *const std::os::raw::c_char,
310 ) -> *mut openssl_sys::EVP_PKEY_CTX;
311 fn EVP_PKEY_keygen_init(ctx: *mut openssl_sys::EVP_PKEY_CTX) -> std::os::raw::c_int;
312 fn EVP_PKEY_generate(
313 ctx: *mut openssl_sys::EVP_PKEY_CTX,
314 ppkey: *mut *mut openssl_sys::EVP_PKEY,
315 ) -> std::os::raw::c_int;
316 fn EVP_PKEY_CTX_free(ctx: *mut openssl_sys::EVP_PKEY_CTX);
317 pub fn EVP_PKEY_is_a(
321 pkey: *mut openssl_sys::EVP_PKEY,
322 name: *const std::os::raw::c_char,
323 ) -> std::os::raw::c_int;
324 }
325
326 struct PkeyCtx(*mut openssl_sys::EVP_PKEY_CTX);
328
329 impl Drop for PkeyCtx {
330 fn drop(&mut self) {
331 if !self.0.is_null() {
332 unsafe { EVP_PKEY_CTX_free(self.0) }
333 }
334 }
335 }
336
337 pub(super) fn generate_pqc_key(alg_name: &str) -> Result<PKey<Private>, ErrorStack> {
347 let cname = CString::new(alg_name).expect("alg_name contains interior NUL");
348
349 let ctx_ptr = unsafe {
352 EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), cname.as_ptr(), std::ptr::null())
353 };
354 if ctx_ptr.is_null() {
355 return Err(ErrorStack::get());
356 }
357 let ctx = PkeyCtx(ctx_ptr);
358
359 if unsafe { EVP_PKEY_keygen_init(ctx.0) } <= 0 {
360 return Err(ErrorStack::get());
361 }
362
363 let mut pkey_ptr: *mut openssl_sys::EVP_PKEY = std::ptr::null_mut();
364 if unsafe { EVP_PKEY_generate(ctx.0, &mut pkey_ptr) } <= 0 {
365 return Err(ErrorStack::get());
366 }
367 if pkey_ptr.is_null() {
368 return Err(ErrorStack::get());
369 }
370
371 Ok(unsafe { PKey::<Private>::from_ptr(pkey_ptr) })
374 }
375}
376
377#[cfg(feature = "pqc")]
378use pqc::generate_pqc_key;
379
380macro_rules! vec_str_to_hs {
381 ($vec:expr) => {
382 $vec.iter()
383 .map(|s| s.to_string())
384 .collect::<HashSet<String>>()
385 };
386}
387#[derive(Debug, Clone, PartialEq)]
389pub enum KeyType {
390 RSA2048,
392 RSA4096,
394 P224,
396 P256,
398 P384,
400 P521,
402 Ed25519,
404 #[cfg(feature = "pqc")]
406 MlDsa44,
407 #[cfg(feature = "pqc")]
409 MlDsa65,
410 #[cfg(feature = "pqc")]
412 MlDsa87,
413 #[cfg(feature = "pqc")]
415 SlhDsaSha2_128s,
416 #[cfg(feature = "pqc")]
418 SlhDsaSha2_192s,
419 #[cfg(feature = "pqc")]
421 SlhDsaSha2_256s,
422 #[cfg(feature = "pqc")]
427 MlKem512,
428 #[cfg(feature = "pqc")]
431 MlKem768,
432 #[cfg(feature = "pqc")]
435 MlKem1024,
436}
437#[derive(Debug, Clone)]
439pub enum HashAlg {
440 SHA1,
442 SHA256,
444 SHA384,
446 SHA512,
448}
449#[allow(non_camel_case_types)]
451#[derive(Hash, Eq, PartialEq, Debug, Clone)]
452pub enum Usage {
453 certsign,
455 crlsign,
457 encipherment,
459 clientauth,
461 serverauth,
463 signature,
465 contentcommitment,
467}
468
469pub trait X509Parts {
471 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
476 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
481 fn pem_extension(&self) -> &'static str;
486}
487
488pub trait X509Common {
490 fn save<P: AsRef<Path>, F: AsRef<Path>>(
503 &self,
504 path: P,
505 filename: F,
506 ) -> Result<(), Box<dyn std::error::Error>>;
507}
508
509impl<T: X509Parts> X509Common for T {
518 fn save<P: AsRef<Path>, F: AsRef<Path>>(
529 &self,
530 path: P,
531 filename: F,
532 ) -> Result<(), Box<dyn std::error::Error>> {
533 create_dir_all(&path)?;
534
535 let os_file = filename
536 .as_ref()
537 .file_name()
538 .ok_or("Failed to extract file name")?;
539
540 let write_file = |suffix: &str, content: &[u8]| -> Result<(), Box<dyn std::error::Error>> {
541 let mut new_name = os_file.to_os_string();
542 new_name.push(suffix);
543 let full_path = path.as_ref().join(new_name);
544 let mut file = File::create(full_path)?;
545 file.write_all(content)?;
546 Ok(())
547 };
548 if let Ok(ref key) = self.get_private_key() {
549 write_file("_pkey.pem", key)?;
550 }
551 write_file(self.pem_extension(), &self.get_pem()?)?;
552 Ok(())
553 }
554}
555pub struct Csr {
557 pub csr: X509Req,
559 pub pkey: Option<PKey<Private>>,
563}
564
565impl X509Parts for Csr {
566 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
567 Ok(self.csr.to_pem()?)
568 }
569
570 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
571 match self.pkey {
572 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
573 _ => Err("No private key found".into()),
574 }
575 }
576 fn pem_extension(&self) -> &'static str {
577 "_csr.pem"
578 }
579}
580pub trait CsrX509Common: X509Common {}
582impl CsrX509Common for Csr {}
583
584pub struct CsrOptions {
586 valid_to: Asn1Time,
587 valid_from: Asn1Time,
588 ca: bool,
589 policies: Vec<CertificatePolicy>,
590}
591impl Default for CsrOptions {
592 fn default() -> Self {
593 Self::new()
594 }
595}
596
597impl CsrOptions {
598 pub fn new() -> Self {
603 Self {
604 ca: false,
605 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), policies: Default::default(),
608 }
609 }
610
611 pub fn valid_from(mut self, valid_from: &str) -> Self {
616 self.valid_from =
617 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
618 self
619 }
620
621 pub fn valid_to(mut self, valid_to: &str) -> Self {
626 self.valid_to =
627 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
628 self
629 }
630
631 pub fn is_ca(mut self, ca: bool) -> Self {
636 self.ca = ca;
637 self
638 }
639 pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
644 self.policies = policies;
645 self
646 }
647}
648impl Csr {
649 pub fn load_csr<C: AsRef<Path>>(csr_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
651 let cert_pem = std::fs::read(csr_pem_file)?;
652 let cs_req = X509Req::from_pem(&cert_pem)?;
653 Ok(Self {
654 csr: cs_req,
655 pkey: None,
656 })
657 }
658 pub fn build_signed_certificate(
660 &self,
661 signer: &Certificate,
662 options: CsrOptions,
663 ) -> Result<Certificate, Box<dyn std::error::Error>> {
664 let can_sign = can_sign_cert(&signer.x509)?;
665 if !can_sign {
666 let err = format!(
667 "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
668 signer.x509.issuer_name()
669 );
670 return Err(err.into());
671 }
672 let mut builder = X509Builder::new()?;
673 builder.set_version(2)?;
674 builder.set_subject_name(self.csr.subject_name())?;
675 builder.set_issuer_name(signer.x509.subject_name())?;
676 let csr_public_key = self.csr.public_key()?;
677 builder.set_pubkey(&csr_public_key)?;
678
679 let der = self.csr.to_der()?;
680 let parsed_csr = X509CertificationRequest::from_der(&der)?;
681
682 let req_ext = parsed_csr.1.requested_extensions();
683 let mut any_key_used = false;
684 if let Some(exts) = req_ext {
685 for ext in exts {
686 match ext {
687 ParsedExtension::KeyUsage(ku) => {
688 any_key_used = true;
689 let mut cert_sign_added = false;
690 let mut crl_sign_added = false;
691 let mut usage = openssl::x509::extension::KeyUsage::new();
692 if ku.digital_signature() {
693 usage.digital_signature();
694 }
695 if ku.key_encipherment() {
696 usage.key_encipherment();
697 }
698 if ku.key_cert_sign() {
699 cert_sign_added = true;
700 usage.key_cert_sign();
701 }
702 if ku.non_repudiation() {
703 usage.non_repudiation();
704 }
705 if ku.crl_sign() {
706 crl_sign_added = true;
707 usage.crl_sign();
708 }
709
710 if options.ca {
711 if !cert_sign_added {
712 usage.key_cert_sign();
713 }
714 if !crl_sign_added {
715 usage.crl_sign();
716 }
717 }
718 builder.append_extension(usage.build()?)?;
719 }
720 ParsedExtension::ExtendedKeyUsage(eku) => {
721 let mut ext = openssl::x509::extension::ExtendedKeyUsage::new();
722 if eku.server_auth {
723 ext.server_auth();
724 }
725 if eku.client_auth {
726 ext.client_auth();
727 }
728 if eku.code_signing {
729 ext.code_signing();
730 }
731 if eku.email_protection {
732 ext.email_protection();
733 }
734 builder.append_extension(ext.build()?)?;
735 }
736 ParsedExtension::SubjectAlternativeName(san) => {
737 let mut openssl_san =
738 openssl::x509::extension::SubjectAlternativeName::new();
739 for name in &san.general_names {
740 if let x509_parser::extensions::GeneralName::DNSName(dns) = name {
741 openssl_san.dns(dns);
742 }
743 }
744 builder.append_extension(
745 openssl_san.build(&builder.x509v3_context(None, None))?,
746 )?;
747 }
748 _ => {
749 println!("Unsupported extension: {:?}", ext);
750 }
751 }
752 }
753 }
754 if options.ca {
755 builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
756 if !any_key_used {
757 let key_usage = KeyUsage::new().key_cert_sign().crl_sign().build().unwrap();
758 builder.append_extension(key_usage)?;
759 }
760 } else {
761 builder.append_extension(BasicConstraints::new().build()?)?;
762 }
763 builder.set_not_before(&options.valid_from)?;
764 builder.set_not_after(&options.valid_to)?;
765 let serial_number = {
766 let mut serial = BigNum::new()?;
767 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
768 serial.to_asn1_integer()?
769 };
770 builder.set_serial_number(&serial_number)?;
771 if signer.x509.subject_key_id().is_some() {
772 let aki = AuthorityKeyIdentifier::new()
773 .keyid(true)
774 .issuer(false)
775 .build(&builder.x509v3_context(Some(&signer.x509), None))?;
776 builder.append_extension(aki)?;
777 }
778 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = self.csr.public_key().unwrap().public_key_to_der()?;
780 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
781 let der_encoded = yasna::construct_der(|writer| {
782 writer.write_bytes(ski_hash.as_ref());
783 });
784 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
785 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
786 builder.append_extension(ext)?;
787 append_certificate_policies(&mut builder, &options.policies)?;
788 let cert: X509 = if is_digestless_key(signer.pkey.as_ref().unwrap()) {
789 let builder_cert = builder.build();
790 sign_certificate_digestless(&builder_cert, signer.pkey.as_ref().unwrap())
791 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
792 builder_cert
793 } else {
794 builder.sign(signer.pkey.as_ref().unwrap(), MessageDigest::sha256())?;
795 builder.build()
796 };
797
798 Ok(Certificate {
799 x509: cert,
800 pkey: None,
801 })
802 }
803}
804#[derive(Clone)]
806pub struct Certificate {
807 pub x509: X509,
809 pub pkey: Option<PKey<Private>>,
813}
814
815impl X509Parts for Certificate {
816 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
817 Ok(self.x509.to_pem()?)
818 }
819
820 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
821 match self.pkey {
822 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
823 _ => Err("No private key found".into()),
824 }
825 }
826 fn pem_extension(&self) -> &'static str {
827 "_cert.pem"
828 }
829}
830
831pub trait CertificateX509Common: X509Common {}
833impl CertificateX509Common for Certificate {}
834
835impl Certificate {
836 pub fn load_cert_and_key<C: AsRef<Path>, K: AsRef<Path>>(
839 cert_pem_file: C,
840 key_pem_file: K,
841 ) -> Result<Self, Box<dyn std::error::Error>> {
842 let cert_pem = std::fs::read(cert_pem_file)?;
843 let key_pem = std::fs::read(key_pem_file)?;
844 let cert = X509::from_pem(&cert_pem)?;
845 let pkey = PKey::private_key_from_pem(&key_pem)?;
846 Ok(Self {
847 x509: cert,
848 pkey: Some(pkey),
849 })
850 }
851}
852
853pub trait BuilderCommon {
855 fn set_common_name(&mut self, name: &str);
856 fn set_signer(&mut self, signer: &str);
857 fn set_country_name(&mut self, name: &str);
858 fn set_state_province(&mut self, name: &str);
859 fn set_organization(&mut self, name: &str);
860 fn set_organization_unit(&mut self, name: &str);
861 fn set_alternative_names(&mut self, alternative_names: Vec<&str>);
862 fn set_locality_time(&mut self, locality_time: &str);
863 fn set_key_type(&mut self, key_type: KeyType);
864 fn set_signature_alg(&mut self, signature_alg: HashAlg);
865 fn set_key_usage(&mut self, key_usage: HashSet<Usage>);
866}
867
868#[derive(Debug)]
870pub struct BuilderFields {
871 common_name: String,
872 signer: Option<String>, alternative_names: HashSet<String>,
874 organization_unit: String,
875 country_name: String,
876 state_province: String,
877 organization: String,
878 locality_time: String,
879 key_type: Option<KeyType>,
880 signature_alg: Option<HashAlg>,
881 usage: Option<HashSet<Usage>>,
882}
883impl BuilderCommon for BuilderFields {
884 fn set_common_name(&mut self, common_name: &str) {
886 self.common_name = common_name.into();
887 self.alternative_names.insert(String::from(common_name));
888 }
889 fn set_alternative_names(&mut self, alternative_names: Vec<&str>) {
891 self.alternative_names
892 .extend(vec_str_to_hs!(alternative_names));
893 }
894 fn set_signer(&mut self, signer: &str) {
896 self.signer = Some(signer.into());
897 }
898 fn set_country_name(&mut self, country_name: &str) {
900 self.country_name = country_name.into();
901 }
902 fn set_state_province(&mut self, state_province: &str) {
904 self.state_province = state_province.into();
905 }
906 fn set_organization(&mut self, organization: &str) {
908 self.organization = organization.into();
909 }
910 fn set_organization_unit(&mut self, organization_unit: &str) {
912 self.organization_unit = organization_unit.into();
913 }
914 fn set_locality_time(&mut self, locality_time: &str) {
916 self.locality_time = locality_time.into();
917 }
918 fn set_key_type(&mut self, key_type: KeyType) {
920 self.key_type = Some(key_type);
921 }
922 fn set_signature_alg(&mut self, signature_alg: HashAlg) {
924 self.signature_alg = Some(signature_alg);
925 }
926
927 fn set_key_usage(&mut self, key_usage: HashSet<Usage>) {
929 match &mut self.usage {
930 Some(existing_usage) => {
931 existing_usage.extend(key_usage);
932 }
933 None => {
934 self.usage = Some(key_usage);
935 }
936 };
937 }
938}
939
940impl Default for BuilderFields {
941 fn default() -> Self {
943 Self {
944 common_name: Default::default(),
945 signer: Default::default(),
946 alternative_names: Default::default(),
947 country_name: Default::default(),
948 state_province: Default::default(),
949 organization: Default::default(),
950 organization_unit: Default::default(),
951 locality_time: Default::default(),
952 key_type: Default::default(),
953 signature_alg: Default::default(),
954 usage: Default::default(),
955 }
956 }
957}
958pub trait UseesBuilderFields: Sized {
960 fn fields_mut(&mut self) -> &mut BuilderFields;
962
963 fn common_name(mut self, common_name: &str) -> Self {
967 self.fields_mut().set_common_name(common_name);
968 self
969 }
970 fn signer(mut self, signer: &str) -> Self {
972 self.fields_mut().set_signer(signer);
973 self
974 }
975 fn alternative_names(mut self, alternative_names: Vec<&str>) -> Self {
979 self.fields_mut().set_alternative_names(alternative_names);
980 self
981 }
982 fn country_name(mut self, country_name: &str) -> Self {
984 self.fields_mut().set_country_name(country_name);
985 self
986 }
987 fn state_province(mut self, state_province: &str) -> Self {
989 self.fields_mut().set_state_province(state_province);
990 self
991 }
992 fn organization(mut self, organization: &str) -> Self {
994 self.fields_mut().set_organization(organization);
995 self
996 }
997 fn locality_time(mut self, locality_time: &str) -> Self {
999 self.fields_mut().set_locality_time(locality_time);
1000 self
1001 }
1002 fn key_type(mut self, key_type: KeyType) -> Self {
1004 self.fields_mut().set_key_type(key_type);
1005 self
1006 }
1007 fn signature_alg(mut self, signature_alg: HashAlg) -> Self {
1009 self.fields_mut().set_signature_alg(signature_alg);
1010 self
1011 }
1012
1013 fn key_usage(mut self, key_usage: HashSet<Usage>) -> Self {
1017 self.fields_mut().set_key_usage(key_usage);
1018 self
1019 }
1020}
1021
1022pub struct CertBuilder {
1024 fields: BuilderFields,
1025 valid_from: Asn1Time,
1026 valid_to: Asn1Time,
1027 policies: Vec<CertificatePolicy>,
1028 ca: bool,
1029}
1030
1031impl UseesBuilderFields for CertBuilder {
1032 fn fields_mut(&mut self) -> &mut BuilderFields {
1033 &mut self.fields
1034 }
1035}
1036impl Default for CertBuilder {
1037 fn default() -> Self {
1038 Self::new()
1039 }
1040}
1041
1042impl CertBuilder {
1043 pub fn new() -> Self {
1045 Self {
1046 fields: BuilderFields::default(),
1047 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), ca: false,
1050 policies: Default::default(),
1051 }
1052 }
1053 pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
1058 self.policies = policies;
1059 self
1060 }
1061 pub fn valid_from(mut self, valid_from: &str) -> Self {
1066 self.valid_from =
1067 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
1068 self
1069 }
1070 pub fn valid_to(mut self, valid_to: &str) -> Self {
1075 self.valid_to =
1076 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
1077 self
1078 }
1079 pub fn is_ca(mut self, ca: bool) -> Self {
1084 if ca {
1085 self.ca = ca;
1086 self.fields
1087 .set_key_usage(HashSet::from([Usage::certsign, Usage::crlsign]));
1088 }
1089 self
1090 }
1091
1092 pub fn build_and_self_sign(&self) -> Result<Certificate, Box<dyn std::error::Error>> {
1094 let (mut builder, pkey) = self.prepare_x509_builder(None)?;
1095
1096 #[cfg(feature = "pqc")]
1099 if is_mlkem_pkey(&pkey) {
1100 return Err("ML-KEM (FIPS 203) is a key-encapsulation key and cannot \
1101 produce signatures, so it cannot self-sign a certificate. Issue an \
1102 ML-KEM certificate with CertBuilder::build_and_sign() using a signing \
1103 CA (e.g. an ML-DSA or ECDSA CA) instead."
1104 .into());
1105 }
1106
1107 let ca_cert: X509 = if is_digestless_key(&pkey) {
1108 let build_cert = builder.build();
1109 sign_certificate_digestless(&build_cert, &pkey)
1110 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1111 build_cert
1112 } else {
1113 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
1114 builder.build()
1115 };
1116
1117 Ok(Certificate {
1118 x509: ca_cert,
1119 pkey: Some(pkey),
1120 })
1121 }
1122 pub fn build_and_sign(
1124 &self,
1125 signer: &Certificate,
1126 ) -> Result<Certificate, Box<dyn std::error::Error>> {
1127 let can_sign = can_sign_cert(&signer.x509)?;
1128 if !can_sign {
1129 let err = format!(
1130 "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
1131 signer.x509.issuer_name()
1132 );
1133 return Err(err.into());
1134 }
1135 let (mut builder, pkey) = self.prepare_x509_builder(Some(signer))?;
1136 let signer_key = signer.pkey.as_ref().unwrap();
1137 let cert: X509 = if is_digestless_key(signer_key) {
1138 let build_cert = builder.build();
1139 sign_certificate_digestless(&build_cert, signer_key)
1140 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1141 build_cert
1142 } else {
1143 builder.sign(signer_key, select_hash(&self.fields.signature_alg))?;
1144 builder.build()
1145 };
1146 Ok(Certificate {
1147 x509: cert,
1148 pkey: Some(pkey),
1149 })
1150 }
1151
1152 fn prepare_x509_builder(
1153 &self,
1154 signer: Option<&Certificate>,
1155 ) -> Result<(X509Builder, PKey<Private>), Box<dyn std::error::Error>> {
1156 let mut name_builder = X509NameBuilder::new()?;
1157 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1158 if !self.fields.country_name.trim().is_empty() {
1159 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1160 }
1161 if !self.fields.state_province.trim().is_empty() {
1162 name_builder
1163 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1164 }
1165 if !self.fields.locality_time.trim().is_empty() {
1166 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1167 }
1168 if !self.fields.organization.trim().is_empty() {
1169 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1170 }
1171 if !self.fields.organization_unit.trim().is_empty() {
1172 name_builder
1173 .append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, &self.fields.organization_unit)?;
1174 }
1175
1176 let name = name_builder.build();
1177
1178 let mut builder = X509::builder()?;
1179 builder.set_version(2)?;
1180
1181 let serial_number = {
1182 let mut serial = BigNum::new()?;
1183 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
1184 serial.to_asn1_integer()?
1185 };
1186
1187 let pkey = select_key(&self.fields.key_type).unwrap();
1188 builder.set_serial_number(&serial_number)?;
1189 builder.set_subject_name(&name)?;
1190 builder.set_pubkey(&pkey)?;
1191 builder.set_not_before(&self.valid_from)?;
1192 builder.set_not_after(&self.valid_to)?;
1193 match signer {
1194 Some(signer) => builder.set_issuer_name(signer.x509.subject_name())?,
1195 None => builder.set_issuer_name(&name)?,
1196 }
1197
1198 let key_usage = self.fields.usage.clone().unwrap_or_default();
1199 append_certificate_policies(&mut builder, &self.policies)?;
1200 #[cfg(feature = "pqc")]
1203 {
1204 if is_pqc_pkey(&pkey) && key_usage.contains(&Usage::encipherment) {
1205 return Err("keyEncipherment (Usage::encipherment) is not valid for a \
1206 post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
1207 signature-only and cannot perform key encipherment. Use Usage::signature \
1208 (and certsign/crlsign for a CA) instead."
1209 .into());
1210 }
1211 }
1212
1213 #[cfg(feature = "pqc")]
1218 {
1219 if is_mlkem_pkey(&pkey)
1220 && !key_usage.is_empty()
1221 && key_usage != HashSet::from([Usage::encipherment])
1222 {
1223 return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
1224 (Usage::encipherment) in its KeyUsage — no other bit is permitted \
1225 (no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
1226 crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
1227 .into());
1228 }
1229 }
1230
1231 if self.ca {
1232 builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
1233 } else {
1234 builder.append_extension(BasicConstraints::new().build()?)?;
1235 }
1236
1237 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1238 if tracked_key_usage.is_used() {
1239 builder.append_extension(tracked_key_usage.into_inner().build()?)?;
1240 }
1241 if tracked_extended_key_usage.is_used() {
1242 builder.append_extension(tracked_extended_key_usage.into_inner().build()?)?;
1243 }
1244
1245 let mut san = SubjectAlternativeName::new();
1246 for s in &self.fields.alternative_names {
1247 san.dns(s);
1248 }
1249 if let Some(signer_cert) = signer {
1250 builder.append_extension(
1251 san.build(&builder.x509v3_context(Some(&signer_cert.x509), None))?,
1252 )?;
1253 if signer_cert.x509.subject_key_id().is_some() {
1254 let aki = AuthorityKeyIdentifier::new()
1255 .keyid(true)
1256 .issuer(false)
1257 .build(&builder.x509v3_context(Some(&signer_cert.x509), None))?;
1258 builder.append_extension(aki)?;
1259 }
1260 } else {
1261 builder.append_extension(san.build(&builder.x509v3_context(None, None))?)?;
1263 let oid = Asn1Object::from_str("2.5.29.35")?; let pubkey_der = pkey.public_key_to_der()?;
1265 let aki_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1266 let der_encoded = yasna::construct_der(|writer| {
1267 writer.write_sequence(|writer| {
1268 writer
1269 .next()
1270 .write_tagged_implicit(yasna::Tag::context(0), |writer| {
1271 writer.write_bytes(aki_hash.as_ref());
1272 })
1273 })
1274 });
1275 let aki_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1276 let ext = X509Extension::new_from_der(oid.as_ref(), false, &aki_asn1)?;
1277 builder.append_extension(ext)?;
1278 }
1279 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = pkey.public_key_to_der()?;
1289 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1290 let der_encoded = yasna::construct_der(|writer| {
1291 writer.write_bytes(ski_hash.as_ref());
1292 });
1293 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1294 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
1295 builder.append_extension(ext)?;
1296
1297 Ok((builder, pkey))
1298 }
1299}
1300
1301pub struct CsrBuilder {
1303 fields: BuilderFields,
1304}
1305impl UseesBuilderFields for CsrBuilder {
1306 fn fields_mut(&mut self) -> &mut BuilderFields {
1307 &mut self.fields
1308 }
1309}
1310impl Default for CsrBuilder {
1311 fn default() -> Self {
1312 Self::new()
1313 }
1314}
1315
1316impl CsrBuilder {
1317 pub fn new() -> Self {
1319 Self {
1320 fields: BuilderFields::default(),
1321 }
1322 }
1323
1324 pub fn certificate_signing_request(self) -> Result<Csr, Box<dyn std::error::Error>> {
1357 let mut name_builder = X509NameBuilder::new()?;
1358 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1359 if !self.fields.country_name.trim().is_empty() {
1360 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1361 }
1362 if !self.fields.state_province.trim().is_empty() {
1363 name_builder
1364 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1365 }
1366 if !self.fields.locality_time.trim().is_empty() {
1367 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1368 }
1369 if !self.fields.organization.trim().is_empty() {
1370 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1371 }
1372 let name = name_builder.build();
1373 let mut builder = X509ReqBuilder::new()?;
1374 builder.set_version(0)?;
1375 builder.set_subject_name(&name)?;
1376 let pkey = select_key(&self.fields.key_type).unwrap();
1377 builder.set_pubkey(&pkey)?;
1378 let key_usage = self.fields.usage.clone().unwrap_or_default();
1379
1380 #[cfg(feature = "pqc")]
1383 {
1384 if is_pqc_pkey(&pkey) && key_usage.contains(&Usage::encipherment) {
1385 return Err("keyEncipherment (Usage::encipherment) is not valid for a \
1386 post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
1387 signature-only and cannot perform key encipherment. Use Usage::signature \
1388 instead."
1389 .into());
1390 }
1391 }
1392
1393 #[cfg(feature = "pqc")]
1397 {
1398 if is_mlkem_pkey(&pkey)
1399 && !key_usage.is_empty()
1400 && key_usage != HashSet::from([Usage::encipherment])
1401 {
1402 return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
1403 (Usage::encipherment) in its KeyUsage — no other bit is permitted \
1404 (no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
1405 crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
1406 .into());
1407 }
1408 }
1409
1410 #[cfg(feature = "pqc")]
1413 if is_mlkem_pkey(&pkey) {
1414 return Err(
1415 "ML-KEM (FIPS 203) is a key-encapsulation key and cannot sign \
1416 a CSR (PKCS#10 requires a self-signature for proof-of-possession). \
1417 Issue the ML-KEM certificate directly with CertBuilder::build_and_sign() \
1418 using a signing CA instead."
1419 .into(),
1420 );
1421 }
1422
1423 let mut extensions = Stack::new()?;
1424
1425 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1426 if tracked_key_usage.is_used() {
1427 extensions.push(tracked_key_usage.inner.build()?)?;
1428 }
1429 if tracked_extended_key_usage.is_used() {
1430 extensions.push(tracked_extended_key_usage.inner.build()?)?;
1431 }
1432
1433 let mut san = SubjectAlternativeName::new();
1434 for s in &self.fields.alternative_names {
1435 san.dns(s);
1436 }
1437 extensions.push(san.build(&builder.x509v3_context(None))?)?;
1438
1439 builder.add_extensions(&extensions)?;
1440 let csr: X509Req = if is_digestless_key(&pkey) {
1441 let builder_csr = builder.build();
1442 sign_x509_req_digestless(&builder_csr, &pkey)
1443 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1444 builder_csr
1445 } else {
1446 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
1447 builder.build()
1448 };
1449 Ok(Csr {
1450 csr,
1451 pkey: Some(pkey),
1452 })
1453 }
1454}
1455struct TrackedExtendedKeyUsage {
1456 inner: ExtendedKeyUsage,
1457 used: bool,
1458}
1459
1460impl TrackedExtendedKeyUsage {
1461 fn new() -> Self {
1462 Self {
1463 inner: ExtendedKeyUsage::new(),
1464 used: false,
1465 }
1466 }
1467
1468 fn client_auth(&mut self) {
1469 self.inner.client_auth();
1470 self.used = true;
1471 }
1472
1473 fn server_auth(&mut self) {
1474 self.inner.server_auth();
1475 self.used = true;
1476 }
1477
1478 fn is_used(&self) -> bool {
1479 self.used
1480 }
1481
1482 fn into_inner(self) -> ExtendedKeyUsage {
1483 self.inner
1484 }
1485}
1486
1487struct TrackedKeyUsage {
1488 inner: KeyUsage,
1489 used: bool,
1490}
1491
1492impl TrackedKeyUsage {
1493 fn new() -> Self {
1494 Self {
1495 inner: KeyUsage::new(),
1496 used: false,
1497 }
1498 }
1499
1500 fn digital_signature(&mut self) {
1501 self.inner.digital_signature();
1502 self.used = true;
1503 }
1504
1505 fn non_repudiation(&mut self) {
1506 self.inner.non_repudiation();
1507 self.used = true;
1508 }
1509
1510 fn key_encipherment(&mut self) {
1511 self.inner.key_encipherment();
1512 self.used = true;
1513 }
1514
1515 fn key_cert_sign(&mut self) {
1516 self.inner.key_cert_sign();
1517 self.used = true;
1518 }
1519
1520 fn crl_sign(&mut self) {
1521 self.inner.crl_sign();
1522 self.used = true;
1523 }
1524
1525 fn is_used(&self) -> bool {
1526 self.used
1527 }
1528
1529 fn into_inner(self) -> KeyUsage {
1530 self.inner
1531 }
1532}
1533fn parse_oid(dotted: &str) -> Result<ObjectIdentifier, Box<dyn std::error::Error>> {
1535 let components = dotted
1536 .split('.')
1537 .map(|c| c.parse::<u64>())
1538 .collect::<Result<Vec<u64>, _>>()
1539 .map_err(|_| format!("invalid policy OID: {dotted}"))?;
1540 Ok(ObjectIdentifier::new(components))
1541}
1542
1543fn append_certificate_policies(
1544 builder: &mut X509Builder,
1545 policies: &[CertificatePolicy],
1546) -> Result<(), Box<dyn std::error::Error>> {
1547 if policies.is_empty() {
1548 return Ok(());
1549 }
1550 let oids = policies
1551 .iter()
1552 .map(|p| parse_oid(p.oid()))
1553 .collect::<Result<Vec<_>, _>>()?;
1554 let der = yasna::construct_der(|w| {
1555 w.write_sequence(|seq| {
1556 for oid in &oids {
1557 seq.next().write_sequence(|pi| pi.next().write_oid(oid));
1558 }
1559 });
1560 });
1561 let oid = Asn1Object::from_str("2.5.29.32")?;
1562 let value = Asn1OctetString::new_from_bytes(&der)?;
1563 builder.append_extension(X509Extension::new_from_der(oid.as_ref(), false, &value)?)?;
1564 Ok(())
1565}
1566
1567pub fn verify_cert(
1571 cert: &X509,
1572 ca: &X509,
1573 cert_chain: Vec<&X509>,
1574) -> Result<bool, Box<dyn std::error::Error>> {
1575 let mut store_builder = X509StoreBuilder::new()?;
1577 store_builder.add_cert(ca.clone())?;
1578 let store = store_builder.build();
1579
1580 let mut ctx = X509StoreContext::new()?;
1582 let mut chain = Stack::new()?; cert_chain
1584 .iter()
1585 .try_for_each(|c| chain.push((*c).clone()))?;
1586 ctx.init(&store, cert, &chain, |c| c.verify_cert())?;
1587 let verified = ctx.error() == openssl::x509::X509VerifyResult::OK;
1588 Ok(verified)
1589}
1590
1591pub fn create_cert_chain_from_cert_list(
1600 certs: Vec<X509>,
1601) -> Result<Vec<X509>, Box<dyn std::error::Error>> {
1602 let mut subject_map: HashMap<Vec<u8>, X509> = HashMap::new();
1603 let mut issuer_map: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
1604
1605 for cert in &certs {
1606 let subject = cert.subject_name().to_der()?;
1607 let issuer = cert.issuer_name().to_der()?;
1608 subject_map.insert(subject.clone(), cert.clone());
1609 issuer_map.insert(subject, issuer);
1610 }
1611
1612 let all_issuers: Vec<Vec<u8>> = issuer_map.values().cloned().collect();
1614 let leaf_certs: Vec<X509> = subject_map
1615 .iter()
1616 .filter(|(subject, _)| !all_issuers.contains(subject))
1617 .map(|(_, cert)| cert.clone())
1618 .collect();
1619
1620 let mut longest_chain = Vec::new();
1622
1623 for leaf in leaf_certs {
1624 let mut chain = vec![leaf.clone()];
1625 let mut current_cert = leaf;
1626
1627 while let Ok(issuer_der) = current_cert.issuer_name().to_der() {
1628 if let Some(parent_cert) = subject_map.get(&issuer_der) {
1629 if parent_cert.subject_name().to_der()? == current_cert.subject_name().to_der()? {
1630 break; }
1632 chain.push(parent_cert.clone());
1633 current_cert = parent_cert.clone();
1634 } else {
1635 break; }
1637 }
1638
1639 if chain.len() > longest_chain.len() {
1640 longest_chain = chain;
1641 }
1642 }
1643
1644 longest_chain.reverse();
1646 Ok(longest_chain)
1647}
1648
1649fn create_asn1_time_from_date(date_str: &str) -> Result<Asn1Time, Box<dyn std::error::Error>> {
1650 let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")?;
1651 let datetime = NaiveDateTime::new(date, chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap());
1652 let utc_datetime = Utc.from_utc_datetime(&datetime);
1653 let asn1_time_str = utc_datetime.format("%Y%m%d%H%M%SZ").to_string();
1654 let asn1_time = Asn1Time::from_str(&asn1_time_str)?;
1655 Ok(asn1_time)
1656}
1657
1658fn select_key(key_type: &Option<KeyType>) -> Result<PKey<Private>, ErrorStack> {
1659 match key_type {
1660 Some(KeyType::P224) => {
1661 let group = EcGroup::from_curve_name(Nid::SECP224R1)?;
1662 let ec_key = EcKey::generate(&group)?;
1663 PKey::from_ec_key(ec_key)
1664 }
1665 Some(KeyType::P256) => {
1666 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
1667 let ec_key = EcKey::generate(&group)?;
1668 PKey::from_ec_key(ec_key)
1669 }
1670 Some(KeyType::P384) => {
1671 let group = EcGroup::from_curve_name(Nid::SECP384R1)?;
1672 let ec_key = EcKey::generate(&group)?;
1673 PKey::from_ec_key(ec_key)
1674 }
1675 Some(KeyType::P521) => {
1676 let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
1677 let ec_key = EcKey::generate(&group)?;
1678 PKey::from_ec_key(ec_key)
1679 }
1680 Some(KeyType::Ed25519) => PKey::generate_ed25519(),
1681 #[cfg(feature = "pqc")]
1682 Some(KeyType::MlDsa44) => generate_pqc_key("ML-DSA-44"),
1683 #[cfg(feature = "pqc")]
1684 Some(KeyType::MlDsa65) => generate_pqc_key("ML-DSA-65"),
1685 #[cfg(feature = "pqc")]
1686 Some(KeyType::MlDsa87) => generate_pqc_key("ML-DSA-87"),
1687 #[cfg(feature = "pqc")]
1688 Some(KeyType::SlhDsaSha2_128s) => generate_pqc_key("SLH-DSA-SHA2-128s"),
1689 #[cfg(feature = "pqc")]
1690 Some(KeyType::SlhDsaSha2_192s) => generate_pqc_key("SLH-DSA-SHA2-192s"),
1691 #[cfg(feature = "pqc")]
1692 Some(KeyType::SlhDsaSha2_256s) => generate_pqc_key("SLH-DSA-SHA2-256s"),
1693 #[cfg(feature = "pqc")]
1694 Some(KeyType::MlKem512) => generate_pqc_key("ML-KEM-512"),
1695 #[cfg(feature = "pqc")]
1696 Some(KeyType::MlKem768) => generate_pqc_key("ML-KEM-768"),
1697 #[cfg(feature = "pqc")]
1698 Some(KeyType::MlKem1024) => generate_pqc_key("ML-KEM-1024"),
1699 Some(KeyType::RSA4096) => {
1700 let rsa = Rsa::generate(4096)?;
1701 PKey::from_rsa(rsa)
1702 }
1703 _ => {
1704 let rsa = Rsa::generate(2048)?;
1705 PKey::from_rsa(rsa)
1706 }
1707 }
1708}
1709
1710fn select_hash(hash_type: &Option<HashAlg>) -> MessageDigest {
1711 match hash_type {
1712 Some(HashAlg::SHA1) => MessageDigest::sha1(),
1713 Some(HashAlg::SHA384) => MessageDigest::sha384(),
1714 Some(HashAlg::SHA512) => MessageDigest::sha512(),
1715 _ => MessageDigest::sha256(),
1716 }
1717}
1718
1719fn get_key_usage(usage: &Option<HashSet<Usage>>) -> (TrackedKeyUsage, TrackedExtendedKeyUsage) {
1720 let mut ku = TrackedKeyUsage::new();
1721 let mut eku = TrackedExtendedKeyUsage::new();
1722 if let Some(usages) = usage {
1723 for u in usages {
1724 match u {
1725 Usage::contentcommitment => {
1726 ku.non_repudiation();
1727 }
1728 Usage::encipherment => {
1729 ku.key_encipherment();
1730 }
1731 Usage::certsign => {
1732 ku.key_cert_sign();
1733 }
1734 Usage::clientauth => {
1735 eku.client_auth();
1736 }
1737 Usage::signature => {
1738 ku.digital_signature();
1739 }
1740 Usage::crlsign => {
1741 ku.crl_sign();
1742 }
1743 Usage::serverauth => {
1744 eku.server_auth();
1745 }
1746 }
1747 }
1748 }
1749
1750 (ku, eku)
1751}
1752
1753fn can_sign_cert(cert: &X509) -> Result<bool, Box<dyn std::error::Error>> {
1754 let der = cert.to_der()?;
1755 let (_, parsed_cert) = parse_x509_certificate(&der)?;
1756
1757 let mut is_ca = false;
1758 let mut can_sign = false;
1759
1760 let now = Asn1Time::days_from_now(0)?;
1764 let valid_time = cert.not_before().compare(&now)? != std::cmp::Ordering::Greater
1765 && cert.not_after().compare(&now)? == std::cmp::Ordering::Greater;
1766
1767 for ext in parsed_cert.tbs_certificate.extensions().iter() {
1768 match &ext.parsed_extension() {
1769 ParsedExtension::BasicConstraints(bc) => {
1770 is_ca = bc.ca;
1771 }
1772 ParsedExtension::KeyUsage(ku) => {
1773 can_sign = ku.key_cert_sign();
1774 }
1775 _ => {}
1776 }
1777 }
1778 Ok(is_ca && can_sign && valid_time)
1779}
1780
1781#[cfg(test)]
1782mod tests {
1783 use super::*;
1784 use std::io::Write;
1785 use std::path::Path;
1786 use tempfile::NamedTempFile;
1787
1788 #[test]
1789 fn save_certificate() {
1790 let ca = CertBuilder::new().common_name("My Test Ca").is_ca(true);
1791 match ca.build_and_self_sign() {
1792 Ok(cert) => {
1793 let output_file = NamedTempFile::new().unwrap();
1794 let full_path = output_file.path();
1795 let parent_dir: &Path = full_path.parent().unwrap();
1796 let file_name: &str = full_path.file_name().unwrap().to_str().unwrap();
1797 cert.save(parent_dir, file_name)
1798 .expect("Failed to save certificate and key");
1799 let written_file_path = parent_dir.join(file_name);
1800 assert!(written_file_path.exists(), "File was not created");
1801 }
1802 Err(_) => panic!("Failed to creat certificate"),
1803 }
1804 }
1805
1806 #[test]
1807 fn read_certificate_and_key_from_file() {
1808 let cert_pem = b"-----BEGIN CERTIFICATE-----
1809MIICiDCCAemgAwIBAgIUO3+y1WZPRRNs8dmZZTUHMj6TdiowCgYIKoZIzj0EAwQw
1810WzETMBEGA1UEAwwKTXkgVGVzdCBDYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0
1811b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMQ8wDQYDVQQKDAZteSBvcmcwHhcN
1812MjUwNzA4MTExMzI2WhcNMjYwNzA4MTExMzI2WjBbMRMwEQYDVQQDDApNeSBUZXN0
1813IENhMQswCQYDVQQGEwJTRTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlT
1814dG9ja2hvbG0xDzANBgNVBAoMBm15IG9yZzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOB
1815hgAEADZXcQK2ihgVTJeGx5FKm1x+R+ivygIvMnkv03faq1LpLU3doKX38DEO/cSW
1816Ev5u+kcjspXeeDPhqJFC8rRAz4awAMk+D0mXEms7xpFPh0HmI6NNcJc5eJ/8ZsEJ
1817GH1a34y0Yn6259gqlwAh2Eh9Nx1579BAanRr8lr+n1tZ09T/9AQho0gwRjAMBgNV
1818HRMEBTADAQH/MAsGA1UdDwQEAwIBBjApBgNVHREEIjAgggZjYS5jb22CCnd3dy5j
1819YS5jb22CCk15IFRlc3QgQ2EwCgYIKoZIzj0EAwQDgYwAMIGIAkIB8NVUgRIuNXmJ
1820cLCQ74Ub7Dqo71S0+iCrZF1YyJA8/q65aqMCT54k5Yx7HRBUUVHbCEpDXRqGPsIH
1821frfe5OmS3qICQgDBn07o0CcyfoSEd+Xoj2+/RBuU0vo9lUP7TKj7tssBxzEQFoxX
1822eE1qT98UIe78FZ+zqjwZTN9MCSsatuim6pXvOA==
1823-----END CERTIFICATE-----";
1824
1825 let key_pem = b"-----BEGIN PRIVATE KEY-----
1826MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAgvZTeQgGysadAX0r
1827aZB5Lk4vjHy5iVuKdvcGdYt9NBvYx+Ib3Uk7vqMag7M1jyHL0Xf9uNtT2mxBmzBG
18283CF+EgOhgYkDgYYABAA2V3ECtooYFUyXhseRSptcfkfor8oCLzJ5L9N32qtS6S1N
18293aCl9/AxDv3ElhL+bvpHI7KV3ngz4aiRQvK0QM+GsADJPg9JlxJrO8aRT4dB5iOj
1830TXCXOXif/GbBCRh9Wt+MtGJ+tufYKpcAIdhIfTcdee/QQGp0a/Ja/p9bWdPU//QE
1831IQ==
1832-----END PRIVATE KEY-----";
1833
1834 let mut cert_file = NamedTempFile::new().expect("Failed to create temp cert file");
1835 let mut key_file = NamedTempFile::new().expect("Failed to create temp key file");
1836
1837 cert_file.write_all(cert_pem).expect("Failed to write cert");
1838 key_file.write_all(key_pem).expect("Failed to write key");
1839
1840 let result = Certificate::load_cert_and_key(cert_file.path(), key_file.path());
1841 assert!(
1842 result.is_ok(),
1843 "Failed to load cert and key: {:?}",
1844 result.err()
1845 );
1846 }
1847
1848 #[test]
1849 fn test_reading_csr_from_file() {
1850 let csr_data = b"-----BEGIN CERTIFICATE REQUEST-----
1851MIICzDCCAbQCAQAwXTEVMBMGA1UEAwwMZXhhbXBsZTIuY29tMQswCQYDVQQGEwJT
1852RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xDzANBgNV
1853BAoMBk15IG9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeAXpCG
1854hbIayESfdTzOO0DxIMsOAu4kUm0zF0W/+xDUHl6bGy3wlB9S9nzBG/qwqFZ27Om3
1855o4zrZ8K8DBx0ERWNuhMmr0Nx8QpAWBEyxOc08Gn4c3XVBBkRZSn4AIqr9DGtcUqW
1856tQZXvMGF6sRRljiEvOxO6zMzZKTGYwzIeQvH85cQ3uXsw0Kknsw/fcuywaAC8SS9
1857aqs4jiEIgzdhxdH2OVXBNGj4cjVhK309JiWFHS9XJLNV/PKC+F1nkaANQwbW5A4F
18589vya4js9gk8f4SfF1u+qOJEvsDvAb+1xdjXPRzf77eGh3rC4KgGWQ6WrWfW8PItF
1859BDg/jskq3bJXNL8CAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIM
1860ZXhhbXBsZTIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAHeeSW8C6SMVhWiMvPn7iz
1861FUHQedHRyPz6kTEfC01eNIbs0r4YghOAcm8PF67jncIXVrqgxo1uzq12qlV+0YYb
1862jps31IbQNOz0eFLYvij15ielmOYeQZZ/2vqaGi3geVobLc6Ki5tadnA/NhjTN33j
1863QcqDDic8riAOTbSQ6TH9KPTGJQOPk+taMpDGDHskIW0oME5iT2ewbhBHg6v/kSzy
1864tss2kBY5O7vo2COtbNcwX5Xp9S2LH9kVUKr0GIjuQjwbv5xl+GNdDey09W9EDACU
1865jcGV3++2wS4LN4h3CG4pWZ+LTXhm8ymhoWOapN95lfe3xLRAKFJwiLkGwS75++FW
1866-----END CERTIFICATE REQUEST-----";
1867 let mut csr_file = NamedTempFile::new().expect("Failed to create temp csr file");
1868 csr_file.write_all(csr_data).expect("Failed to write csr");
1869 let result = Csr::load_csr(csr_file.path());
1870 assert!(result.is_ok(), "Failed to load csr: {:?}", result.err());
1871 }
1872}