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 openssl::asn1::{Asn1Object, Asn1OctetString, Asn1Time};
15use openssl::bn::BigNum;
16use openssl::hash::{MessageDigest, hash};
17use openssl::nid::Nid;
18use openssl::pkey::{PKey, Private};
19use openssl::stack::Stack;
20use openssl::x509::extension::{
21 AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
22};
23use openssl::x509::{X509, X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder};
24use std::collections::HashSet;
25use std::path::Path;
26use x509_parser::certification_request::X509CertificationRequest;
27use x509_parser::extensions::ParsedExtension;
28use x509_parser::prelude::FromDer;
29
30pub struct Csr {
32 pub csr: X509Req,
34 pub pkey: Option<PKey<Private>>,
38}
39
40impl X509Parts for Csr {
41 fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
42 Ok(self.csr.to_pem()?)
43 }
44
45 fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
46 match self.pkey {
47 Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
48 _ => Err("No private key found".into()),
49 }
50 }
51 fn pem_extension(&self) -> &'static str {
52 "_csr.pem"
53 }
54}
55pub trait CsrX509Common: X509Common {}
57impl CsrX509Common for Csr {}
58
59pub struct CsrOptions {
61 valid_to: Asn1Time,
62 valid_from: Asn1Time,
63 ca: bool,
64 policies: Vec<CertificatePolicy>,
65 path_len: Option<u32>,
66 chain: Vec<Certificate>,
67}
68impl Default for CsrOptions {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl CsrOptions {
75 pub fn new() -> Self {
80 Self {
81 ca: false,
82 valid_from: Asn1Time::days_from_now(0).unwrap(), valid_to: Asn1Time::days_from_now(365).unwrap(), policies: Default::default(),
85 path_len: None,
86 chain: Vec::new(),
87 }
88 }
89
90 pub fn valid_from(mut self, valid_from: &str) -> Self {
95 self.valid_from =
96 create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
97 self
98 }
99
100 pub fn valid_to(mut self, valid_to: &str) -> Self {
105 self.valid_to =
106 create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
107 self
108 }
109
110 pub fn is_ca(mut self, ca: bool) -> Self {
115 self.ca = ca;
116 self
117 }
118 pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
123 self.policies = policies;
124 self
125 }
126 pub fn pathlen(mut self, path_len: u32, chain: Vec<Certificate>) -> Self {
132 self.path_len = Some(path_len);
133 self.chain = chain;
134 self
135 }
136}
137
138impl Csr {
139 pub fn load_csr<C: AsRef<Path>>(csr_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
141 let cert_pem = std::fs::read(csr_pem_file)?;
142 let cs_req = X509Req::from_pem(&cert_pem)?;
143 Ok(Self {
144 csr: cs_req,
145 pkey: None,
146 })
147 }
148 pub fn build_signed_certificate(
150 &self,
151 signer: &Certificate,
152 options: CsrOptions,
153 ) -> Result<Certificate, Box<dyn std::error::Error>> {
154 verify_csr_proof_of_possession(&self.csr)?;
157
158 let can_sign = can_sign_cert(signer)?;
159 if !can_sign {
160 let err = format!(
161 "Cannot sign with signer certificate {:?}: it must be a valid CA \
162 (BasicConstraints CA flag set, KeyUsage keyCertSign, within its validity \
163 period) and have an associated private key",
164 signer.x509.subject_name()
165 );
166 return Err(err.into());
167 }
168 let chain_refs: Vec<&X509> = options.chain.iter().map(|c| &c.x509).collect();
169 enforce_path_len(options.ca, options.path_len, signer, &chain_refs)?;
170 let signer_key = signer
171 .pkey
172 .as_ref()
173 .ok_or("signer certificate has no associated private key; cannot sign")?;
174 let mut builder = X509Builder::new()?;
175 builder.set_version(2)?;
176 builder.set_subject_name(self.csr.subject_name())?;
177 builder.set_issuer_name(signer.x509.subject_name())?;
178 let csr_public_key = self.csr.public_key()?;
179 builder.set_pubkey(&csr_public_key)?;
180
181 let der = self.csr.to_der()?;
182 let parsed_csr = X509CertificationRequest::from_der(&der)?;
183
184 let req_ext = parsed_csr.1.requested_extensions();
185 let mut any_key_used = false;
186 let mut requested: HashSet<Usage> = HashSet::new();
187 if let Some(exts) = req_ext {
188 for ext in exts {
189 match ext {
190 ParsedExtension::KeyUsage(ku) => {
191 any_key_used = true;
192 let mut cert_sign_added = false;
193 let mut crl_sign_added = false;
194 let mut usage = openssl::x509::extension::KeyUsage::new();
195 if ku.digital_signature() {
196 usage.digital_signature();
197 requested.insert(Usage::signature);
198 }
199 if ku.key_encipherment() {
200 usage.key_encipherment();
201 requested.insert(Usage::encipherment);
202 }
203 if ku.key_cert_sign() {
204 cert_sign_added = true;
205 usage.key_cert_sign();
206 requested.insert(Usage::certsign);
207 }
208 if ku.non_repudiation() {
209 usage.non_repudiation();
210 requested.insert(Usage::contentcommitment);
211 }
212 if ku.crl_sign() {
213 crl_sign_added = true;
214 usage.crl_sign();
215 requested.insert(Usage::crlsign);
216 }
217
218 if options.ca {
219 if !cert_sign_added {
220 usage.key_cert_sign();
221 }
222 if !crl_sign_added {
223 usage.crl_sign();
224 }
225 }
226 builder.append_extension(usage.build()?)?;
227 }
228 ParsedExtension::ExtendedKeyUsage(eku) => {
229 let mut ext = openssl::x509::extension::ExtendedKeyUsage::new();
230 if eku.server_auth {
231 ext.server_auth();
232 }
233 if eku.client_auth {
234 ext.client_auth();
235 }
236 if eku.code_signing {
237 ext.code_signing();
238 }
239 if eku.email_protection {
240 ext.email_protection();
241 }
242 builder.append_extension(ext.build()?)?;
243 }
244 ParsedExtension::SubjectAlternativeName(san) => {
245 let mut openssl_san =
246 openssl::x509::extension::SubjectAlternativeName::new();
247 for name in &san.general_names {
248 if let x509_parser::extensions::GeneralName::DNSName(dns) = name {
249 openssl_san.dns(dns);
250 }
251 }
252 builder.append_extension(
253 openssl_san.build(&builder.x509v3_context(None, None))?,
254 )?;
255 }
256 _ => {
257 println!("Unsupported extension: {:?}", ext);
258 }
259 }
260 }
261 }
262 #[cfg(feature = "pqc")]
263 validate_pqc_key_usage(&csr_public_key, &requested)?;
264
265 if options.ca {
266 let result = ca_basic_constraints(options.path_len)?;
267 builder.append_extension(result)?;
268 if !any_key_used {
269 let key_usage = KeyUsage::new().key_cert_sign().crl_sign().build().unwrap();
270 builder.append_extension(key_usage)?;
271 }
272 } else {
273 builder.append_extension(BasicConstraints::new().build()?)?;
274 }
275 builder.set_not_before(&options.valid_from)?;
276 builder.set_not_after(&options.valid_to)?;
277 let serial_number = {
278 let mut serial = BigNum::new()?;
279 serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
280 serial.to_asn1_integer()?
281 };
282 builder.set_serial_number(&serial_number)?;
283 if signer.x509.subject_key_id().is_some() {
284 let aki = AuthorityKeyIdentifier::new()
285 .keyid(true)
286 .issuer(false)
287 .build(&builder.x509v3_context(Some(&signer.x509), None))?;
288 builder.append_extension(aki)?;
289 }
290 let oid = Asn1Object::from_str("2.5.29.14")?; let pubkey_der = self.csr.public_key().unwrap().public_key_to_der()?;
292 let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
293 let der_encoded = yasna::construct_der(|writer| {
294 writer.write_bytes(ski_hash.as_ref());
295 });
296 let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
297 let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
298 builder.append_extension(ext)?;
299 append_certificate_policies(&mut builder, &options.policies)?;
300 let cert: X509 = if is_digestless_key(signer_key) {
301 let builder_cert = builder.build();
302 sign_certificate_digestless(&builder_cert, signer_key)
303 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
304 builder_cert
305 } else {
306 builder.sign(signer_key, MessageDigest::sha256())?;
307 builder.build()
308 };
309
310 Ok(Certificate {
311 x509: cert,
312 pkey: None,
313 })
314 }
315}
316
317pub struct CsrBuilder {
319 fields: BuilderFields,
320}
321impl UseesBuilderFields for CsrBuilder {
322 fn fields_mut(&mut self) -> &mut BuilderFields {
323 &mut self.fields
324 }
325}
326impl Default for CsrBuilder {
327 fn default() -> Self {
328 Self::new()
329 }
330}
331
332impl CsrBuilder {
333 pub fn new() -> Self {
335 Self {
336 fields: BuilderFields::default(),
337 }
338 }
339
340 pub fn certificate_signing_request(self) -> Result<Csr, Box<dyn std::error::Error>> {
373 let mut name_builder = X509NameBuilder::new()?;
374 name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
375 if !self.fields.country_name.trim().is_empty() {
376 name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
377 }
378 if !self.fields.state_province.trim().is_empty() {
379 name_builder
380 .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
381 }
382 if !self.fields.locality_time.trim().is_empty() {
383 name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
384 }
385 if !self.fields.organization.trim().is_empty() {
386 name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
387 }
388 let name = name_builder.build();
389 let mut builder = X509ReqBuilder::new()?;
390 builder.set_version(0)?;
391 builder.set_subject_name(&name)?;
392 let pkey = select_key(&self.fields.key_type).unwrap();
393 builder.set_pubkey(&pkey)?;
394 let key_usage = self.fields.usage.clone().unwrap_or_default();
395
396 #[cfg(feature = "pqc")]
399 validate_pqc_key_usage(&pkey, &key_usage)?;
400
401 #[cfg(feature = "pqc")]
404 reject_mlkem_signing(
405 &pkey,
406 "ML-KEM (FIPS 203) is a key-encapsulation key and cannot sign a CSR \
407 (PKCS#10 requires a self-signature for proof-of-possession). Issue the \
408 ML-KEM certificate directly with CertBuilder::build_and_sign() using a \
409 signing CA instead.",
410 )?;
411
412 let mut extensions = Stack::new()?;
413
414 let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
415 if tracked_key_usage.is_used() {
416 extensions.push(tracked_key_usage.into_inner().build()?)?;
417 }
418 if tracked_extended_key_usage.is_used() {
419 extensions.push(tracked_extended_key_usage.into_inner().build()?)?;
420 }
421
422 let mut san = SubjectAlternativeName::new();
423 for s in &self.fields.alternative_names {
424 san.dns(s);
425 }
426 extensions.push(san.build(&builder.x509v3_context(None))?)?;
427
428 builder.add_extensions(&extensions)?;
429 let csr: X509Req = if is_digestless_key(&pkey) {
430 let builder_csr = builder.build();
431 sign_x509_req_digestless(&builder_csr, &pkey)
432 .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
433 builder_csr
434 } else {
435 builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
436 builder.build()
437 };
438 Ok(Csr {
439 csr,
440 pkey: Some(pkey),
441 })
442 }
443}
444
445fn verify_csr_proof_of_possession(csr: &X509Req) -> Result<(), Box<dyn std::error::Error>> {
452 let public_key = csr.public_key()?;
453 if !csr.verify(&public_key)? {
454 return Err(
455 "CSR proof-of-possession check failed: the request signature does \
456 not verify against its own public key"
457 .into(),
458 );
459 }
460 Ok(())
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::certificate::CertBuilder;
467 #[cfg(feature = "pqc")]
468 use crate::certificate::key::generate_pqc_key;
469 #[cfg(feature = "pqc")]
470 use crate::certificate::key::sign_x509_req_digestless;
471 use openssl::x509::X509Req;
472 #[cfg(feature = "pqc")]
473 use openssl::x509::X509ReqBuilder;
474 #[cfg(feature = "pqc")]
475 use openssl::x509::extension::KeyUsage;
476 use std::io::Write;
477 use tempfile::NamedTempFile;
478
479 #[test]
480 fn create_ca_cert_from_csr_with_path_len() {
481 let ca = CertBuilder::new()
482 .common_name("My Test Ca")
483 .is_ca(true)
484 .pathlen(2)
485 .build_and_self_sign()
486 .unwrap();
487
488 let csr = CsrBuilder::new()
489 .common_name("leaf")
490 .certificate_signing_request()
491 .unwrap();
492 let cert = csr
493 .build_signed_certificate(&ca, CsrOptions::new().pathlen(1, vec![]).is_ca(true))
494 .unwrap();
495 assert_eq!(cert.x509.pathlen(), Some(1));
496 }
497
498 #[test]
499 fn create_non_ca_cert_from_csr_with_path_len() {
500 let ca = CertBuilder::new()
501 .common_name("My Test Ca")
502 .is_ca(true)
503 .pathlen(2)
504 .build_and_self_sign()
505 .unwrap();
506
507 let csr = CsrBuilder::new()
508 .common_name("leaf")
509 .certificate_signing_request()
510 .unwrap();
511 let cert = csr
512 .build_signed_certificate(&ca, CsrOptions::new().pathlen(1, vec![]))
513 .unwrap();
514 assert_eq!(cert.x509.pathlen(), None);
515 }
516
517 #[test]
518 fn create_ca_cert_with_chain_from_csr_with_path_len() {
519 let ca = CertBuilder::new()
520 .common_name("My Test root Ca")
521 .is_ca(true)
522 .pathlen(3)
523 .build_and_self_sign()
524 .unwrap();
525 let interca1 = CertBuilder::new()
526 .common_name("My Test Ca1")
527 .is_ca(true)
528 .pathlen(2)
529 .build_and_sign_with_chain(&ca, &[])
530 .unwrap();
531 let interca2 = CertBuilder::new()
532 .common_name("My Test Ca2")
533 .is_ca(true)
534 .pathlen(1)
535 .build_and_sign_with_chain(&interca1, &[&ca])
536 .unwrap();
537 let csr = CsrBuilder::new()
538 .common_name("leaf")
539 .certificate_signing_request()
540 .unwrap();
541 let cert = csr
542 .build_signed_certificate(
543 &interca2,
544 CsrOptions::new().pathlen(0, vec![ca, interca1]).is_ca(true),
545 )
546 .unwrap();
547 assert_eq!(cert.x509.pathlen(), Some(0));
548 }
549
550 #[test]
551 fn create_ca_cert_with_chain_from_csr_with_budget_exhausted() {
552 let ca = CertBuilder::new()
553 .common_name("My Test root Ca")
554 .is_ca(true)
555 .pathlen(3)
556 .build_and_self_sign()
557 .unwrap();
558 let interca1 = CertBuilder::new()
559 .common_name("My Test Ca1")
560 .is_ca(true)
561 .pathlen(2)
562 .build_and_sign_with_chain(&ca, &[])
563 .unwrap();
564 let interca2 = CertBuilder::new()
565 .common_name("My Test Ca2")
566 .is_ca(true)
567 .pathlen(0)
568 .build_and_sign_with_chain(&interca1, &[&ca])
569 .unwrap();
570 let csr = CsrBuilder::new()
571 .common_name("leaf")
572 .certificate_signing_request()
573 .unwrap();
574 let err = csr
575 .build_signed_certificate(
576 &interca2,
577 CsrOptions::new().pathlen(0, vec![ca, interca1]).is_ca(true),
578 )
579 .err()
580 .expect("");
581 assert!(
582 err.to_string()
583 .contains("signer's path length budget is exhausted; cannot issue a CA"),
584 "signer's path length budget is exhausted; cannot issue a CA got: {err}"
585 );
586 }
587 #[test]
588 fn csr_inflated_pathlen_is_rejected() {
589 let ca = CertBuilder::new()
590 .common_name("root")
591 .is_ca(true)
592 .pathlen(1)
593 .build_and_self_sign()
594 .unwrap();
595 let csr = CsrBuilder::new()
596 .common_name("leaf")
597 .certificate_signing_request()
598 .unwrap();
599 let err = csr
600 .build_signed_certificate(
601 &ca,
602 CsrOptions::new().is_ca(true).pathlen(1, vec![]), )
604 .err()
605 .expect("inflated pathLen must be rejected");
606 assert!(
607 err.to_string()
608 .contains("requested pathLen exceeds what the signer's chain permits"),
609 "got: {err}"
610 );
611 }
612
613 #[test]
614 fn csr_incomplete_chain_is_rejected() {
615 let ca = CertBuilder::new()
616 .common_name("root")
617 .is_ca(true)
618 .pathlen(2)
619 .build_and_self_sign()
620 .unwrap();
621 let inter = CertBuilder::new()
622 .common_name("inter")
623 .is_ca(true)
624 .pathlen(1)
625 .build_and_sign_with_chain(&ca, &[])
626 .unwrap();
627 let csr = CsrBuilder::new()
628 .common_name("leaf")
629 .certificate_signing_request()
630 .unwrap();
631 let err = csr
632 .build_signed_certificate(
633 &inter,
634 CsrOptions::new().is_ca(true).pathlen(0, vec![]), )
636 .err()
637 .expect("incomplete chain must be rejected");
638 assert!(
639 err.to_string().contains("Could not find self signed root"),
640 "got: {err}"
641 );
642 }
643
644 #[test]
645 fn csr_unlimited_signer_allows_ca_issuance() {
646 let ca = CertBuilder::new()
647 .common_name("root")
648 .is_ca(true) .build_and_self_sign()
650 .unwrap();
651 let csr = CsrBuilder::new()
652 .common_name("leaf")
653 .certificate_signing_request()
654 .unwrap();
655 let cert = csr
656 .build_signed_certificate(
657 &ca,
658 CsrOptions::new().is_ca(true).pathlen(5, vec![]), )
660 .unwrap();
661 assert_eq!(cert.x509.pathlen(), Some(5));
662 }
663 #[test]
664 fn build_signed_certificate_rejects_csr_with_bad_proof_of_possession() {
665 let ca = CertBuilder::new()
668 .common_name("My Test Ca")
669 .is_ca(true)
670 .build_and_self_sign()
671 .unwrap();
672
673 let good = CsrBuilder::new()
674 .common_name("leaf")
675 .certificate_signing_request()
676 .unwrap();
677
678 let mut der = good.csr.to_der().unwrap();
682 let last = der.len() - 1;
683 der[last] ^= 0xFF;
684 let tampered = Csr {
685 csr: X509Req::from_der(&der).unwrap(),
686 pkey: None,
687 };
688
689 let err = tampered
690 .build_signed_certificate(&ca, CsrOptions::new())
691 .err()
692 .expect("CSR with broken proof-of-possession must be rejected");
693 assert!(
694 err.to_string().contains("proof-of-possession"),
695 "expected a proof-of-possession error, got: {err}"
696 );
697 }
698
699 #[cfg(feature = "pqc")]
700 #[test]
701 fn do_not_allow_csr_with_pqc_key_and_encipherment_to_generate_certificate() {
702 let ca = CertBuilder::new()
703 .common_name("My Test Ca")
704 .is_ca(true)
705 .build_and_self_sign()
706 .unwrap();
707
708 let pkey = generate_pqc_key("ML-DSA-65").unwrap();
709 let mut builder = X509ReqBuilder::new().unwrap();
710 builder
711 .set_pubkey(&pkey)
712 .expect("failed to set public key in csr");
713 let mut exts = Stack::new().unwrap();
714 exts.push(KeyUsage::new().key_encipherment().build().unwrap())
715 .unwrap();
716 builder.set_version(0).unwrap();
717 builder.add_extensions(&exts).unwrap();
718 let req = builder.build();
719
720 let _ = sign_x509_req_digestless(&req, &pkey);
721 let csr = Csr {
722 csr: req,
723 pkey: None,
724 };
725 let err = csr
726 .build_signed_certificate(&ca, CsrOptions::new())
727 .err()
728 .expect("a PQC signature key requesting keyEncipherment must be rejected");
729 assert!(
730 err.to_string().contains("keyEncipherment"),
731 "expected a keyEncipherment error, got: {err}"
732 );
733 }
734
735 #[test]
736 fn test_reading_csr_from_file() {
737 let csr_data = b"-----BEGIN CERTIFICATE REQUEST-----
738MIICzDCCAbQCAQAwXTEVMBMGA1UEAwwMZXhhbXBsZTIuY29tMQswCQYDVQQGEwJT
739RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xDzANBgNV
740BAoMBk15IG9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeAXpCG
741hbIayESfdTzOO0DxIMsOAu4kUm0zF0W/+xDUHl6bGy3wlB9S9nzBG/qwqFZ27Om3
742o4zrZ8K8DBx0ERWNuhMmr0Nx8QpAWBEyxOc08Gn4c3XVBBkRZSn4AIqr9DGtcUqW
743tQZXvMGF6sRRljiEvOxO6zMzZKTGYwzIeQvH85cQ3uXsw0Kknsw/fcuywaAC8SS9
744aqs4jiEIgzdhxdH2OVXBNGj4cjVhK309JiWFHS9XJLNV/PKC+F1nkaANQwbW5A4F
7459vya4js9gk8f4SfF1u+qOJEvsDvAb+1xdjXPRzf77eGh3rC4KgGWQ6WrWfW8PItF
746BDg/jskq3bJXNL8CAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIM
747ZXhhbXBsZTIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAHeeSW8C6SMVhWiMvPn7iz
748FUHQedHRyPz6kTEfC01eNIbs0r4YghOAcm8PF67jncIXVrqgxo1uzq12qlV+0YYb
749jps31IbQNOz0eFLYvij15ielmOYeQZZ/2vqaGi3geVobLc6Ki5tadnA/NhjTN33j
750QcqDDic8riAOTbSQ6TH9KPTGJQOPk+taMpDGDHskIW0oME5iT2ewbhBHg6v/kSzy
751tss2kBY5O7vo2COtbNcwX5Xp9S2LH9kVUKr0GIjuQjwbv5xl+GNdDey09W9EDACU
752jcGV3++2wS4LN4h3CG4pWZ+LTXhm8ymhoWOapN95lfe3xLRAKFJwiLkGwS75++FW
753-----END CERTIFICATE REQUEST-----";
754 let mut csr_file = NamedTempFile::new().expect("Failed to create temp csr file");
755 csr_file.write_all(csr_data).expect("Failed to write csr");
756 let result = Csr::load_csr(csr_file.path());
757 assert!(result.is_ok(), "Failed to load csr: {:?}", result.err());
758 }
759}