1use chrono::{DateTime, 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;
23
24use x509_parser::certification_request::X509CertificationRequest;
25use x509_parser::extensions::ParsedExtension;
26use x509_parser::parse_x509_certificate;
27use x509_parser::prelude::FromDer;
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
53fn sign_certificate_digestless(
70 cert: &X509,
71 pkey: &PKey<openssl::pkey::Private>,
72) -> Result<(), String> {
73 if !is_digestless_key(pkey) {
74 return Err("sign_certificate_digestless called with non-digestless key".to_string());
75 }
76 let cert_ptr = cert.as_ptr();
77 let pkey_ptr = pkey.as_ptr();
78
79 if pkey.id() == Id::ED25519 {
80 let result = unsafe { X509_sign(cert_ptr, pkey_ptr, std::ptr::null()) };
81 return if result > 0 {
82 Ok(())
83 } else {
84 Err("Failed to sign certificate with Ed25519".to_string())
85 };
86 }
87
88 let result = unsafe {
92 let ctx = openssl_sys::EVP_MD_CTX_new();
93 if ctx.is_null() {
94 return Err("EVP_MD_CTX_new returned NULL".to_string());
95 }
96 let init = openssl_sys::EVP_DigestSignInit(
97 ctx,
98 std::ptr::null_mut(),
99 std::ptr::null(),
100 std::ptr::null_mut(),
101 pkey_ptr,
102 );
103 if init <= 0 {
104 openssl_sys::EVP_MD_CTX_free(ctx);
105 return Err("EVP_DigestSignInit failed for PQC key".to_string());
106 }
107 let rc = X509_sign_ctx(cert_ptr, ctx);
108 openssl_sys::EVP_MD_CTX_free(ctx);
109 rc
110 };
111
112 if result > 0 {
113 Ok(())
114 } else {
115 Err("X509_sign_ctx failed for PQC key".to_string())
116 }
117}
118
119fn sign_x509_req_digestless(req: &X509Req, pkey: &PKey<Private>) -> Result<(), String> {
123 if !is_digestless_key(pkey) {
124 return Err("sign_x509_req_digestless called with non-digestless key".to_string());
125 }
126 let req_ptr = req.as_ptr();
127 let pkey_ptr = pkey.as_ptr();
128
129 if pkey.id() == Id::ED25519 {
130 let result = unsafe { X509_REQ_sign(req_ptr, pkey_ptr, std::ptr::null()) };
131 return if result > 0 {
132 Ok(())
133 } else {
134 Err("Failed to sign X509Req with Ed25519".to_string())
135 };
136 }
137
138 let result = unsafe {
139 let ctx = openssl_sys::EVP_MD_CTX_new();
140 if ctx.is_null() {
141 return Err("EVP_MD_CTX_new returned NULL".to_string());
142 }
143 let init = openssl_sys::EVP_DigestSignInit(
144 ctx,
145 std::ptr::null_mut(),
146 std::ptr::null(),
147 std::ptr::null_mut(),
148 pkey_ptr,
149 );
150 if init <= 0 {
151 openssl_sys::EVP_MD_CTX_free(ctx);
152 return Err("EVP_DigestSignInit failed for PQC key".to_string());
153 }
154 let rc = X509_REQ_sign_ctx(req_ptr, ctx);
155 openssl_sys::EVP_MD_CTX_free(ctx);
156 rc
157 };
158
159 if result > 0 {
160 Ok(())
161 } else {
162 Err("X509_REQ_sign_ctx failed for PQC key".to_string())
163 }
164}
165
166pub(crate) fn is_digestless_key(pkey: &PKey<Private>) -> bool {
170 if pkey.id() == Id::ED25519 {
171 return true;
172 }
173 #[cfg(feature = "pqc")]
174 {
175 return is_pqc_pkey(pkey);
176 }
177 #[allow(unreachable_code)]
178 false
179}
180
181#[cfg(feature = "pqc")]
182fn is_pqc_pkey(pkey: &PKey<Private>) -> bool {
183 use std::ffi::CString;
184 use std::sync::OnceLock;
185
186 static NAMES: OnceLock<[CString; 6]> = OnceLock::new();
188 let names = NAMES.get_or_init(|| {
189 [
190 CString::new("ML-DSA-44").unwrap(),
191 CString::new("ML-DSA-65").unwrap(),
192 CString::new("ML-DSA-87").unwrap(),
193 CString::new("SLH-DSA-SHA2-128s").unwrap(),
194 CString::new("SLH-DSA-SHA2-192s").unwrap(),
195 CString::new("SLH-DSA-SHA2-256s").unwrap(),
196 ]
197 });
198 use foreign_types::ForeignType;
199 let ptr = pkey.as_ptr();
200 names
201 .iter()
202 .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
205}
206
207#[cfg(feature = "pqc")]
208mod pqc {
209 use foreign_types::ForeignType;
210 use openssl::error::ErrorStack;
211 use openssl::pkey::{PKey, Private};
212 use std::ffi::CString;
213
214 unsafe extern "C" {
215 fn EVP_PKEY_CTX_new_from_name(
216 libctx: *mut std::ffi::c_void,
217 name: *const std::os::raw::c_char,
218 propquery: *const std::os::raw::c_char,
219 ) -> *mut openssl_sys::EVP_PKEY_CTX;
220 fn EVP_PKEY_keygen_init(ctx: *mut openssl_sys::EVP_PKEY_CTX) -> std::os::raw::c_int;
221 fn EVP_PKEY_generate(
222 ctx: *mut openssl_sys::EVP_PKEY_CTX,
223 ppkey: *mut *mut openssl_sys::EVP_PKEY,
224 ) -> std::os::raw::c_int;
225 fn EVP_PKEY_CTX_free(ctx: *mut openssl_sys::EVP_PKEY_CTX);
226 pub fn EVP_PKEY_is_a(
230 pkey: *mut openssl_sys::EVP_PKEY,
231 name: *const std::os::raw::c_char,
232 ) -> std::os::raw::c_int;
233 }
234
235 struct PkeyCtx(*mut openssl_sys::EVP_PKEY_CTX);
237
238 impl Drop for PkeyCtx {
239 fn drop(&mut self) {
240 if !self.0.is_null() {
241 unsafe { EVP_PKEY_CTX_free(self.0) }
242 }
243 }
244 }
245
246 pub(super) fn generate_pqc_key(alg_name: &str) -> Result<PKey<Private>, ErrorStack> {
256 let cname = CString::new(alg_name).expect("alg_name contains interior NUL");
257
258 let ctx_ptr = unsafe {
261 EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), cname.as_ptr(), std::ptr::null())
262 };
263 if ctx_ptr.is_null() {
264 return Err(ErrorStack::get());
265 }
266 let ctx = PkeyCtx(ctx_ptr);
267
268 if unsafe { EVP_PKEY_keygen_init(ctx.0) } <= 0 {
269 return Err(ErrorStack::get());
270 }
271
272 let mut pkey_ptr: *mut openssl_sys::EVP_PKEY = std::ptr::null_mut();
273 if unsafe { EVP_PKEY_generate(ctx.0, &mut pkey_ptr) } <= 0 {
274 return Err(ErrorStack::get());
275 }
276 if pkey_ptr.is_null() {
277 return Err(ErrorStack::get());
278 }
279
280 Ok(unsafe { PKey::<Private>::from_ptr(pkey_ptr) })
283 }
284}
285
286#[cfg(feature = "pqc")]
287use pqc::generate_pqc_key;
288
289macro_rules! vec_str_to_hs {
290 ($vec:expr) => {
291 $vec.iter()
292 .map(|s| s.to_string())
293 .collect::<HashSet<String>>()
294 };
295}
296#[derive(Debug, Clone, PartialEq)]
298pub enum KeyType {
299 RSA2048,
301 RSA4096,
303 P224,
305 P256,
307 P384,
309 P521,
311 Ed25519,
313 #[cfg(feature = "pqc")]
315 MlDsa44,
316 #[cfg(feature = "pqc")]
318 MlDsa65,
319 #[cfg(feature = "pqc")]
321 MlDsa87,
322 #[cfg(feature = "pqc")]
324 SlhDsaSha2_128s,
325 #[cfg(feature = "pqc")]
327 SlhDsaSha2_192s,
328 #[cfg(feature = "pqc")]
330 SlhDsaSha2_256s,
331}
332#[derive(Debug, Clone)]
334pub enum HashAlg {
335 SHA1,
337 SHA256,
339 SHA384,
341 SHA512,
343}
344#[allow(non_camel_case_types)]
346#[derive(Hash, Eq, PartialEq, Debug, Clone)]
347pub enum Usage {
348 certsign,
350 crlsign,
352 encipherment,
354 clientauth,
356 serverauth,
358 signature,
360 contentcommitment,
362}
363
364pub trait X509Parts {
366 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
371 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
376 fn pem_extension(&self) -> &'static str;
381}
382
383pub trait X509Common {
385 fn save<P: AsRef<Path>, F: AsRef<Path>>(
398 &self,
399 path: P,
400 filename: F,
401 ) -> Result<(), Box<dyn std::error::Error>>;
402}
403
404impl<T: X509Parts> X509Common for T {
413 fn save<P: AsRef<Path>, F: AsRef<Path>>(
424 &self,
425 path: P,
426 filename: F,
427 ) -> Result<(), Box<dyn std::error::Error>> {
428 create_dir_all(&path)?;
429
430 let os_file = filename
431 .as_ref()
432 .file_name()
433 .ok_or("Failed to extract file name")?;
434
435 let write_file = |suffix: &str, content: &[u8]| -> Result<(), Box<dyn std::error::Error>> {
436 let mut new_name = os_file.to_os_string();
437 new_name.push(suffix);
438 let full_path = path.as_ref().join(new_name);
439 let mut file = File::create(full_path)?;
440 file.write_all(content)?;
441 Ok(())
442 };
443 if let Ok(ref key) = self.get_private_key() {
444 write_file("_pkey.pem", key)?;
445 }
446 write_file(self.pem_extension(), &self.get_pem()?)?;
447 Ok(())
448 }
449}
450pub struct Csr {
452 pub csr: X509Req,
454 pub pkey: Option<PKey<Private>>,
458}
459
460impl X509Parts for Csr {
461 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
462 Ok(self.csr.to_pem()?)
463 }
464
465 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
466 match self.pkey {
467 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
468 _ => Err("No private key found".into()),
469 }
470 }
471 fn pem_extension(&self) -> &'static str {
472 "_csr.pem"
473 }
474}
475pub trait CsrX509Common: X509Common {}
477impl CsrX509Common for Csr {}
478
479pub struct CsrOptions {
481 valid_to: Asn1Time,
482 valid_from: Asn1Time,
483 ca: bool,
484}
485impl Default for CsrOptions {
486 fn default() -> Self {
487 Self::new()
488 }
489}
490
491impl CsrOptions {
492 pub fn new() -> Self {
497 Self {
498 ca: false,
499 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), }
502 }
503
504 pub fn valid_from(mut self, valid_from: &str) -> Self {
509 self.valid_from =
510 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
511 self
512 }
513
514 pub fn valid_to(mut self, valid_to: &str) -> Self {
519 self.valid_to =
520 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
521 self
522 }
523
524 pub fn is_ca(mut self, ca: bool) -> Self {
529 self.ca = ca;
530 self
531 }
532}
533impl Csr {
534 pub fn load_csr<C: AsRef<Path>>(csr_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
536 let cert_pem = std::fs::read(csr_pem_file)?;
537 let cs_req = X509Req::from_pem(&cert_pem)?;
538 Ok(Self {
539 csr: cs_req,
540 pkey: None,
541 })
542 }
543 pub fn build_signed_certificate(
545 &self,
546 signer: &Certificate,
547 options: CsrOptions,
548 ) -> Result<Certificate, Box<dyn std::error::Error>> {
549 let can_sign = can_sign_cert(&signer.x509)?;
550 if !can_sign {
551 let err = format!(
552 "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
553 signer.x509.issuer_name()
554 );
555 return Err(err.into());
556 }
557 let mut builder = X509Builder::new()?;
558 builder.set_version(2)?;
559 builder.set_subject_name(self.csr.subject_name())?;
560 builder.set_issuer_name(signer.x509.subject_name())?;
561 let csr_public_key = self.csr.public_key()?;
562 builder.set_pubkey(&csr_public_key)?;
563
564 let der = self.csr.to_der()?;
565 let parsed_csr = X509CertificationRequest::from_der(&der)?;
566
567 let req_ext = parsed_csr.1.requested_extensions();
568 let mut any_key_used = false;
569 if let Some(exts) = req_ext {
570 for ext in exts {
571 match ext {
572 ParsedExtension::KeyUsage(ku) => {
573 any_key_used = true;
574 let mut cert_sign_added = false;
575 let mut crl_sign_added = false;
576 let mut usage = openssl::x509::extension::KeyUsage::new();
577 if ku.digital_signature() {
578 usage.digital_signature();
579 }
580 if ku.key_encipherment() {
581 usage.key_encipherment();
582 }
583 if ku.key_cert_sign() {
584 cert_sign_added = true;
585 usage.key_cert_sign();
586 }
587 if ku.non_repudiation() {
588 usage.non_repudiation();
589 }
590 if ku.crl_sign() {
591 crl_sign_added = true;
592 usage.crl_sign();
593 }
594
595 if options.ca {
596 if !cert_sign_added {
597 usage.key_cert_sign();
598 }
599 if !crl_sign_added {
600 usage.crl_sign();
601 }
602 }
603 builder.append_extension(usage.build()?)?;
604 }
605 ParsedExtension::ExtendedKeyUsage(eku) => {
606 let mut ext = openssl::x509::extension::ExtendedKeyUsage::new();
607 if eku.server_auth {
608 ext.server_auth();
609 }
610 if eku.client_auth {
611 ext.client_auth();
612 }
613 if eku.code_signing {
614 ext.code_signing();
615 }
616 if eku.email_protection {
617 ext.email_protection();
618 }
619 builder.append_extension(ext.build()?)?;
620 }
621 ParsedExtension::SubjectAlternativeName(san) => {
622 let mut openssl_san =
623 openssl::x509::extension::SubjectAlternativeName::new();
624 for name in &san.general_names {
625 if let x509_parser::extensions::GeneralName::DNSName(dns) = name {
626 openssl_san.dns(dns);
627 }
628 }
629 builder.append_extension(
630 openssl_san.build(&builder.x509v3_context(None, None))?,
631 )?;
632 }
633 _ => {
634 println!("Unsupported extension: {:?}", ext);
635 }
636 }
637 }
638 }
639 if options.ca {
640 builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
641 if !any_key_used {
642 let key_usage = KeyUsage::new().key_cert_sign().crl_sign().build().unwrap();
643 builder.append_extension(key_usage)?;
644 }
645 } else {
646 builder.append_extension(BasicConstraints::new().build()?)?;
647 }
648 builder.set_not_before(&options.valid_from)?;
649 builder.set_not_after(&options.valid_to)?;
650 let serial_number = {
651 let mut serial = BigNum::new()?;
652 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
653 serial.to_asn1_integer()?
654 };
655 builder.set_serial_number(&serial_number)?;
656 if signer.x509.subject_key_id().is_some() {
657 let aki = AuthorityKeyIdentifier::new()
658 .keyid(true)
659 .issuer(false)
660 .build(&builder.x509v3_context(Some(&signer.x509), None))?;
661 builder.append_extension(aki)?;
662 }
663 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = self.csr.public_key().unwrap().public_key_to_der()?;
665 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
666 let der_encoded = yasna::construct_der(|writer| {
667 writer.write_bytes(ski_hash.as_ref());
668 });
669 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
670 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
671 builder.append_extension(ext)?;
672 let cert: X509 = if is_digestless_key(signer.pkey.as_ref().unwrap()) {
673 let builder_cert = builder.build();
674 sign_certificate_digestless(&builder_cert, signer.pkey.as_ref().unwrap())
675 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
676 builder_cert
677 } else {
678 builder.sign(signer.pkey.as_ref().unwrap(), MessageDigest::sha256())?;
679 builder.build()
680 };
681
682 Ok(Certificate {
683 x509: cert,
684 pkey: None,
685 })
686 }
687}
688#[derive(Clone)]
690pub struct Certificate {
691 pub x509: X509,
693 pub pkey: Option<PKey<Private>>,
697}
698
699impl X509Parts for Certificate {
700 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
701 Ok(self.x509.to_pem()?)
702 }
703
704 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
705 match self.pkey {
706 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
707 _ => Err("No private key found".into()),
708 }
709 }
710 fn pem_extension(&self) -> &'static str {
711 "_cert.pem"
712 }
713}
714
715pub trait CertificateX509Common: X509Common {}
717impl CertificateX509Common for Certificate {}
718
719impl Certificate {
720 pub fn load_cert_and_key<C: AsRef<Path>, K: AsRef<Path>>(
723 cert_pem_file: C,
724 key_pem_file: K,
725 ) -> Result<Self, Box<dyn std::error::Error>> {
726 let cert_pem = std::fs::read(cert_pem_file)?;
727 let key_pem = std::fs::read(key_pem_file)?;
728 let cert = X509::from_pem(&cert_pem)?;
729 let pkey = PKey::private_key_from_pem(&key_pem)?;
730 Ok(Self {
731 x509: cert,
732 pkey: Some(pkey),
733 })
734 }
735}
736
737pub trait BuilderCommon {
739 fn set_common_name(&mut self, name: &str);
740 fn set_signer(&mut self, signer: &str);
741 fn set_country_name(&mut self, name: &str);
742 fn set_state_province(&mut self, name: &str);
743 fn set_organization(&mut self, name: &str);
744 fn set_organization_unit(&mut self, name: &str);
745 fn set_alternative_names(&mut self, alternative_names: Vec<&str>);
746 fn set_locality_time(&mut self, locality_time: &str);
747 fn set_key_type(&mut self, key_type: KeyType);
748 fn set_signature_alg(&mut self, signature_alg: HashAlg);
749 fn set_key_usage(&mut self, key_usage: HashSet<Usage>);
750}
751
752#[derive(Debug)]
754pub struct BuilderFields {
755 common_name: String,
756 signer: Option<String>, alternative_names: HashSet<String>,
758 organization_unit: String,
759 country_name: String,
760 state_province: String,
761 organization: String,
762 locality_time: String,
763 key_type: Option<KeyType>,
764 signature_alg: Option<HashAlg>,
765 usage: Option<HashSet<Usage>>,
766}
767impl BuilderCommon for BuilderFields {
768 fn set_common_name(&mut self, common_name: &str) {
770 self.common_name = common_name.into();
771 self.alternative_names.insert(String::from(common_name));
772 }
773 fn set_alternative_names(&mut self, alternative_names: Vec<&str>) {
775 self.alternative_names
776 .extend(vec_str_to_hs!(alternative_names));
777 }
778 fn set_signer(&mut self, signer: &str) {
780 self.signer = Some(signer.into());
781 }
782 fn set_country_name(&mut self, country_name: &str) {
784 self.country_name = country_name.into();
785 }
786 fn set_state_province(&mut self, state_province: &str) {
788 self.state_province = state_province.into();
789 }
790 fn set_organization(&mut self, organization: &str) {
792 self.organization = organization.into();
793 }
794 fn set_organization_unit(&mut self, organization_unit: &str) {
796 self.organization_unit = organization_unit.into();
797 }
798 fn set_locality_time(&mut self, locality_time: &str) {
800 self.locality_time = locality_time.into();
801 }
802 fn set_key_type(&mut self, key_type: KeyType) {
804 self.key_type = Some(key_type);
805 }
806 fn set_signature_alg(&mut self, signature_alg: HashAlg) {
808 self.signature_alg = Some(signature_alg);
809 }
810
811 fn set_key_usage(&mut self, key_usage: HashSet<Usage>) {
813 match &mut self.usage {
814 Some(existing_usage) => {
815 existing_usage.extend(key_usage);
816 }
817 None => {
818 self.usage = Some(key_usage);
819 }
820 };
821 }
822}
823
824impl Default for BuilderFields {
825 fn default() -> Self {
827 Self {
828 common_name: Default::default(),
829 signer: Default::default(),
830 alternative_names: Default::default(),
831 country_name: Default::default(),
832 state_province: Default::default(),
833 organization: Default::default(),
834 organization_unit: Default::default(),
835 locality_time: Default::default(),
836 key_type: Default::default(),
837 signature_alg: Default::default(),
838 usage: Default::default(),
839 }
840 }
841}
842pub trait UseesBuilderFields: Sized {
844 fn fields_mut(&mut self) -> &mut BuilderFields;
846
847 fn common_name(mut self, common_name: &str) -> Self {
851 self.fields_mut().set_common_name(common_name);
852 self
853 }
854 fn signer(mut self, signer: &str) -> Self {
856 self.fields_mut().set_signer(signer);
857 self
858 }
859 fn alternative_names(mut self, alternative_names: Vec<&str>) -> Self {
863 self.fields_mut().set_alternative_names(alternative_names);
864 self
865 }
866 fn country_name(mut self, country_name: &str) -> Self {
868 self.fields_mut().set_country_name(country_name);
869 self
870 }
871 fn state_province(mut self, state_province: &str) -> Self {
873 self.fields_mut().set_state_province(state_province);
874 self
875 }
876 fn organization(mut self, organization: &str) -> Self {
878 self.fields_mut().set_organization(organization);
879 self
880 }
881 fn locality_time(mut self, locality_time: &str) -> Self {
883 self.fields_mut().set_locality_time(locality_time);
884 self
885 }
886 fn key_type(mut self, key_type: KeyType) -> Self {
888 self.fields_mut().set_key_type(key_type);
889 self
890 }
891 fn signature_alg(mut self, signature_alg: HashAlg) -> Self {
893 self.fields_mut().set_signature_alg(signature_alg);
894 self
895 }
896
897 fn key_usage(mut self, key_usage: HashSet<Usage>) -> Self {
901 self.fields_mut().set_key_usage(key_usage);
902 self
903 }
904}
905
906pub struct CertBuilder {
908 fields: BuilderFields,
909 valid_from: Asn1Time,
910 valid_to: Asn1Time,
911 ca: bool,
912}
913
914impl UseesBuilderFields for CertBuilder {
915 fn fields_mut(&mut self) -> &mut BuilderFields {
916 &mut self.fields
917 }
918}
919impl Default for CertBuilder {
920 fn default() -> Self {
921 Self::new()
922 }
923}
924
925impl CertBuilder {
926 pub fn new() -> Self {
928 Self {
929 fields: BuilderFields::default(),
930 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), ca: false,
933 }
934 }
935 pub fn valid_from(mut self, valid_from: &str) -> Self {
940 self.valid_from =
941 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
942 self
943 }
944 pub fn valid_to(mut self, valid_to: &str) -> Self {
949 self.valid_to =
950 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
951 self
952 }
953 pub fn is_ca(mut self, ca: bool) -> Self {
958 if ca {
959 self.ca = ca;
960 self.fields
961 .set_key_usage(HashSet::from([Usage::certsign, Usage::crlsign]));
962 }
963 self
964 }
965
966 pub fn build_and_self_sign(&self) -> Result<Certificate, Box<dyn std::error::Error>> {
968 let (mut builder, pkey) = self.prepare_x509_builder(None)?;
969 let ca_cert: X509 = if is_digestless_key(&pkey) {
970 let build_cert = builder.build();
971 sign_certificate_digestless(&build_cert, &pkey)
972 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
973 build_cert
974 } else {
975 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
976 builder.build()
977 };
978
979 Ok(Certificate {
980 x509: ca_cert,
981 pkey: Some(pkey),
982 })
983 }
984 pub fn build_and_sign(
986 &self,
987 signer: &Certificate,
988 ) -> Result<Certificate, Box<dyn std::error::Error>> {
989 let can_sign = can_sign_cert(&signer.x509)?;
990 if !can_sign {
991 let err = format!(
992 "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
993 signer.x509.issuer_name()
994 );
995 return Err(err.into());
996 }
997 let (mut builder, pkey) = self.prepare_x509_builder(Some(signer))?;
998 let signer_key = signer.pkey.as_ref().unwrap();
999 let cert: X509 = if is_digestless_key(signer_key) {
1000 let build_cert = builder.build();
1001 sign_certificate_digestless(&build_cert, signer_key)
1002 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1003 build_cert
1004 } else {
1005 builder.sign(signer_key, select_hash(&self.fields.signature_alg))?;
1006 builder.build()
1007 };
1008 Ok(Certificate {
1009 x509: cert,
1010 pkey: Some(pkey),
1011 })
1012 }
1013
1014 fn prepare_x509_builder(
1015 &self,
1016 signer: Option<&Certificate>,
1017 ) -> Result<(X509Builder, PKey<Private>), Box<dyn std::error::Error>> {
1018 let mut name_builder = X509NameBuilder::new()?;
1019 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1020 if !self.fields.country_name.trim().is_empty() {
1021 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1022 }
1023 if !self.fields.state_province.trim().is_empty() {
1024 name_builder
1025 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1026 }
1027 if !self.fields.locality_time.trim().is_empty() {
1028 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1029 }
1030 if !self.fields.organization.trim().is_empty() {
1031 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1032 }
1033 if !self.fields.organization_unit.trim().is_empty() {
1034 name_builder
1035 .append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, &self.fields.organization_unit)?;
1036 }
1037
1038 let name = name_builder.build();
1039
1040 let mut builder = X509::builder()?;
1041 builder.set_version(2)?;
1042
1043 let serial_number = {
1044 let mut serial = BigNum::new()?;
1045 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
1046 serial.to_asn1_integer()?
1047 };
1048
1049 let pkey = select_key(&self.fields.key_type).unwrap();
1050 builder.set_serial_number(&serial_number)?;
1051 builder.set_subject_name(&name)?;
1052 builder.set_pubkey(&pkey)?;
1053 builder.set_not_before(&self.valid_from)?;
1054 builder.set_not_after(&self.valid_to)?;
1055 match signer {
1056 Some(signer) => builder.set_issuer_name(signer.x509.subject_name())?,
1057 None => builder.set_issuer_name(&name)?,
1058 }
1059
1060 let key_usage = self.fields.usage.clone().unwrap_or_default();
1061 if self.ca {
1062 builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
1063 } else {
1064 builder.append_extension(BasicConstraints::new().build()?)?;
1065 }
1066
1067 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1068 if tracked_key_usage.is_used() {
1069 builder.append_extension(tracked_key_usage.into_inner().build()?)?;
1070 }
1071 if tracked_extended_key_usage.is_used() {
1072 builder.append_extension(tracked_extended_key_usage.into_inner().build()?)?;
1073 }
1074
1075 let mut san = SubjectAlternativeName::new();
1076 for s in &self.fields.alternative_names {
1077 san.dns(s);
1078 }
1079 if let Some(signer_cert) = signer {
1080 builder.append_extension(
1081 san.build(&builder.x509v3_context(Some(&signer_cert.x509), None))?,
1082 )?;
1083 if signer_cert.x509.subject_key_id().is_some() {
1084 let aki = AuthorityKeyIdentifier::new()
1085 .keyid(true)
1086 .issuer(false)
1087 .build(&builder.x509v3_context(Some(&signer_cert.x509), None))?;
1088 builder.append_extension(aki)?;
1089 }
1090 } else {
1091 builder.append_extension(san.build(&builder.x509v3_context(None, None))?)?;
1093 let oid = Asn1Object::from_str("2.5.29.35")?; let pubkey_der = pkey.public_key_to_der()?;
1095 let aki_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1096 let der_encoded = yasna::construct_der(|writer| {
1097 writer.write_sequence(|writer| {
1098 writer
1099 .next()
1100 .write_tagged_implicit(yasna::Tag::context(0), |writer| {
1101 writer.write_bytes(aki_hash.as_ref());
1102 })
1103 })
1104 });
1105 let aki_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1106 let ext = X509Extension::new_from_der(oid.as_ref(), false, &aki_asn1)?;
1107 builder.append_extension(ext)?;
1108 }
1109 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = pkey.public_key_to_der()?;
1119 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1120 let der_encoded = yasna::construct_der(|writer| {
1121 writer.write_bytes(ski_hash.as_ref());
1122 });
1123 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1124 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
1125 builder.append_extension(ext)?;
1126
1127 Ok((builder, pkey))
1128 }
1129}
1130
1131pub struct CsrBuilder {
1133 fields: BuilderFields,
1134}
1135impl UseesBuilderFields for CsrBuilder {
1136 fn fields_mut(&mut self) -> &mut BuilderFields {
1137 &mut self.fields
1138 }
1139}
1140impl Default for CsrBuilder {
1141 fn default() -> Self {
1142 Self::new()
1143 }
1144}
1145
1146impl CsrBuilder {
1147 pub fn new() -> Self {
1149 Self {
1150 fields: BuilderFields::default(),
1151 }
1152 }
1153
1154 pub fn certificate_signing_request(self) -> Result<Csr, Box<dyn std::error::Error>> {
1187 let mut name_builder = X509NameBuilder::new()?;
1188 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1189 if !self.fields.country_name.trim().is_empty() {
1190 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1191 }
1192 if !self.fields.state_province.trim().is_empty() {
1193 name_builder
1194 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1195 }
1196 if !self.fields.locality_time.trim().is_empty() {
1197 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1198 }
1199 if !self.fields.organization.trim().is_empty() {
1200 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1201 }
1202 let name = name_builder.build();
1203 let mut builder = X509ReqBuilder::new()?;
1204 builder.set_version(0)?;
1205 builder.set_subject_name(&name)?;
1206 let pkey = select_key(&self.fields.key_type).unwrap();
1207 builder.set_pubkey(&pkey)?;
1208 let key_usage = self.fields.usage.clone().unwrap_or_default();
1209
1210 let mut extensions = Stack::new()?;
1211
1212 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1213 if tracked_key_usage.is_used() {
1214 extensions.push(tracked_key_usage.inner.build()?)?;
1215 }
1216 if tracked_extended_key_usage.is_used() {
1217 extensions.push(tracked_extended_key_usage.inner.build()?)?;
1218 }
1219
1220 let mut san = SubjectAlternativeName::new();
1221 for s in &self.fields.alternative_names {
1222 san.dns(s);
1223 }
1224 extensions.push(san.build(&builder.x509v3_context(None))?)?;
1225
1226 builder.add_extensions(&extensions)?;
1227 let csr: X509Req = if is_digestless_key(&pkey) {
1228 let builder_csr = builder.build();
1229 sign_x509_req_digestless(&builder_csr, &pkey)
1230 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1231 builder_csr
1232 } else {
1233 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
1234 builder.build()
1235 };
1236 Ok(Csr {
1237 csr,
1238 pkey: Some(pkey),
1239 })
1240 }
1241}
1242struct TrackedExtendedKeyUsage {
1243 inner: ExtendedKeyUsage,
1244 used: bool,
1245}
1246
1247impl TrackedExtendedKeyUsage {
1248 fn new() -> Self {
1249 Self {
1250 inner: ExtendedKeyUsage::new(),
1251 used: false,
1252 }
1253 }
1254
1255 fn client_auth(&mut self) {
1256 self.inner.client_auth();
1257 self.used = true;
1258 }
1259
1260 fn server_auth(&mut self) {
1261 self.inner.server_auth();
1262 self.used = true;
1263 }
1264
1265 fn is_used(&self) -> bool {
1266 self.used
1267 }
1268
1269 fn into_inner(self) -> ExtendedKeyUsage {
1270 self.inner
1271 }
1272}
1273
1274struct TrackedKeyUsage {
1275 inner: KeyUsage,
1276 used: bool,
1277}
1278
1279impl TrackedKeyUsage {
1280 fn new() -> Self {
1281 Self {
1282 inner: KeyUsage::new(),
1283 used: false,
1284 }
1285 }
1286
1287 fn digital_signature(&mut self) {
1288 self.inner.digital_signature();
1289 self.used = true;
1290 }
1291
1292 fn non_repudiation(&mut self) {
1293 self.inner.non_repudiation();
1294 self.used = true;
1295 }
1296
1297 fn key_encipherment(&mut self) {
1298 self.inner.key_encipherment();
1299 self.used = true;
1300 }
1301
1302 fn key_cert_sign(&mut self) {
1303 self.inner.key_cert_sign();
1304 self.used = true;
1305 }
1306
1307 fn crl_sign(&mut self) {
1308 self.inner.crl_sign();
1309 self.used = true;
1310 }
1311
1312 fn is_used(&self) -> bool {
1313 self.used
1314 }
1315
1316 fn into_inner(self) -> KeyUsage {
1317 self.inner
1318 }
1319}
1320
1321pub fn verify_cert(
1325 cert: &X509,
1326 ca: &X509,
1327 cert_chain: Vec<&X509>,
1328) -> Result<bool, Box<dyn std::error::Error>> {
1329 let mut store_builder = X509StoreBuilder::new()?;
1331 store_builder.add_cert(ca.clone())?;
1332 let store = store_builder.build();
1333
1334 let mut ctx = X509StoreContext::new()?;
1336 let mut chain = Stack::new()?; cert_chain
1338 .iter()
1339 .try_for_each(|c| chain.push((*c).clone()))?;
1340 ctx.init(&store, cert, &chain, |c| c.verify_cert())?;
1341 let verified = ctx.error() == openssl::x509::X509VerifyResult::OK;
1342 Ok(verified)
1343}
1344
1345pub fn create_cert_chain_from_cert_list(
1354 certs: Vec<X509>,
1355) -> Result<Vec<X509>, Box<dyn std::error::Error>> {
1356 let mut subject_map: HashMap<Vec<u8>, X509> = HashMap::new();
1357 let mut issuer_map: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
1358
1359 for cert in &certs {
1360 let subject = cert.subject_name().to_der()?;
1361 let issuer = cert.issuer_name().to_der()?;
1362 subject_map.insert(subject.clone(), cert.clone());
1363 issuer_map.insert(subject, issuer);
1364 }
1365
1366 let all_issuers: Vec<Vec<u8>> = issuer_map.values().cloned().collect();
1368 let leaf_certs: Vec<X509> = subject_map
1369 .iter()
1370 .filter(|(subject, _)| !all_issuers.contains(subject))
1371 .map(|(_, cert)| cert.clone())
1372 .collect();
1373
1374 let mut longest_chain = Vec::new();
1376
1377 for leaf in leaf_certs {
1378 let mut chain = vec![leaf.clone()];
1379 let mut current_cert = leaf;
1380
1381 while let Ok(issuer_der) = current_cert.issuer_name().to_der() {
1382 if let Some(parent_cert) = subject_map.get(&issuer_der) {
1383 if parent_cert.subject_name().to_der()? == current_cert.subject_name().to_der()? {
1384 break; }
1386 chain.push(parent_cert.clone());
1387 current_cert = parent_cert.clone();
1388 } else {
1389 break; }
1391 }
1392
1393 if chain.len() > longest_chain.len() {
1394 longest_chain = chain;
1395 }
1396 }
1397
1398 longest_chain.reverse();
1400 Ok(longest_chain)
1401}
1402
1403fn create_asn1_time_from_date(date_str: &str) -> Result<Asn1Time, Box<dyn std::error::Error>> {
1404 let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")?;
1405 let datetime = NaiveDateTime::new(date, chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap());
1406 let utc_datetime = Utc.from_utc_datetime(&datetime);
1407 let asn1_time_str = utc_datetime.format("%Y%m%d%H%M%SZ").to_string();
1408 let asn1_time = Asn1Time::from_str(&asn1_time_str)?;
1409 Ok(asn1_time)
1410}
1411
1412fn select_key(key_type: &Option<KeyType>) -> Result<PKey<Private>, ErrorStack> {
1413 match key_type {
1414 Some(KeyType::P224) => {
1415 let group = EcGroup::from_curve_name(Nid::SECP224R1)?;
1416 let ec_key = EcKey::generate(&group)?;
1417 PKey::from_ec_key(ec_key)
1418 }
1419 Some(KeyType::P256) => {
1420 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
1421 let ec_key = EcKey::generate(&group)?;
1422 PKey::from_ec_key(ec_key)
1423 }
1424 Some(KeyType::P384) => {
1425 let group = EcGroup::from_curve_name(Nid::SECP384R1)?;
1426 let ec_key = EcKey::generate(&group)?;
1427 PKey::from_ec_key(ec_key)
1428 }
1429 Some(KeyType::P521) => {
1430 let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
1431 let ec_key = EcKey::generate(&group)?;
1432 PKey::from_ec_key(ec_key)
1433 }
1434 Some(KeyType::Ed25519) => PKey::generate_ed25519(),
1435 #[cfg(feature = "pqc")]
1436 Some(KeyType::MlDsa44) => generate_pqc_key("ML-DSA-44"),
1437 #[cfg(feature = "pqc")]
1438 Some(KeyType::MlDsa65) => generate_pqc_key("ML-DSA-65"),
1439 #[cfg(feature = "pqc")]
1440 Some(KeyType::MlDsa87) => generate_pqc_key("ML-DSA-87"),
1441 #[cfg(feature = "pqc")]
1442 Some(KeyType::SlhDsaSha2_128s) => generate_pqc_key("SLH-DSA-SHA2-128s"),
1443 #[cfg(feature = "pqc")]
1444 Some(KeyType::SlhDsaSha2_192s) => generate_pqc_key("SLH-DSA-SHA2-192s"),
1445 #[cfg(feature = "pqc")]
1446 Some(KeyType::SlhDsaSha2_256s) => generate_pqc_key("SLH-DSA-SHA2-256s"),
1447 Some(KeyType::RSA4096) => {
1448 let rsa = Rsa::generate(4096)?;
1449 PKey::from_rsa(rsa)
1450 }
1451 _ => {
1452 let rsa = Rsa::generate(2048)?;
1453 PKey::from_rsa(rsa)
1454 }
1455 }
1456}
1457
1458fn select_hash(hash_type: &Option<HashAlg>) -> MessageDigest {
1459 match hash_type {
1460 Some(HashAlg::SHA1) => MessageDigest::sha1(),
1461 Some(HashAlg::SHA384) => MessageDigest::sha384(),
1462 Some(HashAlg::SHA512) => MessageDigest::sha512(),
1463 _ => MessageDigest::sha256(),
1464 }
1465}
1466
1467fn get_key_usage(usage: &Option<HashSet<Usage>>) -> (TrackedKeyUsage, TrackedExtendedKeyUsage) {
1468 let mut ku = TrackedKeyUsage::new();
1469 let mut eku = TrackedExtendedKeyUsage::new();
1470 if let Some(usages) = usage {
1471 for u in usages {
1472 match u {
1473 Usage::contentcommitment => {
1474 ku.non_repudiation();
1475 }
1476 Usage::encipherment => {
1477 ku.key_encipherment();
1478 }
1479 Usage::certsign => {
1480 ku.key_cert_sign();
1481 }
1482 Usage::clientauth => {
1483 eku.client_auth();
1484 }
1485 Usage::signature => {
1486 ku.digital_signature();
1487 }
1488 Usage::crlsign => {
1489 ku.crl_sign();
1490 }
1491 Usage::serverauth => {
1492 eku.server_auth();
1493 }
1494 }
1495 }
1496 }
1497
1498 (ku, eku)
1499}
1500
1501fn can_sign_cert(cert: &X509) -> Result<bool, Box<dyn std::error::Error>> {
1502 let der = cert.to_der()?;
1503 let (_, parsed_cert) = parse_x509_certificate(&der)?;
1504
1505 let mut is_ca = false;
1506 let mut can_sign = false;
1507
1508 let not_after_asn1_time = cert.not_after().to_string();
1509 let naive =
1510 NaiveDateTime::parse_from_str(¬_after_asn1_time, "%b %e %H:%M:%S %Y GMT").unwrap();
1511 let not_after_utc: DateTime<Utc> = Utc.from_utc_datetime(&naive);
1512
1513 let not_before_asn1_time = cert.not_before().to_string();
1514 let naive =
1515 NaiveDateTime::parse_from_str(¬_before_asn1_time, "%b %e %H:%M:%S %Y GMT").unwrap();
1516 let not_before_utc: DateTime<Utc> = Utc.from_utc_datetime(&naive);
1517
1518 let now = Utc::now();
1519 let valid_time = (now < not_after_utc) && (now >= not_before_utc);
1520
1521 for ext in parsed_cert.tbs_certificate.extensions().iter() {
1522 match &ext.parsed_extension() {
1523 ParsedExtension::BasicConstraints(bc) => {
1524 is_ca = bc.ca;
1525 }
1526 ParsedExtension::KeyUsage(ku) => {
1527 can_sign = ku.key_cert_sign();
1528 }
1529 _ => {}
1530 }
1531 }
1532 Ok(is_ca && can_sign && valid_time)
1533}
1534
1535#[cfg(test)]
1536mod tests {
1537 use super::*;
1538 use std::io::Write;
1539 use std::path::Path;
1540 use tempfile::NamedTempFile;
1541
1542 #[test]
1543 fn save_certificate() {
1544 let ca = CertBuilder::new().common_name("My Test Ca").is_ca(true);
1545 match ca.build_and_self_sign() {
1546 Ok(cert) => {
1547 let output_file = NamedTempFile::new().unwrap();
1548 let full_path = output_file.path();
1549 let parent_dir: &Path = full_path.parent().unwrap();
1550 let file_name: &str = full_path.file_name().unwrap().to_str().unwrap();
1551 cert.save(parent_dir, file_name)
1552 .expect("Failed to save certificate and key");
1553 let written_file_path = parent_dir.join(file_name);
1554 assert!(written_file_path.exists(), "File was not created");
1555 }
1556 Err(_) => panic!("Failed to creat certificate"),
1557 }
1558 }
1559
1560 #[test]
1561 fn read_certificate_and_key_from_file() {
1562 let cert_pem = b"-----BEGIN CERTIFICATE-----
1563MIICiDCCAemgAwIBAgIUO3+y1WZPRRNs8dmZZTUHMj6TdiowCgYIKoZIzj0EAwQw
1564WzETMBEGA1UEAwwKTXkgVGVzdCBDYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0
1565b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMQ8wDQYDVQQKDAZteSBvcmcwHhcN
1566MjUwNzA4MTExMzI2WhcNMjYwNzA4MTExMzI2WjBbMRMwEQYDVQQDDApNeSBUZXN0
1567IENhMQswCQYDVQQGEwJTRTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlT
1568dG9ja2hvbG0xDzANBgNVBAoMBm15IG9yZzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOB
1569hgAEADZXcQK2ihgVTJeGx5FKm1x+R+ivygIvMnkv03faq1LpLU3doKX38DEO/cSW
1570Ev5u+kcjspXeeDPhqJFC8rRAz4awAMk+D0mXEms7xpFPh0HmI6NNcJc5eJ/8ZsEJ
1571GH1a34y0Yn6259gqlwAh2Eh9Nx1579BAanRr8lr+n1tZ09T/9AQho0gwRjAMBgNV
1572HRMEBTADAQH/MAsGA1UdDwQEAwIBBjApBgNVHREEIjAgggZjYS5jb22CCnd3dy5j
1573YS5jb22CCk15IFRlc3QgQ2EwCgYIKoZIzj0EAwQDgYwAMIGIAkIB8NVUgRIuNXmJ
1574cLCQ74Ub7Dqo71S0+iCrZF1YyJA8/q65aqMCT54k5Yx7HRBUUVHbCEpDXRqGPsIH
1575frfe5OmS3qICQgDBn07o0CcyfoSEd+Xoj2+/RBuU0vo9lUP7TKj7tssBxzEQFoxX
1576eE1qT98UIe78FZ+zqjwZTN9MCSsatuim6pXvOA==
1577-----END CERTIFICATE-----";
1578
1579 let key_pem = b"-----BEGIN PRIVATE KEY-----
1580MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAgvZTeQgGysadAX0r
1581aZB5Lk4vjHy5iVuKdvcGdYt9NBvYx+Ib3Uk7vqMag7M1jyHL0Xf9uNtT2mxBmzBG
15823CF+EgOhgYkDgYYABAA2V3ECtooYFUyXhseRSptcfkfor8oCLzJ5L9N32qtS6S1N
15833aCl9/AxDv3ElhL+bvpHI7KV3ngz4aiRQvK0QM+GsADJPg9JlxJrO8aRT4dB5iOj
1584TXCXOXif/GbBCRh9Wt+MtGJ+tufYKpcAIdhIfTcdee/QQGp0a/Ja/p9bWdPU//QE
1585IQ==
1586-----END PRIVATE KEY-----";
1587
1588 let mut cert_file = NamedTempFile::new().expect("Failed to create temp cert file");
1589 let mut key_file = NamedTempFile::new().expect("Failed to create temp key file");
1590
1591 cert_file.write_all(cert_pem).expect("Failed to write cert");
1592 key_file.write_all(key_pem).expect("Failed to write key");
1593
1594 let result = Certificate::load_cert_and_key(cert_file.path(), key_file.path());
1595 assert!(
1596 result.is_ok(),
1597 "Failed to load cert and key: {:?}",
1598 result.err()
1599 );
1600 }
1601
1602 #[test]
1603 fn test_reading_csr_from_file() {
1604 let csr_data = b"-----BEGIN CERTIFICATE REQUEST-----
1605MIICzDCCAbQCAQAwXTEVMBMGA1UEAwwMZXhhbXBsZTIuY29tMQswCQYDVQQGEwJT
1606RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xDzANBgNV
1607BAoMBk15IG9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeAXpCG
1608hbIayESfdTzOO0DxIMsOAu4kUm0zF0W/+xDUHl6bGy3wlB9S9nzBG/qwqFZ27Om3
1609o4zrZ8K8DBx0ERWNuhMmr0Nx8QpAWBEyxOc08Gn4c3XVBBkRZSn4AIqr9DGtcUqW
1610tQZXvMGF6sRRljiEvOxO6zMzZKTGYwzIeQvH85cQ3uXsw0Kknsw/fcuywaAC8SS9
1611aqs4jiEIgzdhxdH2OVXBNGj4cjVhK309JiWFHS9XJLNV/PKC+F1nkaANQwbW5A4F
16129vya4js9gk8f4SfF1u+qOJEvsDvAb+1xdjXPRzf77eGh3rC4KgGWQ6WrWfW8PItF
1613BDg/jskq3bJXNL8CAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIM
1614ZXhhbXBsZTIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAHeeSW8C6SMVhWiMvPn7iz
1615FUHQedHRyPz6kTEfC01eNIbs0r4YghOAcm8PF67jncIXVrqgxo1uzq12qlV+0YYb
1616jps31IbQNOz0eFLYvij15ielmOYeQZZ/2vqaGi3geVobLc6Ki5tadnA/NhjTN33j
1617QcqDDic8riAOTbSQ6TH9KPTGJQOPk+taMpDGDHskIW0oME5iT2ewbhBHg6v/kSzy
1618tss2kBY5O7vo2COtbNcwX5Xp9S2LH9kVUKr0GIjuQjwbv5xl+GNdDey09W9EDACU
1619jcGV3++2wS4LN4h3CG4pWZ+LTXhm8ymhoWOapN95lfe3xLRAKFJwiLkGwS75++FW
1620-----END CERTIFICATE REQUEST-----";
1621 let mut csr_file = NamedTempFile::new().expect("Failed to create temp csr file");
1622 csr_file.write_all(csr_data).expect("Failed to write csr");
1623 let result = Csr::load_csr(csr_file.path());
1624 assert!(result.is_ok(), "Failed to load csr: {:?}", result.err());
1625 }
1626}