1mod builder;
2mod common;
3mod csr;
4mod key;
5mod policy;
6mod usage;
7use builder::select_hash;
8pub use builder::{BuilderCommon, BuilderFields, HashAlg, UseesBuilderFields};
9use common::{ValidityBound, set_validity};
10pub use common::{X509Common, X509Parts};
11pub use csr::{Csr, CsrBuilder, CsrOptions, CsrX509Common};
12pub use key::KeyType;
13pub(crate) use key::is_digestless_key;
14#[cfg(feature = "pqc")]
15use key::reject_mlkem_signing;
16use key::{select_key, sign_certificate_digestless};
17use openssl::asn1::{Asn1Object, Asn1OctetString, Asn1Time};
18use openssl::bn::BigNum;
19use openssl::error::ErrorStack;
20use openssl::hash::{MessageDigest, hash};
21use openssl::nid::Nid;
22use openssl::pkey::{PKey, Private};
23use openssl::stack::Stack;
24use openssl::x509::extension::{AuthorityKeyIdentifier, BasicConstraints, SubjectAlternativeName};
25use openssl::x509::{
26 X509, X509Builder, X509Extension, X509NameBuilder, X509StoreContext, store::X509StoreBuilder,
27};
28pub use policy::CertificatePolicy;
29use policy::append_certificate_policies;
30use std::collections::{HashMap, HashSet};
31use std::marker::PhantomData;
32use std::net::IpAddr;
33use std::path::Path;
34pub use usage::Usage;
35use usage::get_key_usage;
36#[cfg(feature = "pqc")]
37use usage::validate_pqc_key_usage;
38use x509_parser::extensions::ParsedExtension;
39use x509_parser::parse_x509_certificate;
40
41pub struct PathLenUnset;
48
49pub struct PathLenSet;
57
58#[derive(Clone, Debug)]
63pub struct Certificate {
64 pub x509: X509,
66 pub pkey: Option<PKey<Private>>,
70}
71
72impl X509Parts for Certificate {
73 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
74 Ok(self.x509.to_pem()?)
75 }
76
77 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
78 match self.pkey {
79 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
80 _ => Err("No private key found".into()),
81 }
82 }
83 fn pem_extension(&self) -> &'static str {
84 "_cert.pem"
85 }
86}
87
88pub trait CertificateX509Common: X509Common {}
90impl CertificateX509Common for Certificate {}
91
92impl Certificate {
93 pub fn load_cert_and_key<C: AsRef<Path>, K: AsRef<Path>>(
96 cert_pem_file: C,
97 key_pem_file: K,
98 ) -> Result<Self, Box<dyn std::error::Error>> {
99 let cert_pem = std::fs::read(cert_pem_file)?;
100 let key_pem = std::fs::read(key_pem_file)?;
101 let cert = X509::from_pem(&cert_pem)?;
102 let pkey = PKey::private_key_from_pem(&key_pem)?;
103 Ok(Self {
104 x509: cert,
105 pkey: Some(pkey),
106 })
107 }
108
109 pub fn load_cert<C: AsRef<Path>>(cert_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
119 let cert_pem = std::fs::read(cert_pem_file)?;
120 let cert = X509::from_pem(&cert_pem)?;
121 Ok(Self {
122 x509: cert,
123 pkey: None,
124 })
125 }
126}
127
128pub struct CertBuilder<P = PathLenUnset> {
130 fields: BuilderFields,
131 valid_from: ValidityBound,
132 valid_to: ValidityBound,
133 policies: Vec<CertificatePolicy>,
134 ca: bool,
135 path_len: Option<u32>,
136 _marker: PhantomData<P>,
137}
138
139impl<P> UseesBuilderFields for CertBuilder<P> {
140 fn fields_mut(&mut self) -> &mut BuilderFields {
141 &mut self.fields
142 }
143}
144impl Default for CertBuilder {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl<P> CertBuilder<P> {
151 pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
156 self.policies = policies;
157 self
158 }
159 pub fn valid_from(mut self, valid_from: &str) -> Self {
164 self.valid_from = ValidityBound::Date(valid_from.to_owned());
165 self
166 }
167 pub fn valid_to(mut self, valid_to: &str) -> Self {
175 self.valid_to = ValidityBound::Date(valid_to.to_owned());
176 self
177 }
178 pub fn is_ca(mut self, ca: bool) -> Self {
183 if ca {
184 self.ca = ca;
185 self.fields
186 .set_key_usage(HashSet::from([Usage::certsign, Usage::crlsign]));
187 }
188 self
189 }
190 pub fn pathlen(self, path_len: u32) -> CertBuilder<PathLenSet> {
196 CertBuilder {
197 fields: self.fields,
198 valid_from: self.valid_from,
199 valid_to: self.valid_to,
200 policies: self.policies,
201 ca: self.ca,
202 path_len: Some(path_len),
203 _marker: PhantomData,
204 }
205 }
206
207 pub fn build_and_self_sign(&self) -> Result<Certificate, Box<dyn std::error::Error>> {
213 let (mut builder, pkey) = self.prepare_x509_builder(None)?;
214
215 #[cfg(feature = "pqc")]
218 reject_mlkem_signing(
219 &pkey,
220 "ML-KEM (FIPS 203) is a key-encapsulation key and cannot produce \
221 signatures, so it cannot self-sign a certificate. Issue an ML-KEM \
222 certificate with CertBuilder::build_and_sign() using a signing CA \
223 (e.g. an ML-DSA or ECDSA CA) instead.",
224 )?;
225
226 let ca_cert: X509 = if is_digestless_key(&pkey) {
227 let build_cert = builder.build();
228 sign_certificate_digestless(&build_cert, &pkey)
229 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
230 build_cert
231 } else {
232 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
233 builder.build()
234 };
235
236 Ok(Certificate {
237 x509: ca_cert,
238 pkey: Some(pkey),
239 })
240 }
241
242 fn prepare_x509_builder(
243 &self,
244 signer: Option<&Certificate>,
245 ) -> Result<(X509Builder, PKey<Private>), Box<dyn std::error::Error>> {
246 let mut name_builder = X509NameBuilder::new()?;
247 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
248 if !self.fields.country_name.trim().is_empty() {
249 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
250 }
251 if !self.fields.state_province.trim().is_empty() {
252 name_builder
253 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
254 }
255 if !self.fields.locality_time.trim().is_empty() {
256 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
257 }
258 if !self.fields.organization.trim().is_empty() {
259 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
260 }
261 if !self.fields.organization_unit.trim().is_empty() {
262 name_builder
263 .append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, &self.fields.organization_unit)?;
264 }
265
266 let name = name_builder.build();
267
268 let mut builder = X509::builder()?;
269 builder.set_version(2)?;
270
271 let serial_number = {
272 let mut serial = BigNum::new()?;
273 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
274 serial.to_asn1_integer()?
275 };
276
277 let pkey = match &self.fields.existing_key {
278 Some(pkey) => pkey.clone(),
279 None => select_key(&self.fields.key_type)?,
280 };
281 builder.set_serial_number(&serial_number)?;
282 builder.set_subject_name(&name)?;
283 builder.set_pubkey(&pkey)?;
284 set_validity(
285 &mut builder,
286 &self.valid_from,
287 &self.valid_to,
288 signer.map(|s| &*s.x509),
289 )?;
290 match signer {
291 Some(signer) => builder.set_issuer_name(signer.x509.subject_name())?,
292 None => builder.set_issuer_name(&name)?,
293 }
294
295 let key_usage = self.fields.usage.clone().unwrap_or_default();
296 append_certificate_policies(&mut builder, &self.policies)?;
297 #[cfg(feature = "pqc")]
300 validate_pqc_key_usage(&pkey, &key_usage)?;
301
302 if self.ca {
303 let result = ca_basic_constraints(self.path_len)?;
304 builder.append_extension(result)?;
305 } else {
306 builder.append_extension(BasicConstraints::new().build()?)?;
307 }
308
309 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
310 if tracked_key_usage.is_used() {
311 builder.append_extension(tracked_key_usage.into_inner().build()?)?;
312 }
313 if tracked_extended_key_usage.is_used() {
314 builder.append_extension(tracked_extended_key_usage.into_inner().build()?)?;
315 }
316
317 let names = san_names(&self.fields, !self.ca);
320 if !names.is_empty() {
321 let mut san = SubjectAlternativeName::new();
323 for n in &names {
324 add_san_entry(&mut san, n);
325 }
326 if let Some(signer_cert) = signer {
327 builder.append_extension(
328 san.build(&builder.x509v3_context(Some(&signer_cert.x509), None))?,
329 )?;
330 } else {
331 builder.append_extension(san.build(&builder.x509v3_context(None, None))?)?;
332 }
333 }
334
335 if let Some(signer_cert) = signer {
336 if signer_cert.x509.subject_key_id().is_some() {
337 let aki = AuthorityKeyIdentifier::new()
338 .keyid(true)
339 .issuer(false)
340 .build(&builder.x509v3_context(Some(&signer_cert.x509), None))?;
341 builder.append_extension(aki)?;
342 }
343 } else {
344 let oid = Asn1Object::from_str("2.5.29.35")?; let pubkey_der = pkey.public_key_to_der()?;
347 let aki_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
348 let der_encoded = yasna::construct_der(|writer| {
349 writer.write_sequence(|writer| {
350 writer
351 .next()
352 .write_tagged_implicit(yasna::Tag::context(0), |writer| {
353 writer.write_bytes(aki_hash.as_ref());
354 })
355 })
356 });
357 let aki_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
358 let ext = X509Extension::new_from_der(oid.as_ref(), false, &aki_asn1)?;
359 builder.append_extension(ext)?;
360 }
361 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = pkey.public_key_to_der()?;
371 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
372 let der_encoded = yasna::construct_der(|writer| {
373 writer.write_bytes(ski_hash.as_ref());
374 });
375 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
376 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
377 builder.append_extension(ext)?;
378
379 Ok((builder, pkey))
380 }
381}
382
383impl CertBuilder<PathLenUnset> {
384 pub fn new() -> Self {
386 Self {
387 fields: BuilderFields::default(),
388 valid_from: ValidityBound::DaysFromNow(0), valid_to: ValidityBound::DaysFromNow(365), ca: false,
391 policies: Default::default(),
392 path_len: None,
393 _marker: PhantomData,
394 }
395 }
396 pub fn build_and_sign(
404 &self,
405 signer: &Certificate,
406 ) -> Result<Certificate, Box<dyn std::error::Error>> {
407 let can_sign = can_sign_cert(signer)?;
408 if !can_sign {
409 let err = format!(
410 "Cannot sign with signer certificate {:?}: it must be a valid CA \
411 (BasicConstraints CA flag set, KeyUsage keyCertSign, within its validity \
412 period) and have an associated private key",
413 signer.x509.subject_name()
414 );
415 return Err(err.into());
416 }
417 let (mut builder, pkey) = self.prepare_x509_builder(Some(signer))?;
418 let signer_key = signer
419 .pkey
420 .as_ref()
421 .ok_or("signer certificate has no associated private key; cannot sign")?;
422 let cert: X509 = if is_digestless_key(signer_key) {
423 let build_cert = builder.build();
424 sign_certificate_digestless(&build_cert, signer_key)
425 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
426 build_cert
427 } else {
428 builder.sign(signer_key, select_hash(&self.fields.signature_alg))?;
429 builder.build()
430 };
431 Ok(Certificate {
432 x509: cert,
433 pkey: Some(pkey),
434 })
435 }
436}
437
438impl CertBuilder<PathLenSet> {
439 pub fn build_and_sign_with_chain(
446 &self,
447 signer: &Certificate,
448 chain: &[&Certificate],
449 ) -> Result<Certificate, Box<dyn std::error::Error>> {
450 let can_sign = can_sign_cert(signer)?;
451 if !can_sign {
452 let err = format!(
453 "Cannot sign with signer certificate {:?}: it must be a valid CA \
454 (BasicConstraints CA flag set, KeyUsage keyCertSign, within its validity \
455 period) and have an associated private key",
456 signer.x509.subject_name()
457 );
458 return Err(err.into());
459 }
460 let chain_x509: Vec<&X509> = chain.iter().map(|c| &c.x509).collect();
461 enforce_path_len(self.ca, self.path_len, signer, &chain_x509)?;
462
463 let (mut builder, pkey) = self.prepare_x509_builder(Some(signer))?;
464 let signer_key = signer
465 .pkey
466 .as_ref()
467 .ok_or("signer certificate has no associated private key; cannot sign")?;
468 let cert: X509 = if is_digestless_key(signer_key) {
469 let build_cert = builder.build();
470 sign_certificate_digestless(&build_cert, signer_key)
471 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
472 build_cert
473 } else {
474 builder.sign(signer_key, select_hash(&self.fields.signature_alg))?;
475 builder.build()
476 };
477 Ok(Certificate {
478 x509: cert,
479 pkey: Some(pkey),
480 })
481 }
482}
483fn ca_basic_constraints(path_len: Option<u32>) -> Result<X509Extension, ErrorStack> {
484 let mut bc = BasicConstraints::new();
485 bc.ca().critical();
486 if let Some(len) = path_len {
487 bc.pathlen(len);
488 }
489 bc.build()
490}
491
492pub fn verify_cert(
496 cert: &X509,
497 ca: &X509,
498 cert_chain: Vec<&X509>,
499) -> Result<bool, Box<dyn std::error::Error>> {
500 let mut store_builder = X509StoreBuilder::new()?;
502 store_builder.add_cert(ca.clone())?;
503 let store = store_builder.build();
504
505 let mut ctx = X509StoreContext::new()?;
507 let mut chain = Stack::new()?; cert_chain
509 .iter()
510 .try_for_each(|c| chain.push((*c).clone()))?;
511 ctx.init(&store, cert, &chain, |c| c.verify_cert())?;
512 let verified = ctx.error() == openssl::x509::X509VerifyResult::OK;
513 Ok(verified)
514}
515
516pub fn create_cert_chain_from_cert_list(
525 certs: Vec<X509>,
526) -> Result<Vec<X509>, Box<dyn std::error::Error>> {
527 let mut subject_map: HashMap<Vec<u8>, X509> = HashMap::new();
528 let mut issuer_map: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
529
530 for cert in &certs {
531 let subject = cert.subject_name().to_der()?;
532 let issuer = cert.issuer_name().to_der()?;
533 subject_map.insert(subject.clone(), cert.clone());
534 issuer_map.insert(subject, issuer);
535 }
536
537 let all_issuers: Vec<Vec<u8>> = issuer_map
539 .iter()
540 .filter(|(subject, issuer)| subject != issuer) .map(|(_, issuer)| issuer.clone())
542 .collect();
543 let leaf_certs: Vec<X509> = subject_map
544 .iter()
545 .filter(|(subject, _)| !all_issuers.contains(subject))
546 .map(|(_, cert)| cert.clone())
547 .collect();
548
549 let mut longest_chain = Vec::new();
551
552 for leaf in leaf_certs {
553 let mut chain = vec![leaf.clone()];
554 let mut current_cert = leaf;
555
556 while let Ok(issuer_der) = current_cert.issuer_name().to_der() {
557 if let Some(parent_cert) = subject_map.get(&issuer_der) {
558 if parent_cert.subject_name().to_der()? == current_cert.subject_name().to_der()? {
559 break; }
561 chain.push(parent_cert.clone());
562 current_cert = parent_cert.clone();
563 } else {
564 break; }
566 }
567
568 if chain.len() > longest_chain.len() {
569 longest_chain = chain;
570 }
571 }
572
573 longest_chain.reverse();
575 Ok(longest_chain)
576}
577
578fn add_san_entry(san: &mut SubjectAlternativeName, value: &str) {
579 match value.parse::<IpAddr>() {
580 Ok(ip) => san.ip(&ip.to_string()),
581 Err(_) => san.dns(value),
582 };
583}
584
585pub(crate) fn san_names(fields: &BuilderFields, include_cn: bool) -> Vec<&str> {
600 let mut names: Vec<&str> = fields
601 .alternative_names
602 .iter()
603 .map(String::as_str)
604 .collect();
605 if include_cn
606 && !fields.common_name.trim().is_empty()
607 && !fields.alternative_names.contains(&fields.common_name)
608 {
609 names.push(&fields.common_name);
610 }
611 names
612}
613
614fn enforce_path_len(
615 is_ca: bool,
616 path_len: Option<u32>,
617 signer: &Certificate,
618 chain: &[&X509],
619) -> Result<(), Box<dyn std::error::Error>> {
620 if !is_ca || path_len.is_none() {
623 return Ok(());
624 }
625 let budget = verify_cert_path(signer, chain)?;
626 if let (Some(b), Some(m)) = (budget, path_len)
627 && m >= b
628 {
629 return Err("requested pathLen exceeds what the signer's chain permits".into());
630 }
631 Ok(())
632}
633
634fn verify_cert_path(
635 signer: &Certificate,
636 chain: &[&X509],
637) -> Result<Option<u32>, Box<dyn std::error::Error>> {
638 let mut owned: Vec<X509> = chain.iter().map(|c| (*c).clone()).collect();
639 owned.push(signer.x509.clone());
640 let ordered = create_cert_chain_from_cert_list(owned)?;
641 let root = ordered.first().unwrap();
642 let pubkey = root.public_key()?;
643 if !root.verify(&pubkey)? {
644 let err = format!(
645 "Could not find self signed root, found last ancestor {:?}",
646 root.subject_name()
647 );
648 return Err(err.into());
649 }
650 let signer = ordered.last().unwrap();
651 let intermediates: Vec<&X509> = ordered
652 .get(1..ordered.len().saturating_sub(1)) .unwrap_or(&[])
654 .iter()
655 .collect();
656 match verify_cert(signer, root, intermediates) {
657 Ok(true) => {
658 let mut budget: Option<i64> = None;
659 for (d, ancestor) in ordered.iter().rev().enumerate() {
660 if let Some(p) = ancestor.pathlen() {
661 let candidate = p as i64 - d as i64; budget = Some(budget.map_or(candidate, |b| b.min(candidate)));
664 }
665 }
666 match budget {
667 Some(b) if b < 1 => {
668 Err("signer's path length budget is exhausted; cannot issue a CA".into())
669 }
670 Some(b) => Ok(Some(b as u32)), None => Ok(None), }
673 }
674 _ => {
675 let err = format!(
676 "Cannot verify the crtificate chain for {:?}",
677 signer.subject_name()
678 );
679 Err(err.into())
680 }
681 }
682}
683
684fn can_sign_cert(cert: &Certificate) -> Result<bool, Box<dyn std::error::Error>> {
685 if cert.pkey.is_none() {
686 return Ok(false);
687 }
688
689 let der = cert.x509.to_der()?;
690 let (_, parsed_cert) = parse_x509_certificate(&der)?;
691
692 let mut is_ca = false;
693 let mut can_sign = false;
694 let now = Asn1Time::days_from_now(0)?;
695 let valid_time = cert.x509.not_before().compare(&now)? != std::cmp::Ordering::Greater
696 && cert.x509.not_after().compare(&now)? == std::cmp::Ordering::Greater;
697
698 for ext in parsed_cert.tbs_certificate.extensions().iter() {
699 match &ext.parsed_extension() {
700 ParsedExtension::BasicConstraints(bc) => {
701 is_ca = bc.ca;
702 }
703 ParsedExtension::KeyUsage(ku) => {
704 can_sign = ku.key_cert_sign();
705 }
706 _ => {}
707 }
708 }
709 Ok(is_ca && can_sign && valid_time)
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715
716 use std::io::Write;
717 use std::path::Path;
718 use tempfile::NamedTempFile;
719 use x509_parser::extensions::GeneralName;
720
721 #[test]
722 fn create_ca_with_path_len_set() {
723 let ca = CertBuilder::new()
724 .common_name("My Test Ca")
725 .is_ca(true)
726 .pathlen(0)
727 .build_and_self_sign()
728 .unwrap();
729 assert_eq!(ca.x509.pathlen(), Some(0));
730 }
731
732 #[test]
733 fn create_ca_with_no_path_len_set() {
734 let ca = CertBuilder::new()
735 .common_name("My Test Ca")
736 .is_ca(true)
737 .build_and_self_sign()
738 .unwrap();
739 assert_eq!(ca.x509.pathlen(), None);
740 }
741
742 #[test]
743 fn key_usage_is_marked_critical() {
744 let ca = CertBuilder::new()
745 .common_name("My Test Ca")
746 .is_ca(true)
747 .build_and_self_sign()
748 .unwrap();
749 let leaf = CertBuilder::new()
750 .common_name("leaf")
751 .key_usage([Usage::certsign].into_iter().collect())
752 .build_and_sign(&ca)
753 .unwrap();
754 let der = leaf.x509.to_der().unwrap();
755 let (_, parsed) = parse_x509_certificate(&der).unwrap();
756
757 let ku = parsed
758 .tbs_certificate
759 .extensions()
760 .iter()
761 .find(|ext| matches!(ext.parsed_extension(), ParsedExtension::KeyUsage(_)))
762 .expect("certificate should carry a KeyUsage extension");
763
764 assert!(ku.critical, "KeyUsage must be critical (RFC 5280 §4.2.1.3)");
765 }
766
767 #[test]
768 fn extended_key_usage_is_not_marked_critical() {
769 let ca = CertBuilder::new()
770 .common_name("My Test Ca")
771 .is_ca(true)
772 .build_and_self_sign()
773 .unwrap();
774 let leaf = CertBuilder::new()
775 .common_name("leaf")
776 .key_usage([Usage::serverauth].into_iter().collect())
777 .build_and_sign(&ca)
778 .unwrap();
779 let der = leaf.x509.to_der().unwrap();
780 let (_, parsed) = parse_x509_certificate(&der).unwrap();
781
782 let eku = parsed
783 .tbs_certificate
784 .extensions()
785 .iter()
786 .find(|ext| matches!(ext.parsed_extension(), ParsedExtension::ExtendedKeyUsage(_)))
787 .expect("certificate should carry a ExtendedKeyUsage extension");
788
789 assert!(!eku.critical, "Extened KeyUsage not critical by default");
790 }
791
792 #[test]
793 fn san_names_should_be_added_correctly() {
794 let ca = CertBuilder::new()
795 .common_name("My Test Ca")
796 .is_ca(true)
797 .build_and_self_sign()
798 .unwrap();
799 let leaf = CertBuilder::new()
800 .common_name("leaf")
801 .alternative_names(vec!["localhost", "127.0.0.1", "::1"])
802 .key_usage([Usage::serverauth].into_iter().collect())
803 .build_and_sign(&ca)
804 .unwrap();
805 let der = leaf.x509.to_der().unwrap();
806 let (_, parsed) = parse_x509_certificate(&der).unwrap();
807 let san = parsed
808 .subject_alternative_name()
809 .unwrap()
810 .expect("leaf should carry a SubjectAltName extension");
811
812 let mut dns: Vec<&str> = Vec::new();
813 let mut ips: Vec<IpAddr> = Vec::new();
814 for gn in &san.value.general_names {
815 match gn {
816 GeneralName::DNSName(name) => dns.push(name),
817 GeneralName::IPAddress(bytes) => ips.push(match bytes.len() {
818 4 => IpAddr::from(<[u8; 4]>::try_from(*bytes).unwrap()),
819 16 => IpAddr::from(<[u8; 16]>::try_from(*bytes).unwrap()),
820 n => panic!("iPAddress SAN must be 4 or 16 octets, got {n}"),
821 }),
822 other => panic!("unexpected GeneralName in SAN: {other:?}"),
823 }
824 }
825
826 dns.sort_unstable();
828 ips.sort_unstable();
829
830 assert_eq!(dns, ["leaf", "localhost"]);
833 assert_eq!(
834 ips,
835 [
836 "127.0.0.1".parse::<IpAddr>().unwrap(),
837 "::1".parse::<IpAddr>().unwrap()
838 ]
839 )
840 }
841
842 #[test]
843 fn common_name_listed_in_alternative_names_is_not_duplicated() {
844 let ca = CertBuilder::new()
845 .common_name("My Test Ca")
846 .is_ca(true)
847 .build_and_self_sign()
848 .unwrap();
849 let leaf = CertBuilder::new()
851 .common_name("example.com")
852 .alternative_names(vec!["example.com", "www.example.com"])
853 .key_usage([Usage::serverauth].into_iter().collect())
854 .build_and_sign(&ca)
855 .unwrap();
856
857 let der = leaf.x509.to_der().unwrap();
858 let (_, parsed) = parse_x509_certificate(&der).unwrap();
859 let san = parsed
860 .subject_alternative_name()
861 .unwrap()
862 .expect("leaf should carry a SubjectAltName");
863
864 let mut dns: Vec<&str> = san
865 .value
866 .general_names
867 .iter()
868 .filter_map(|gn| match gn {
869 GeneralName::DNSName(n) => Some(*n),
870 _ => None,
871 })
872 .collect();
873 dns.sort_unstable();
874
875 assert_eq!(dns, ["example.com", "www.example.com"]);
876 }
877
878 #[test]
879 fn incomplete_chain_without_root_is_rejected() {
880 let ca = CertBuilder::new()
881 .common_name("Root")
882 .is_ca(true)
883 .pathlen(2)
884 .build_and_self_sign()
885 .unwrap();
886 let inter = CertBuilder::new()
887 .common_name("Inter")
888 .is_ca(true)
889 .pathlen(1)
890 .build_and_sign_with_chain(&ca, &[])
891 .unwrap();
892
893 let err = CertBuilder::new()
896 .common_name("Sub")
897 .is_ca(true)
898 .pathlen(0)
899 .build_and_sign_with_chain(&inter, &[])
900 .expect_err("incomplete chain (missing root) must be rejected");
901 assert!(
902 err.to_string().contains("Could not find self signed root"),
903 "got: {err}"
904 );
905 }
906
907 #[test]
908 fn unlimited_root_can_issue_ca() {
909 let root = CertBuilder::new()
910 .common_name("Unlimited Root")
911 .is_ca(true) .build_and_self_sign()
913 .unwrap();
914 assert_eq!(root.x509.pathlen(), None);
915
916 let inter = CertBuilder::new()
918 .common_name("Inter")
919 .is_ca(true)
920 .pathlen(5)
921 .build_and_sign_with_chain(&root, &[])
922 .unwrap();
923 assert_eq!(inter.x509.pathlen(), Some(5));
924 }
925
926 #[test]
927 fn create_cert_chain_and_verify() {
928 let ca = CertBuilder::new()
929 .common_name("My Test Ca")
930 .is_ca(true)
931 .pathlen(1)
932 .build_and_self_sign()
933 .unwrap();
934 let chain: Vec<&Certificate> = Vec::new();
935 let inter_ca = CertBuilder::new()
936 .common_name("My Test inter Ca")
937 .is_ca(true)
938 .pathlen(0)
939 .build_and_sign_with_chain(&ca, chain.as_slice())
940 .unwrap();
941 let leaf = CertBuilder::new()
942 .common_name("My Test leaf")
943 .build_and_sign(&inter_ca)
944 .unwrap();
945 assert_eq!(ca.x509.pathlen(), Some(1));
946 assert_eq!(leaf.x509.pathlen(), None);
947 assert!(verify_cert(&leaf.x509, &ca.x509, vec![&inter_ca.x509]).unwrap());
948 }
949
950 #[test]
951 fn create_ca_cert_chain_and_verify() {
952 let ca = CertBuilder::new()
953 .common_name("My Test Ca")
954 .is_ca(true)
955 .pathlen(2)
956 .build_and_self_sign()
957 .unwrap();
958 let chain: Vec<&Certificate> = Vec::new();
959 let inter_ca = CertBuilder::new()
960 .common_name("My Test inter Ca")
961 .is_ca(true)
962 .pathlen(1)
963 .build_and_sign_with_chain(&ca, chain.as_slice())
964 .unwrap();
965 let leaf = CertBuilder::new()
966 .common_name("leaf ca")
967 .is_ca(true)
968 .pathlen(0)
969 .build_and_sign_with_chain(&inter_ca, &[&ca])
970 .unwrap();
971
972 assert_eq!(leaf.x509.pathlen(), Some(0));
973 }
974
975 #[test]
976 fn can_not_create_ca_cert_chain_with_wrong_intermediate_ca_path() {
977 let ca = CertBuilder::new()
978 .common_name("My Test Ca")
979 .is_ca(true)
980 .pathlen(2)
981 .build_and_self_sign()
982 .unwrap();
983 let chain: Vec<&Certificate> = Vec::new();
984 let inter_ca = CertBuilder::new()
985 .common_name("My Test inter Ca")
986 .is_ca(true)
987 .pathlen(0)
988 .build_and_sign_with_chain(&ca, chain.as_slice())
989 .unwrap();
990 let err = CertBuilder::new()
991 .common_name("leaf ca")
992 .is_ca(true)
993 .pathlen(0)
994 .build_and_sign_with_chain(&inter_ca, &[&ca])
995 .expect_err("");
996
997 assert!(
998 err.to_string()
999 .contains("signer's path length budget is exhausted; cannot issue a CA"),
1000 "signer's path length budget is exhausted; cannot issue a CA got: {err}"
1001 );
1002 }
1003
1004 #[test]
1005 fn root_ca_have_one_path_length_and_can_not_sign_ca() {
1006 let ca = CertBuilder::new()
1007 .common_name("My Test Ca")
1008 .is_ca(true)
1009 .pathlen(1)
1010 .build_and_self_sign()
1011 .unwrap();
1012
1013 let chain: Vec<&Certificate> = Vec::new();
1014
1015 let err = CertBuilder::new()
1018 .common_name("My Test inter Ca")
1019 .is_ca(true)
1020 .pathlen(1)
1021 .build_and_sign_with_chain(&ca, chain.as_slice())
1022 .expect_err("inter CA with pathlen(1) under root pathlen(1) must be rejected");
1023
1024 assert!(
1025 err.to_string()
1026 .contains("requested pathLen exceeds what the signer's chain permits"),
1027 "expected a pathLen-exceeds error, got: {err}"
1028 );
1029 }
1030
1031 #[test]
1032 fn build_and_sign_without_signer_private_key_errors_not_panics() {
1033 let ca = CertBuilder::new()
1034 .common_name("My Test Ca")
1035 .is_ca(true)
1036 .build_and_self_sign()
1037 .unwrap();
1038 let keyless_ca = Certificate {
1039 x509: ca.x509.clone(),
1040 pkey: None,
1041 };
1042
1043 let err = CertBuilder::new()
1044 .common_name("leaf")
1045 .build_and_sign(&keyless_ca)
1046 .expect_err("signing with a key-less CA must return an error, not panic");
1047 assert!(
1048 err.to_string().contains("private key"),
1049 "expected a missing-private-key error, got: {err}"
1050 );
1051 }
1052
1053 #[test]
1054 fn ensure_that_supplied_private_key_is_used() {
1055 let ca_one = CertBuilder::new()
1056 .common_name("My Test Ca1")
1057 .is_ca(true)
1058 .build_and_self_sign()
1059 .unwrap();
1060 let ca_two = CertBuilder::new()
1061 .common_name("My Test Ca2")
1062 .is_ca(true)
1063 .private_key(ca_one.pkey.unwrap())
1064 .build_and_self_sign()
1065 .unwrap();
1066 assert_eq!(
1067 ca_one
1068 .x509
1069 .public_key()
1070 .unwrap()
1071 .public_key_to_der()
1072 .unwrap(),
1073 ca_two
1074 .x509
1075 .public_key()
1076 .unwrap()
1077 .public_key_to_der()
1078 .unwrap()
1079 )
1080 }
1081 #[test]
1082 fn save_certificate() {
1083 let ca = CertBuilder::new().common_name("My Test Ca").is_ca(true);
1084 match ca.build_and_self_sign() {
1085 Ok(cert) => {
1086 let output_file = NamedTempFile::new().unwrap();
1087 let full_path = output_file.path();
1088 let parent_dir: &Path = full_path.parent().unwrap();
1089 let file_name: &str = full_path.file_name().unwrap().to_str().unwrap();
1090 cert.save(parent_dir, file_name)
1091 .expect("Failed to save certificate and key");
1092 let written_file_path = parent_dir.join(file_name);
1093 assert!(written_file_path.exists(), "File was not created");
1094 }
1095 Err(_) => panic!("Failed to creat certificate"),
1096 }
1097 }
1098
1099 #[test]
1100 fn read_certificate_and_key_from_file() {
1101 let cert_pem = b"-----BEGIN CERTIFICATE-----
1102MIICiDCCAemgAwIBAgIUO3+y1WZPRRNs8dmZZTUHMj6TdiowCgYIKoZIzj0EAwQw
1103WzETMBEGA1UEAwwKTXkgVGVzdCBDYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0
1104b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMQ8wDQYDVQQKDAZteSBvcmcwHhcN
1105MjUwNzA4MTExMzI2WhcNMjYwNzA4MTExMzI2WjBbMRMwEQYDVQQDDApNeSBUZXN0
1106IENhMQswCQYDVQQGEwJTRTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlT
1107dG9ja2hvbG0xDzANBgNVBAoMBm15IG9yZzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOB
1108hgAEADZXcQK2ihgVTJeGx5FKm1x+R+ivygIvMnkv03faq1LpLU3doKX38DEO/cSW
1109Ev5u+kcjspXeeDPhqJFC8rRAz4awAMk+D0mXEms7xpFPh0HmI6NNcJc5eJ/8ZsEJ
1110GH1a34y0Yn6259gqlwAh2Eh9Nx1579BAanRr8lr+n1tZ09T/9AQho0gwRjAMBgNV
1111HRMEBTADAQH/MAsGA1UdDwQEAwIBBjApBgNVHREEIjAgggZjYS5jb22CCnd3dy5j
1112YS5jb22CCk15IFRlc3QgQ2EwCgYIKoZIzj0EAwQDgYwAMIGIAkIB8NVUgRIuNXmJ
1113cLCQ74Ub7Dqo71S0+iCrZF1YyJA8/q65aqMCT54k5Yx7HRBUUVHbCEpDXRqGPsIH
1114frfe5OmS3qICQgDBn07o0CcyfoSEd+Xoj2+/RBuU0vo9lUP7TKj7tssBxzEQFoxX
1115eE1qT98UIe78FZ+zqjwZTN9MCSsatuim6pXvOA==
1116-----END CERTIFICATE-----";
1117
1118 let key_pem = b"-----BEGIN PRIVATE KEY-----
1119MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAgvZTeQgGysadAX0r
1120aZB5Lk4vjHy5iVuKdvcGdYt9NBvYx+Ib3Uk7vqMag7M1jyHL0Xf9uNtT2mxBmzBG
11213CF+EgOhgYkDgYYABAA2V3ECtooYFUyXhseRSptcfkfor8oCLzJ5L9N32qtS6S1N
11223aCl9/AxDv3ElhL+bvpHI7KV3ngz4aiRQvK0QM+GsADJPg9JlxJrO8aRT4dB5iOj
1123TXCXOXif/GbBCRh9Wt+MtGJ+tufYKpcAIdhIfTcdee/QQGp0a/Ja/p9bWdPU//QE
1124IQ==
1125-----END PRIVATE KEY-----";
1126
1127 let mut cert_file = NamedTempFile::new().expect("Failed to create temp cert file");
1128 let mut key_file = NamedTempFile::new().expect("Failed to create temp key file");
1129
1130 cert_file.write_all(cert_pem).expect("Failed to write cert");
1131 key_file.write_all(key_pem).expect("Failed to write key");
1132
1133 let result = Certificate::load_cert_and_key(cert_file.path(), key_file.path());
1134 assert!(
1135 result.is_ok(),
1136 "Failed to load cert and key: {:?}",
1137 result.err()
1138 );
1139 }
1140
1141 #[test]
1142 fn load_cert_reads_cert_without_key() {
1143 use std::io::Write;
1144 let cert = CertBuilder::new()
1146 .common_name("load-cert-test")
1147 .is_ca(true)
1148 .build_and_self_sign()
1149 .unwrap();
1150 let mut file = tempfile::NamedTempFile::new().unwrap();
1151 file.write_all(&cert.x509.to_pem().unwrap()).unwrap();
1152
1153 let loaded = Certificate::load_cert(file.path()).unwrap();
1155
1156 assert!(
1158 loaded.pkey.is_none(),
1159 "load_cert must not carry a private key"
1160 );
1161 assert_eq!(
1162 loaded
1163 .x509
1164 .subject_name()
1165 .try_cmp(cert.x509.subject_name())
1166 .unwrap(),
1167 std::cmp::Ordering::Equal,
1168 );
1169 }
1170
1171 #[test]
1172 fn load_cert_missing_file_errors() {
1173 let err = Certificate::load_cert("/no/such/file/here.pem").expect_err("msg");
1174 assert!(
1175 err.to_string().contains("No such file or directory"),
1176 "No such file or directory, got: {err}"
1177 );
1178 }
1179}