1use super::builder::{BuilderFields, UseesBuilderFields, select_hash};
2use super::common::{X509Common, X509Parts, create_asn1_time_from_date};
3use super::enforce_path_len;
4#[cfg(feature = "pqc")]
5use super::key::reject_mlkem_signing;
6use super::key::{
7 is_digestless_key, select_key, sign_certificate_digestless, sign_x509_req_digestless,
8};
9use super::policy::append_certificate_policies;
10use super::usage::get_key_usage;
11#[cfg(feature = "pqc")]
12use super::usage::validate_pqc_key_usage;
13use super::{Certificate, CertificatePolicy, Usage, ca_basic_constraints, can_sign_cert};
14use super::{add_san_entry, san_names};
15use openssl::asn1::{Asn1Object, Asn1OctetString, Asn1Time};
16use openssl::bn::BigNum;
17use openssl::hash::{MessageDigest, hash};
18use openssl::nid::Nid;
19use openssl::pkey::{PKey, Private};
20use openssl::stack::Stack;
21use openssl::x509::extension::{
22 AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
23};
24use openssl::x509::{X509, X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder};
25use std::collections::HashSet;
26use std::error::Error;
27use std::net::{Ipv4Addr, Ipv6Addr};
28use std::path::Path;
29use x509_parser::certification_request::X509CertificationRequest;
30use x509_parser::extensions::{GeneralName, ParsedExtension};
31use x509_parser::prelude::FromDer;
32
33pub struct Csr {
35 pub csr: X509Req,
37 pub pkey: Option<PKey<Private>>,
41}
42
43impl X509Parts for Csr {
44 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
45 Ok(self.csr.to_pem()?)
46 }
47
48 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
49 match self.pkey {
50 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
51 _ => Err("No private key found".into()),
52 }
53 }
54 fn pem_extension(&self) -> &'static str {
55 "_csr.pem"
56 }
57}
58pub trait CsrX509Common: X509Common {}
60impl CsrX509Common for Csr {}
61
62pub struct CsrOptions {
64 valid_to: Asn1Time,
65 valid_from: Asn1Time,
66 ca: bool,
67 policies: Vec<CertificatePolicy>,
68 path_len: Option<u32>,
69 chain: Vec<Certificate>,
70}
71impl Default for CsrOptions {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl CsrOptions {
78 pub fn new() -> Self {
83 Self {
84 ca: false,
85 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), policies: Default::default(),
88 path_len: None,
89 chain: Vec::new(),
90 }
91 }
92
93 pub fn valid_from(mut self, valid_from: &str) -> Self {
98 self.valid_from =
99 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
100 self
101 }
102
103 pub fn valid_to(mut self, valid_to: &str) -> Self {
108 self.valid_to =
109 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
110 self
111 }
112
113 pub fn is_ca(mut self, ca: bool) -> Self {
118 self.ca = ca;
119 self
120 }
121 pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
126 self.policies = policies;
127 self
128 }
129 pub fn pathlen(mut self, path_len: u32, chain: Vec<Certificate>) -> Self {
135 self.path_len = Some(path_len);
136 self.chain = chain;
137 self
138 }
139}
140
141impl Csr {
142 pub fn load_csr<C: AsRef<Path>>(csr_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
144 let cert_pem = std::fs::read(csr_pem_file)?;
145 let cs_req = X509Req::from_pem(&cert_pem)?;
146 Ok(Self {
147 csr: cs_req,
148 pkey: None,
149 })
150 }
151 pub fn build_signed_certificate(
153 &self,
154 signer: &Certificate,
155 options: CsrOptions,
156 ) -> Result<Certificate, Box<dyn std::error::Error>> {
157 verify_csr_proof_of_possession(&self.csr)?;
160
161 let can_sign = can_sign_cert(signer)?;
162 if !can_sign {
163 let err = format!(
164 "Cannot sign with signer certificate {:?}: it must be a valid CA \
165 (BasicConstraints CA flag set, KeyUsage keyCertSign, within its validity \
166 period) and have an associated private key",
167 signer.x509.subject_name()
168 );
169 return Err(err.into());
170 }
171 let chain_refs: Vec<&X509> = options.chain.iter().map(|c| &c.x509).collect();
172 enforce_path_len(options.ca, options.path_len, signer, &chain_refs)?;
173 let signer_key = signer
174 .pkey
175 .as_ref()
176 .ok_or("signer certificate has no associated private key; cannot sign")?;
177 let mut builder = X509Builder::new()?;
178 builder.set_version(2)?;
179 builder.set_subject_name(self.csr.subject_name())?;
180 builder.set_issuer_name(signer.x509.subject_name())?;
181 let csr_public_key = self.csr.public_key()?;
182 builder.set_pubkey(&csr_public_key)?;
183
184 let der = self.csr.to_der()?;
185 let parsed_csr = X509CertificationRequest::from_der(&der)?;
186
187 let req_ext = parsed_csr.1.requested_extensions();
188 let mut any_key_used = false;
189 let mut requested: HashSet<Usage> = HashSet::new();
190 if let Some(exts) = req_ext {
191 for ext in exts {
192 match ext {
193 ParsedExtension::KeyUsage(ku) => {
194 any_key_used = true;
195 let mut cert_sign_added = false;
196 let mut crl_sign_added = false;
197 let mut usage = openssl::x509::extension::KeyUsage::new();
198 usage.critical();
199 if ku.digital_signature() {
200 usage.digital_signature();
201 requested.insert(Usage::signature);
202 }
203 if ku.key_encipherment() {
204 usage.key_encipherment();
205 requested.insert(Usage::encipherment);
206 }
207 if ku.key_cert_sign() {
208 cert_sign_added = true;
209 usage.key_cert_sign();
210 requested.insert(Usage::certsign);
211 }
212 if ku.non_repudiation() {
213 usage.non_repudiation();
214 requested.insert(Usage::contentcommitment);
215 }
216 if ku.crl_sign() {
217 crl_sign_added = true;
218 usage.crl_sign();
219 requested.insert(Usage::crlsign);
220 }
221
222 if options.ca {
223 if !cert_sign_added {
224 usage.key_cert_sign();
225 }
226 if !crl_sign_added {
227 usage.crl_sign();
228 }
229 }
230 builder.append_extension(usage.build()?)?;
231 }
232 ParsedExtension::ExtendedKeyUsage(eku) => {
233 let mut ext = openssl::x509::extension::ExtendedKeyUsage::new();
234 if eku.server_auth {
235 ext.server_auth();
236 }
237 if eku.client_auth {
238 ext.client_auth();
239 }
240 if eku.code_signing {
241 ext.code_signing();
242 }
243 if eku.email_protection {
244 ext.email_protection();
245 }
246 builder.append_extension(ext.build()?)?;
247 }
248 ParsedExtension::SubjectAlternativeName(san) => {
249 let mut openssl_san =
250 openssl::x509::extension::SubjectAlternativeName::new();
251 for name in &san.general_names {
252 match name {
253 GeneralName::DNSName(dns) => {
254 openssl_san.dns(dns);
255 }
256 GeneralName::IPAddress(bytes) => match bytes.len() {
257 4 => {
258 let octets = <[u8; 4]>::try_from(*bytes)
259 .map_err(|_| "unreachable: 4 byte slice")?;
260 openssl_san.ip(&Ipv4Addr::from(octets).to_string());
261 }
262 16 => {
263 let octets = <[u8; 16]>::try_from(*bytes)
264 .map_err(|_| "unreachable: 16 byte slice")?;
265 openssl_san.ip(&Ipv6Addr::from(octets).to_string());
266 }
267 len => {
268 return Err(format!(
269 "CSR contains a malformed iPAddress SubjectAltName: \
270 expected 4 or 16 octets, got {len}"
271 )
272 .into());
273 }
274 },
275 GeneralName::RFC822Name(email) => {
276 openssl_san.email(email);
277 }
278 GeneralName::URI(uri) => {
279 openssl_san.uri(uri);
280 }
281 GeneralName::RegisteredID(oid) => {
282 openssl_san.rid(&oid.to_id_string());
283 }
284
285 GeneralName::DirectoryName(_) => {
286 return Err(unsupported_san("directoryName"));
287 }
288 GeneralName::OtherName(..) => {
289 return Err(unsupported_san("otherName"));
290 }
291 GeneralName::X400Address(_) => {
292 return Err(unsupported_san("x400Address"));
293 }
294 GeneralName::EDIPartyName(_) => {
295 return Err(unsupported_san("ediPartyName"));
296 }
297 }
298 }
299 builder.append_extension(
300 openssl_san.build(&builder.x509v3_context(None, None))?,
301 )?;
302 }
303 _ => {
304 println!("Unsupported extension: {:?}", ext);
305 }
306 }
307 }
308 }
309 #[cfg(feature = "pqc")]
310 validate_pqc_key_usage(&csr_public_key, &requested)?;
311
312 if options.ca {
313 let result = ca_basic_constraints(options.path_len)?;
314 builder.append_extension(result)?;
315 if !any_key_used {
316 let key_usage = KeyUsage::new()
317 .critical()
318 .key_cert_sign()
319 .crl_sign()
320 .build()
321 .unwrap();
322 builder.append_extension(key_usage)?;
323 }
324 } else {
325 builder.append_extension(BasicConstraints::new().build()?)?;
326 }
327 builder.set_not_before(&options.valid_from)?;
328 builder.set_not_after(&options.valid_to)?;
329 let serial_number = {
330 let mut serial = BigNum::new()?;
331 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
332 serial.to_asn1_integer()?
333 };
334 builder.set_serial_number(&serial_number)?;
335 if signer.x509.subject_key_id().is_some() {
336 let aki = AuthorityKeyIdentifier::new()
337 .keyid(true)
338 .issuer(false)
339 .build(&builder.x509v3_context(Some(&signer.x509), None))?;
340 builder.append_extension(aki)?;
341 }
342 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = self.csr.public_key().unwrap().public_key_to_der()?;
344 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
345 let der_encoded = yasna::construct_der(|writer| {
346 writer.write_bytes(ski_hash.as_ref());
347 });
348 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
349 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
350 builder.append_extension(ext)?;
351 append_certificate_policies(&mut builder, &options.policies)?;
352 let cert: X509 = if is_digestless_key(signer_key) {
353 let builder_cert = builder.build();
354 sign_certificate_digestless(&builder_cert, signer_key)
355 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
356 builder_cert
357 } else {
358 builder.sign(signer_key, MessageDigest::sha256())?;
359 builder.build()
360 };
361
362 Ok(Certificate {
363 x509: cert,
364 pkey: None,
365 })
366 }
367}
368
369pub struct CsrBuilder {
371 fields: BuilderFields,
372}
373impl UseesBuilderFields for CsrBuilder {
374 fn fields_mut(&mut self) -> &mut BuilderFields {
375 &mut self.fields
376 }
377}
378impl Default for CsrBuilder {
379 fn default() -> Self {
380 Self::new()
381 }
382}
383
384impl CsrBuilder {
385 pub fn new() -> Self {
387 Self {
388 fields: BuilderFields::default(),
389 }
390 }
391
392 pub fn certificate_signing_request(self) -> Result<Csr, Box<dyn std::error::Error>> {
425 let mut name_builder = X509NameBuilder::new()?;
426 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
427 if !self.fields.country_name.trim().is_empty() {
428 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
429 }
430 if !self.fields.state_province.trim().is_empty() {
431 name_builder
432 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
433 }
434 if !self.fields.locality_time.trim().is_empty() {
435 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
436 }
437 if !self.fields.organization.trim().is_empty() {
438 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
439 }
440 let name = name_builder.build();
441 let mut builder = X509ReqBuilder::new()?;
442 builder.set_version(0)?;
443 builder.set_subject_name(&name)?;
444 let pkey = match &self.fields.existing_key {
445 Some(pkey) => pkey.clone(),
446 None => select_key(&self.fields.key_type)?,
447 };
448 builder.set_pubkey(&pkey)?;
449 let key_usage = self.fields.usage.clone().unwrap_or_default();
450
451 #[cfg(feature = "pqc")]
454 validate_pqc_key_usage(&pkey, &key_usage)?;
455
456 #[cfg(feature = "pqc")]
459 reject_mlkem_signing(
460 &pkey,
461 "ML-KEM (FIPS 203) is a key-encapsulation key and cannot sign a CSR \
462 (PKCS#10 requires a self-signature for proof-of-possession). Issue the \
463 ML-KEM certificate directly with CertBuilder::build_and_sign() using a \
464 signing CA instead.",
465 )?;
466
467 let mut extensions = Stack::new()?;
468
469 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
470 if tracked_key_usage.is_used() {
471 extensions.push(tracked_key_usage.into_inner().build()?)?;
472 }
473 if tracked_extended_key_usage.is_used() {
474 extensions.push(tracked_extended_key_usage.into_inner().build()?)?;
475 }
476
477 let names = san_names(&self.fields, true);
480 if !names.is_empty() {
481 let mut san = SubjectAlternativeName::new();
483 for n in &names {
484 add_san_entry(&mut san, n);
485 }
486 extensions.push(san.build(&builder.x509v3_context(None))?)?;
487 }
488
489 builder.add_extensions(&extensions)?;
490 let csr: X509Req = if is_digestless_key(&pkey) {
491 let builder_csr = builder.build();
492 sign_x509_req_digestless(&builder_csr, &pkey)
493 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
494 builder_csr
495 } else {
496 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
497 builder.build()
498 };
499 Ok(Csr {
500 csr,
501 pkey: Some(pkey),
502 })
503 }
504}
505
506fn verify_csr_proof_of_possession(csr: &X509Req) -> Result<(), Box<dyn std::error::Error>> {
513 let public_key = csr.public_key()?;
514 if !csr.verify(&public_key)? {
515 return Err(
516 "CSR proof-of-possession check failed: the request signature does \
517 not verify against its own public key"
518 .into(),
519 );
520 }
521 Ok(())
522}
523
524fn unsupported_san(kind: &str) -> Box<dyn Error> {
525 format!("CSR contains a SubjectAltName type this crate cannot reproduce: {kind}").into()
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531 use crate::certificate::CertBuilder;
532 #[cfg(feature = "pqc")]
533 use crate::certificate::key::generate_pqc_key;
534 #[cfg(feature = "pqc")]
535 use crate::certificate::key::sign_x509_req_digestless;
536 use openssl::ec::{EcGroup, EcKey};
537 use openssl::x509::X509Req;
538 #[cfg(feature = "pqc")]
539 use openssl::x509::X509ReqBuilder;
540 #[cfg(feature = "pqc")]
541 use openssl::x509::extension::KeyUsage;
542 use std::io::Write;
543 use tempfile::NamedTempFile;
544
545 #[test]
546 fn create_ca_cert_from_csr_with_path_len() {
547 let ca = CertBuilder::new()
548 .common_name("My Test Ca")
549 .is_ca(true)
550 .pathlen(2)
551 .build_and_self_sign()
552 .unwrap();
553
554 let csr = CsrBuilder::new()
555 .common_name("leaf")
556 .certificate_signing_request()
557 .unwrap();
558 let cert = csr
559 .build_signed_certificate(&ca, CsrOptions::new().pathlen(1, vec![]).is_ca(true))
560 .unwrap();
561 assert_eq!(cert.x509.pathlen(), Some(1));
562 }
563
564 #[test]
565 fn create_non_ca_cert_from_csr_with_path_len() {
566 let ca = CertBuilder::new()
567 .common_name("My Test Ca")
568 .is_ca(true)
569 .pathlen(2)
570 .build_and_self_sign()
571 .unwrap();
572
573 let csr = CsrBuilder::new()
574 .common_name("leaf")
575 .certificate_signing_request()
576 .unwrap();
577 let cert = csr
578 .build_signed_certificate(&ca, CsrOptions::new().pathlen(1, vec![]))
579 .unwrap();
580 assert_eq!(cert.x509.pathlen(), None);
581 }
582
583 #[test]
584 fn create_ca_cert_with_chain_from_csr_with_path_len() {
585 let ca = CertBuilder::new()
586 .common_name("My Test root Ca")
587 .is_ca(true)
588 .pathlen(3)
589 .build_and_self_sign()
590 .unwrap();
591 let interca1 = CertBuilder::new()
592 .common_name("My Test Ca1")
593 .is_ca(true)
594 .pathlen(2)
595 .build_and_sign_with_chain(&ca, &[])
596 .unwrap();
597 let interca2 = CertBuilder::new()
598 .common_name("My Test Ca2")
599 .is_ca(true)
600 .pathlen(1)
601 .build_and_sign_with_chain(&interca1, &[&ca])
602 .unwrap();
603 let csr = CsrBuilder::new()
604 .common_name("leaf")
605 .certificate_signing_request()
606 .unwrap();
607 let cert = csr
608 .build_signed_certificate(
609 &interca2,
610 CsrOptions::new().pathlen(0, vec![ca, interca1]).is_ca(true),
611 )
612 .unwrap();
613 assert_eq!(cert.x509.pathlen(), Some(0));
614 }
615
616 #[test]
617 fn create_ca_cert_with_chain_from_csr_with_budget_exhausted() {
618 let ca = CertBuilder::new()
619 .common_name("My Test root Ca")
620 .is_ca(true)
621 .pathlen(3)
622 .build_and_self_sign()
623 .unwrap();
624 let interca1 = CertBuilder::new()
625 .common_name("My Test Ca1")
626 .is_ca(true)
627 .pathlen(2)
628 .build_and_sign_with_chain(&ca, &[])
629 .unwrap();
630 let interca2 = CertBuilder::new()
631 .common_name("My Test Ca2")
632 .is_ca(true)
633 .pathlen(0)
634 .build_and_sign_with_chain(&interca1, &[&ca])
635 .unwrap();
636 let csr = CsrBuilder::new()
637 .common_name("leaf")
638 .certificate_signing_request()
639 .unwrap();
640 let err = csr
641 .build_signed_certificate(
642 &interca2,
643 CsrOptions::new().pathlen(0, vec![ca, interca1]).is_ca(true),
644 )
645 .err()
646 .expect("");
647 assert!(
648 err.to_string()
649 .contains("signer's path length budget is exhausted; cannot issue a CA"),
650 "signer's path length budget is exhausted; cannot issue a CA got: {err}"
651 );
652 }
653 #[test]
654 fn csr_inflated_pathlen_is_rejected() {
655 let ca = CertBuilder::new()
656 .common_name("root")
657 .is_ca(true)
658 .pathlen(1)
659 .build_and_self_sign()
660 .unwrap();
661 let csr = CsrBuilder::new()
662 .common_name("leaf")
663 .certificate_signing_request()
664 .unwrap();
665 let err = csr
666 .build_signed_certificate(
667 &ca,
668 CsrOptions::new().is_ca(true).pathlen(1, vec![]), )
670 .err()
671 .expect("inflated pathLen must be rejected");
672 assert!(
673 err.to_string()
674 .contains("requested pathLen exceeds what the signer's chain permits"),
675 "got: {err}"
676 );
677 }
678
679 #[test]
680 fn csr_incomplete_chain_is_rejected() {
681 let ca = CertBuilder::new()
682 .common_name("root")
683 .is_ca(true)
684 .pathlen(2)
685 .build_and_self_sign()
686 .unwrap();
687 let inter = CertBuilder::new()
688 .common_name("inter")
689 .is_ca(true)
690 .pathlen(1)
691 .build_and_sign_with_chain(&ca, &[])
692 .unwrap();
693 let csr = CsrBuilder::new()
694 .common_name("leaf")
695 .certificate_signing_request()
696 .unwrap();
697 let err = csr
698 .build_signed_certificate(
699 &inter,
700 CsrOptions::new().is_ca(true).pathlen(0, vec![]), )
702 .err()
703 .expect("incomplete chain must be rejected");
704 assert!(
705 err.to_string().contains("Could not find self signed root"),
706 "got: {err}"
707 );
708 }
709
710 #[test]
711 fn csr_unlimited_signer_allows_ca_issuance() {
712 let ca = CertBuilder::new()
713 .common_name("root")
714 .is_ca(true) .build_and_self_sign()
716 .unwrap();
717 let csr = CsrBuilder::new()
718 .common_name("leaf")
719 .certificate_signing_request()
720 .unwrap();
721 let cert = csr
722 .build_signed_certificate(
723 &ca,
724 CsrOptions::new().is_ca(true).pathlen(5, vec![]), )
726 .unwrap();
727 assert_eq!(cert.x509.pathlen(), Some(5));
728 }
729 #[test]
730 fn build_signed_certificate_rejects_csr_with_bad_proof_of_possession() {
731 let ca = CertBuilder::new()
734 .common_name("My Test Ca")
735 .is_ca(true)
736 .build_and_self_sign()
737 .unwrap();
738
739 let good = CsrBuilder::new()
740 .common_name("leaf")
741 .certificate_signing_request()
742 .unwrap();
743
744 let mut der = good.csr.to_der().unwrap();
748 let last = der.len() - 1;
749 der[last] ^= 0xFF;
750 let tampered = Csr {
751 csr: X509Req::from_der(&der).unwrap(),
752 pkey: None,
753 };
754
755 let err = tampered
756 .build_signed_certificate(&ca, CsrOptions::new())
757 .err()
758 .expect("CSR with broken proof-of-possession must be rejected");
759 assert!(
760 err.to_string().contains("proof-of-possession"),
761 "expected a proof-of-possession error, got: {err}"
762 );
763 }
764
765 fn csr_san_dns(csr: &Csr) -> Option<Vec<String>> {
768 use x509_parser::certification_request::X509CertificationRequest;
769 use x509_parser::prelude::FromDer;
770
771 let der = csr.csr.to_der().unwrap();
772 let (_, parsed) = X509CertificationRequest::from_der(&der).unwrap();
773 parsed.requested_extensions()?.find_map(|ext| match ext {
774 ParsedExtension::SubjectAlternativeName(san) => Some(
775 san.general_names
776 .iter()
777 .filter_map(|gn| match gn {
778 GeneralName::DNSName(n) => Some((*n).to_string()),
779 _ => None,
780 })
781 .collect(),
782 ),
783 _ => None,
784 })
785 }
786
787 #[test]
788 fn csr_common_name_is_included_in_the_san() {
789 let csr = CsrBuilder::new()
790 .common_name("leaf.example.com")
791 .certificate_signing_request()
792 .unwrap();
793
794 assert_eq!(
795 csr_san_dns(&csr).expect("a CSR with a common name should request a SAN"),
796 ["leaf.example.com"]
797 );
798 }
799
800 #[test]
801 fn csr_common_name_listed_in_alternative_names_is_not_duplicated() {
802 let csr = CsrBuilder::new()
803 .common_name("example.com")
804 .alternative_names(vec!["example.com", "www.example.com"])
805 .certificate_signing_request()
806 .unwrap();
807
808 let mut dns = csr_san_dns(&csr).expect("CSR should request a SAN");
809 dns.sort();
810 assert_eq!(dns, ["example.com", "www.example.com"]);
811 }
812
813 #[test]
814 fn csr_without_any_names_emits_no_san_extension() {
815 let csr = CsrBuilder::new()
819 .common_name(" ")
820 .certificate_signing_request()
821 .unwrap();
822
823 assert_eq!(csr_san_dns(&csr), None);
824 }
825
826 #[test]
827 fn ensure_that_supplied_private_key_is_used() {
828 let ca = CertBuilder::new()
829 .common_name("My Test Ca")
830 .is_ca(true)
831 .build_and_self_sign()
832 .unwrap();
833 let csr = CsrBuilder::new()
834 .common_name("leaf")
835 .private_key(ca.pkey.clone().unwrap())
836 .certificate_signing_request()
837 .unwrap();
838 let cert = csr
839 .build_signed_certificate(
840 &ca,
841 CsrOptions::new().is_ca(true), )
843 .unwrap();
844 assert_eq!(
845 ca.x509.public_key().unwrap().public_key_to_der().unwrap(),
846 cert.x509.public_key().unwrap().public_key_to_der().unwrap()
847 )
848 }
849 #[test]
850 fn san_directory_name_should_give_an_error() {
851 let ca = CertBuilder::new()
852 .common_name("My Test Ca")
853 .is_ca(true)
854 .build_and_self_sign()
855 .unwrap();
856 let bad_csr = csr_with_directory_name_san();
857 let err = bad_csr
858 .build_signed_certificate(&ca, CsrOptions::new())
859 .unwrap_err();
860 assert!(
861 err.to_string().contains("directoryName"),
862 "expected error for san with directory name"
863 )
864 }
865
866 #[cfg(feature = "pqc")]
867 #[test]
868 fn do_not_allow_csr_with_pqc_key_and_encipherment_to_generate_certificate() {
869 let ca = CertBuilder::new()
870 .common_name("My Test Ca")
871 .is_ca(true)
872 .build_and_self_sign()
873 .unwrap();
874
875 let pkey = generate_pqc_key("ML-DSA-65").unwrap();
876 let mut builder = X509ReqBuilder::new().unwrap();
877 builder
878 .set_pubkey(&pkey)
879 .expect("failed to set public key in csr");
880 let mut exts = Stack::new().unwrap();
881 exts.push(
882 KeyUsage::new()
883 .critical()
884 .key_encipherment()
885 .build()
886 .unwrap(),
887 )
888 .unwrap();
889 builder.set_version(0).unwrap();
890 builder.add_extensions(&exts).unwrap();
891 let req = builder.build();
892
893 let _ = sign_x509_req_digestless(&req, &pkey);
894 let csr = Csr {
895 csr: req,
896 pkey: None,
897 };
898 let err = csr
899 .build_signed_certificate(&ca, CsrOptions::new())
900 .err()
901 .expect("a PQC signature key requesting keyEncipherment must be rejected");
902 assert!(
903 err.to_string().contains("keyEncipherment"),
904 "expected a keyEncipherment error, got: {err}"
905 );
906 }
907
908 #[test]
909 fn test_reading_csr_from_file() {
910 let csr_data = b"-----BEGIN CERTIFICATE REQUEST-----
911MIICzDCCAbQCAQAwXTEVMBMGA1UEAwwMZXhhbXBsZTIuY29tMQswCQYDVQQGEwJT
912RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xDzANBgNV
913BAoMBk15IG9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeAXpCG
914hbIayESfdTzOO0DxIMsOAu4kUm0zF0W/+xDUHl6bGy3wlB9S9nzBG/qwqFZ27Om3
915o4zrZ8K8DBx0ERWNuhMmr0Nx8QpAWBEyxOc08Gn4c3XVBBkRZSn4AIqr9DGtcUqW
916tQZXvMGF6sRRljiEvOxO6zMzZKTGYwzIeQvH85cQ3uXsw0Kknsw/fcuywaAC8SS9
917aqs4jiEIgzdhxdH2OVXBNGj4cjVhK309JiWFHS9XJLNV/PKC+F1nkaANQwbW5A4F
9189vya4js9gk8f4SfF1u+qOJEvsDvAb+1xdjXPRzf77eGh3rC4KgGWQ6WrWfW8PItF
919BDg/jskq3bJXNL8CAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIM
920ZXhhbXBsZTIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAHeeSW8C6SMVhWiMvPn7iz
921FUHQedHRyPz6kTEfC01eNIbs0r4YghOAcm8PF67jncIXVrqgxo1uzq12qlV+0YYb
922jps31IbQNOz0eFLYvij15ielmOYeQZZ/2vqaGi3geVobLc6Ki5tadnA/NhjTN33j
923QcqDDic8riAOTbSQ6TH9KPTGJQOPk+taMpDGDHskIW0oME5iT2ewbhBHg6v/kSzy
924tss2kBY5O7vo2COtbNcwX5Xp9S2LH9kVUKr0GIjuQjwbv5xl+GNdDey09W9EDACU
925jcGV3++2wS4LN4h3CG4pWZ+LTXhm8ymhoWOapN95lfe3xLRAKFJwiLkGwS75++FW
926-----END CERTIFICATE REQUEST-----";
927 let mut csr_file = NamedTempFile::new().expect("Failed to create temp csr file");
928 csr_file.write_all(csr_data).expect("Failed to write csr");
929 let result = Csr::load_csr(csr_file.path());
930 assert!(result.is_ok(), "Failed to load csr: {:?}", result.err());
931 }
932
933 fn csr_with_directory_name_san() -> Csr {
934 let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
935 let pkey = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
936
937 let mut req = X509ReqBuilder::new().unwrap();
938 req.set_version(0).unwrap();
939
940 let mut subject = X509NameBuilder::new().unwrap();
941 subject.append_entry_by_text("CN", "leaf").unwrap();
942 req.set_subject_name(&subject.build()).unwrap();
943 req.set_pubkey(&pkey).unwrap();
944
945 let mut dir = X509NameBuilder::new().unwrap();
946 dir.append_entry_by_text("CN", "some directory name")
947 .unwrap();
948 let dir = dir.build();
949
950 let mut san = SubjectAlternativeName::new();
951 san.dir_name2(dir); let ext = san.build(&req.x509v3_context(None)).unwrap();
953
954 let mut exts = Stack::new().unwrap();
955 exts.push(ext).unwrap();
956 req.add_extensions(&exts).unwrap();
957
958 req.sign(&pkey, MessageDigest::sha256()).unwrap(); Csr {
961 csr: req.build(),
962 pkey: Some(pkey),
963 }
964 }
965}