1use rcgen::{CertificateSigningRequestParams, DnType, DnValue, SanType};
16use rustls_pki_types::CertificateSigningRequestDer;
17use time::OffsetDateTime;
18use time::format_description::well_known::Rfc3339;
19use tracing::warn;
20
21use acme_proxy_core::error::Problem;
22use acme_proxy_core::identifier::Identifier;
23
24pub(crate) fn parse_csr(csr_der: &[u8]) -> Result<CertificateSigningRequestParams, Problem> {
29 let der = CertificateSigningRequestDer::from(csr_der.to_vec());
30 CertificateSigningRequestParams::from_der(&der).map_err(|error| {
31 warn!(event = "csr_parse_failed", outcome = "failure", error = %error);
32 Problem::bad_csr("CSR is unparsable")
33 })
34}
35
36pub(crate) fn check_csr_matches_order(
55 csr: &CertificateSigningRequestParams,
56 csr_der: &[u8],
57 identifiers: &[Identifier],
58) -> Result<(), Problem> {
59 if let Some(other) = csr
63 .params
64 .subject_alt_names
65 .iter()
66 .find(|san| !matches!(san, SanType::DnsName(_)))
67 {
68 warn!(event = "csr_non_dns_san", outcome = "failure", san = ?other);
69 return Err(Problem::bad_csr(
70 "CSR carries a subject alternative name that is not a DNS name",
71 ));
72 }
73
74 let csr_dns: std::collections::BTreeSet<&str> = csr
75 .params
76 .subject_alt_names
77 .iter()
78 .filter_map(|san| match san {
79 SanType::DnsName(name) => Some(name.as_str()),
80 _ => None,
81 })
82 .collect();
83 let want_dns: std::collections::BTreeSet<&str> = identifiers
84 .iter()
85 .filter(|id| id.typ == "dns")
86 .map(|id| id.value.as_str())
87 .collect();
88
89 if csr_dns != want_dns {
90 warn!(event = "csr_identifier_mismatch", outcome = "failure", csr = ?csr_dns, order = ?want_dns);
91 return Err(Problem::bad_csr(
92 "CSR does not request the order's identifiers",
93 ));
94 }
95
96 let single_label_order = want_dns.iter().any(|name| !name.contains('.'));
124 for common_name in subject_common_names(csr_der)? {
125 let candidate = normalize_dns_name(&common_name);
126 let asserted = looks_like_dns_name(&candidate)
127 || (single_label_order
128 && well_formed_name(&candidate)
129 && !candidate.chars().any(|c| c.is_ascii_whitespace()));
130 if asserted && !want_dns.contains(candidate.as_str()) {
131 warn!(event = "csr_common_name_mismatch", outcome = "failure", common_name = %candidate);
132 return Err(Problem::bad_csr(
133 "CSR common name is a domain the order does not cover",
134 ));
135 }
136 }
137
138 Ok(())
139}
140
141fn subject_common_names(csr_der: &[u8]) -> Result<Vec<String>, Problem> {
149 use x509_parser::prelude::FromDer;
150
151 let (_, request) = x509_parser::certification_request::X509CertificationRequest::from_der(
152 csr_der,
153 )
154 .map_err(|error| {
155 warn!(event = "csr_parse_failed", outcome = "failure", error = %error);
156 Problem::bad_csr("CSR is unparsable")
157 })?;
158
159 request
160 .certification_request_info
161 .subject
162 .iter_common_name()
163 .map(|attribute| {
164 attribute.as_str().map(str::to_string).map_err(|_| {
165 warn!(event = "csr_common_name_unreadable", outcome = "failure");
166 Problem::bad_csr("CSR common name is not a readable string")
167 })
168 })
169 .collect()
170}
171
172fn looks_like_dns_name(value: &str) -> bool {
176 !value.is_empty()
177 && value.contains('.')
178 && !value.chars().any(|c| c.is_ascii_whitespace())
179 && well_formed_name(value)
180}
181
182pub(crate) fn csr_identifiers(csr: &CertificateSigningRequestParams) -> Vec<Identifier> {
185 let mut identifiers: Vec<Identifier> = csr
186 .params
187 .subject_alt_names
188 .iter()
189 .map(|san| match san {
190 SanType::DnsName(name) => Identifier::dns(normalize_dns_name(name.as_str())),
191 SanType::IpAddress(ip) => Identifier::new("ip", ip.to_canonical().to_string()),
192 SanType::Rfc822Name(name) => Identifier::new("email", name.as_str().to_string()),
193 SanType::URI(uri) => Identifier::new("uri", uri.as_str().to_string()),
194 other => Identifier::new("other", format!("{other:?}")),
195 })
196 .collect();
197
198 if let Some(common_name) = csr.params.distinguished_name.get(&DnType::CommonName) {
199 identifiers.push(match dn_text(common_name) {
200 Some(value) => Identifier::new("cn", normalize_dns_name(&value)),
201 None => Identifier::new("other", format!("{common_name:?}")),
202 });
203 }
204
205 identifiers
206}
207
208#[must_use]
211pub fn normalize_dns_name(value: &str) -> String {
212 let trimmed = value.strip_suffix('.').unwrap_or(value);
213 trimmed.to_ascii_lowercase()
214}
215
216#[must_use]
218pub fn is_wildcard(value: &str) -> bool {
219 value.starts_with("*.")
220}
221
222const MAX_DNS_NAME: usize = 253;
225
226const MAX_DNS_LABEL: usize = 63;
228
229#[must_use]
265pub fn well_formed_name(value: &str) -> bool {
266 let name = match value.strip_prefix("*.") {
267 Some(rest) => rest,
268 None => value,
269 };
270 !name.contains('*') && is_dns_name(name)
272}
273
274fn is_dns_name(name: &str) -> bool {
276 if name.is_empty() || name.len() > MAX_DNS_NAME {
277 return false;
278 }
279 let name = name.strip_suffix('.').unwrap_or(name);
282 if name.is_empty() {
283 return false;
284 }
285 name.split('.').all(is_dns_label)
286}
287
288#[must_use]
303pub fn names_an_ip_address(value: &str) -> bool {
304 let name = value.strip_prefix("*.").unwrap_or(value);
305 matches!(
308 url::Host::parse(name),
309 Ok(url::Host::Ipv4(_) | url::Host::Ipv6(_))
310 )
311}
312
313fn is_dns_label(label: &str) -> bool {
315 !label.is_empty()
316 && label.len() <= MAX_DNS_LABEL
317 && !label.starts_with('-')
318 && !label.ends_with('-')
319 && label
320 .bytes()
321 .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
322}
323
324pub(crate) fn validate_contacts(contacts: &[String]) -> Result<(), Problem> {
344 match contact_shape_error(contacts) {
345 None => Ok(()),
346 Some(rejection) if rejection.unsupported => {
347 Err(Problem::unsupported_contact(rejection.detail))
348 }
349 Some(rejection) => Err(Problem::invalid_contact(rejection.detail)),
350 }
351}
352
353pub struct ContactRejection {
355 pub unsupported: bool,
359 pub detail: String,
360}
361
362pub fn contact_shape_error(contacts: &[String]) -> Option<ContactRejection> {
371 const MAX_CONTACTS: usize = 32;
379
380 fn unsupported(detail: String) -> Option<ContactRejection> {
381 Some(ContactRejection {
382 unsupported: true,
383 detail,
384 })
385 }
386 fn invalid(detail: String) -> Option<ContactRejection> {
387 Some(ContactRejection {
388 unsupported: false,
389 detail,
390 })
391 }
392
393 if contacts.len() > MAX_CONTACTS {
394 warn!(
395 event = "contact_list_too_long",
396 outcome = "failure",
397 contacts_count = contacts.len()
398 );
399 return invalid(format!(
400 "An account may carry at most {MAX_CONTACTS} contacts; this one carries {}",
401 contacts.len()
402 ));
403 }
404
405 for contact in contacts {
406 let Some(rest) = contact.strip_prefix("mailto:") else {
407 let scheme = contact.split_once(':').map_or("(none)", |(s, _)| s);
408 warn!(event = "contact_scheme_unsupported", outcome = "failure", scheme = %scheme);
409 return unsupported(format!(
410 "Contact {contact} uses an unsupported scheme; only mailto: is supported"
411 ));
412 };
413
414 if rest.chars().any(|c| c.is_control()) {
420 warn!(
421 event = "contact_has_control_characters",
422 outcome = "failure"
423 );
424 return invalid(format!(
425 "Contact {contact:?} carries a control character, which is not part of an address"
426 ));
427 }
428
429 if rest.contains('?') {
431 warn!(event = "contact_has_hfields", outcome = "failure");
432 return invalid(format!(
433 "Contact {contact} carries hfields, which RFC 8555 §7.3 forbids"
434 ));
435 }
436
437 if rest.contains(',') {
440 warn!(
441 event = "contact_has_multiple_addresses",
442 outcome = "failure"
443 );
444 return invalid(format!(
445 "Contact {contact} names more than one address; RFC 8555 §7.3 allows one"
446 ));
447 }
448
449 let Some((local, domain)) = rest.rsplit_once('@') else {
452 warn!(event = "contact_not_an_address", outcome = "failure");
453 return invalid(format!("Contact {contact} is not an email address"));
454 };
455 if local.is_empty() || domain.is_empty() || !domain.contains('.') {
456 warn!(event = "contact_address_incomplete", outcome = "failure");
457 return invalid(format!("Contact {contact} is not a complete email address"));
458 }
459 }
460
461 None
462}
463
464pub(crate) fn dn_text(value: &DnValue) -> Option<String> {
466 match value {
467 DnValue::Utf8String(text) => Some(text.clone()),
468 DnValue::Ia5String(text) => Some(text.as_str().to_string()),
469 DnValue::PrintableString(text) => Some(text.as_str().to_string()),
470 DnValue::TeletexString(text) => Some(text.as_str().to_string()),
471 _ => None,
472 }
473}
474
475pub(crate) fn parse_rfc3339(field: &str, value: &str) -> Result<i64, Problem> {
477 OffsetDateTime::parse(value, &Rfc3339)
478 .map(time::OffsetDateTime::unix_timestamp)
479 .map_err(|_| {
480 warn!(event = "order_datetime_invalid", outcome = "failure", field = %field, value = %value);
481 Problem::malformed("Invalid notBefore/notAfter datetime")
482 })
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn wildcard_shapes_are_recognised_and_the_rest_refused() {
491 assert!(is_wildcard("*.example.com"));
492 assert!(well_formed_name("*.example.com"));
493
494 assert!(!is_wildcard("example.com"));
495 assert!(well_formed_name("example.com"));
496
497 for bad in [
498 "*example.com",
499 "*.*.example.com",
500 "a.*.example.com",
501 "*",
502 "*.",
503 ] {
504 assert!(!well_formed_name(bad), "{bad} must not be well formed");
505 }
506 assert!(!is_wildcard("*example.com"));
507 }
508
509 #[test]
516 fn a_dns_identifier_that_is_not_a_dns_name_is_refused() {
517 for good in [
518 "example.com",
519 "a.example.com",
520 "EXAMPLE.com",
521 "host-1.example.com",
522 "_acme.example.com",
525 "single-label",
526 "1.2.3.4",
528 "*.sub.example.com",
529 ] {
530 assert!(well_formed_name(good), "{good} must be well formed");
531 }
532
533 for bad in [
534 "a.example.com,b.example.com",
536 "internal.corp/",
537 "user@internal.corp",
538 "example.com#frag",
539 "example.com?q=1",
540 "example .com",
541 "example.com:8080",
542 "example.com\n",
544 "example.com\r\nX",
545 "example\t.com",
546 "",
548 ".",
549 "..",
550 "a..b",
551 ".example.com",
552 "-example.com",
553 "example-.com",
554 "a.-b.com",
555 ] {
556 assert!(!well_formed_name(bad), "{bad:?} must not be well formed");
557 }
558 }
559
560 #[test]
563 fn an_address_spelled_as_a_dns_name_is_recognised() {
564 for address in [
565 "10.0.0.5",
566 "127.0.0.1",
567 "169.254.169.254",
568 "2130706433",
569 "0x7f.1",
570 "127.1",
571 "0177.0.0.1",
572 "1.2.3.4.",
573 "*.10.0.0.5",
574 ] {
575 assert!(names_an_ip_address(address), "{address} is an address");
576 }
577 for name in [
578 "example.com",
579 "1.2.3.4.example.com",
580 "10-0-0-5.internal",
581 "single-label",
582 "_acme.example.com",
583 "*.example.com",
584 "0x7f.example",
585 ] {
586 assert!(!names_an_ip_address(name), "{name} is a name");
587 }
588 }
589
590 #[test]
591 fn a_dns_identifier_longer_than_the_protocol_allows_is_refused() {
592 let label = "a".repeat(MAX_DNS_LABEL);
593 assert!(well_formed_name(&format!("{label}.example.com")));
594
595 let too_long_label = "a".repeat(MAX_DNS_LABEL + 1);
596 assert!(!well_formed_name(&format!("{too_long_label}.example.com")));
597
598 let name = std::iter::repeat_n(label.as_str(), 4)
600 .collect::<Vec<_>>()
601 .join(".");
602 assert_eq!(name.len(), 255);
603 assert!(!well_formed_name(&name));
604
605 let fits = format!("{}.com", &name[..MAX_DNS_NAME - 4]);
606 assert_eq!(fits.len(), MAX_DNS_NAME);
607 assert!(well_formed_name(&fits));
608 }
609
610 #[test]
613 fn a_trailing_root_label_is_accepted_but_a_bare_dot_is_not() {
614 assert!(well_formed_name("example.com."));
615 assert!(!well_formed_name("example.com.."));
616 assert!(!well_formed_name("."));
617 }
618
619 #[test]
622 fn a_contact_carrying_a_control_character_is_refused() {
623 for bad in [
624 "mailto:alice@example.com\nBcc: attacker@evil.test",
625 "mailto:alice@example.com\r\n",
626 "mailto:al\tice@example.com",
627 "mailto:alice@example.com\u{0}",
628 ] {
629 let rejection = contact_shape_error(&[bad.to_string()])
630 .unwrap_or_else(|| panic!("{bad:?} must be refused"));
631 assert!(!rejection.unsupported, "{bad:?}");
634 }
635
636 assert!(contact_shape_error(&["mailto:alice@example.com".to_string()]).is_none());
637 }
638
639 fn csr_with(sans: Vec<SanType>, common_name: Option<&str>) -> Vec<u8> {
640 let key_pair = rcgen::KeyPair::generate().unwrap();
641 let mut params = rcgen::CertificateParams::default();
642 params.subject_alt_names = sans;
643 params.distinguished_name = rcgen::DistinguishedName::new();
644 if let Some(name) = common_name {
645 params.distinguished_name.push(DnType::CommonName, name);
646 }
647 params.serialize_request(&key_pair).unwrap().der().to_vec()
648 }
649
650 fn find<'a>(identifiers: &'a [Identifier], typ: &str) -> Vec<&'a str> {
651 identifiers
652 .iter()
653 .filter(|id| id.typ == typ)
654 .map(|id| id.value.as_str())
655 .collect()
656 }
657
658 #[test]
659 fn csr_identifiers_projects_every_san_type() {
660 let der = csr_with(
661 vec![
662 SanType::DnsName("host.example.com".try_into().unwrap()),
663 SanType::IpAddress("10.0.0.1".parse().unwrap()),
664 SanType::Rfc822Name("someone@example.com".try_into().unwrap()),
665 SanType::URI("https://example.com/x".try_into().unwrap()),
666 ],
667 None,
668 );
669
670 let identifiers = csr_identifiers(&parse_csr(&der).unwrap());
671 assert_eq!(find(&identifiers, "dns"), vec!["host.example.com"]);
672 assert_eq!(find(&identifiers, "ip"), vec!["10.0.0.1"]);
673 assert_eq!(find(&identifiers, "email"), vec!["someone@example.com"]);
674 assert_eq!(find(&identifiers, "uri"), vec!["https://example.com/x"]);
675 }
676
677 #[test]
678 fn csr_identifiers_renders_ipv6_addresses() {
679 let der = csr_with(
680 vec![SanType::IpAddress("2001:db8::1".parse().unwrap())],
681 None,
682 );
683 assert_eq!(
684 find(&csr_identifiers(&parse_csr(&der).unwrap()), "ip"),
685 vec!["2001:db8::1"]
686 );
687 }
688
689 #[test]
690 fn csr_identifiers_includes_the_common_name() {
691 let der = csr_with(
692 vec![SanType::DnsName("ok.example.com".try_into().unwrap())],
693 Some("secret.internal.example.com"),
694 );
695
696 let identifiers = csr_identifiers(&parse_csr(&der).unwrap());
697 assert_eq!(find(&identifiers, "dns"), vec!["ok.example.com"]);
698 assert_eq!(
699 find(&identifiers, "cn"),
700 vec!["secret.internal.example.com"]
701 );
702 }
703
704 #[test]
705 fn csr_identifiers_omits_an_absent_common_name() {
706 let der = csr_with(
707 vec![SanType::DnsName("ok.example.com".try_into().unwrap())],
708 None,
709 );
710 assert!(find(&csr_identifiers(&parse_csr(&der).unwrap()), "cn").is_empty());
711 }
712
713 #[test]
714 fn parse_csr_rejects_garbage() {
715 assert!(parse_csr(&[0xde, 0xad, 0xbe, 0xef]).is_err());
716 }
717
718 use acme_proxy_store::testutil::dns_identifiers as dns;
719
720 #[test]
721 fn a_csr_matching_the_order_exactly_is_accepted() {
722 let der = csr_with(
723 vec![
724 SanType::DnsName("a.example.com".try_into().unwrap()),
725 SanType::DnsName("b.example.com".try_into().unwrap()),
726 ],
727 None,
728 );
729 let identifiers = dns(&["b.example.com", "a.example.com"]);
732 assert!(check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &identifiers).is_ok());
733 }
734
735 #[test]
736 fn a_csr_naming_another_domain_is_refused() {
737 let der = csr_with(
738 vec![SanType::DnsName("victim.example".try_into().unwrap())],
739 None,
740 );
741 let value =
742 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
743 .unwrap_err()
744 .to_value();
745 assert_eq!(value["type"], "urn:ietf:params:acme:error:badCSR");
746 assert_eq!(value["status"], 400);
747 }
748
749 #[test]
750 fn a_csr_naming_more_than_the_order_is_refused() {
751 let der = csr_with(
754 vec![
755 SanType::DnsName("a.example.com".try_into().unwrap()),
756 SanType::DnsName("extra.example.com".try_into().unwrap()),
757 ],
758 None,
759 );
760 assert!(
761 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
762 .is_err()
763 );
764 }
765
766 #[test]
767 fn a_csr_naming_less_than_the_order_is_refused() {
768 let der = csr_with(
769 vec![SanType::DnsName("a.example.com".try_into().unwrap())],
770 None,
771 );
772 assert!(
773 check_csr_matches_order(
774 &parse_csr(&der).unwrap(),
775 &der,
776 &dns(&["a.example.com", "b.example.com"]),
777 )
778 .is_err()
779 );
780 }
781
782 #[test]
783 fn a_csr_smuggling_a_non_dns_san_is_refused() {
784 let der = csr_with(
787 vec![
788 SanType::DnsName("a.example.com".try_into().unwrap()),
789 SanType::IpAddress("10.0.0.1".parse().unwrap()),
790 ],
791 None,
792 );
793 assert!(
794 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
795 .is_err()
796 );
797 }
798
799 #[test]
800 fn a_csr_whose_common_name_is_not_an_order_identifier_is_refused() {
801 let der = csr_with(
802 vec![SanType::DnsName("a.example.com".try_into().unwrap())],
803 Some("victim.example"),
804 );
805 assert!(
806 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
807 .is_err()
808 );
809 }
810
811 #[test]
812 fn a_csr_whose_common_name_is_an_order_identifier_is_accepted() {
813 let der = csr_with(
814 vec![SanType::DnsName("a.example.com".try_into().unwrap())],
815 Some("a.example.com"),
816 );
817 assert!(
818 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
819 .is_ok()
820 );
821 }
822
823 #[test]
824 fn a_common_name_that_is_a_human_label_is_left_alone() {
825 for label in ["rcgen self signed cert", "ACME client", "no-dot-label"] {
829 let der = csr_with(
830 vec![SanType::DnsName("a.example.com".try_into().unwrap())],
831 Some(label),
832 );
833 assert!(
834 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
835 .is_ok(),
836 "{label} should not be read as a host name"
837 );
838 }
839 }
840
841 #[test]
845 fn a_common_name_in_an_unreadable_encoding_is_refused() {
846 let key_pair = rcgen::KeyPair::generate().unwrap();
847 let mut params = rcgen::CertificateParams::default();
848 params.subject_alt_names = vec![SanType::DnsName("a.example.com".try_into().unwrap())];
849 params.distinguished_name = rcgen::DistinguishedName::new();
850 params.distinguished_name.push(
851 DnType::CommonName,
852 DnValue::BmpString("victim.example".try_into().unwrap()),
853 );
854 let der = params.serialize_request(&key_pair).unwrap().der().to_vec();
855
856 let value =
857 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
858 .unwrap_err()
859 .to_value();
860 assert_eq!(value["type"], "urn:ietf:params:acme:error:badCSR");
861 }
862
863 #[test]
868 fn every_common_name_of_the_subject_is_read() {
869 use rcgen::PublicKeyData;
870
871 fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {
872 let mut out = vec![tag];
873 match body.len() {
874 len if len < 0x80 => out.push(len as u8),
875 len if len < 0x100 => out.extend([0x81, len as u8]),
876 len => out.extend([0x82, (len >> 8) as u8, (len & 0xff) as u8]),
877 }
878 out.extend(body);
879 out
880 }
881 fn common_name(value: &str) -> Vec<u8> {
882 let oid = [0x06, 0x03, 0x55, 0x04, 0x03];
884 let mut attribute = oid.to_vec();
885 attribute.extend(tlv(0x0c, value.as_bytes()));
886 tlv(0x31, &tlv(0x30, &attribute))
887 }
888
889 let key_pair = rcgen::KeyPair::generate().unwrap();
890 let mut subject = common_name("a.example.com");
891 subject.extend(common_name("victim.example"));
892 let mut info = tlv(0x02, &[0x00]);
893 info.extend(tlv(0x30, &subject));
894 info.extend(key_pair.subject_public_key_info());
895 info.extend(tlv(0xa0, &[]));
896 let mut request = tlv(0x30, &info);
897 request.extend(tlv(
899 0x30,
900 &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02],
901 ));
902 request.extend(tlv(0x03, &[0x00]));
903 let der = tlv(0x30, &request);
904
905 assert_eq!(
906 subject_common_names(&der).unwrap(),
907 vec!["a.example.com".to_string(), "victim.example".to_string()]
908 );
909 }
910
911 #[test]
915 fn a_single_label_common_name_is_checked_against_a_single_label_order() {
916 let der = csr_with(
917 vec![SanType::DnsName("fileserver".try_into().unwrap())],
918 Some("mailserver"),
919 );
920 assert!(
921 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["fileserver"]))
922 .is_err()
923 );
924
925 let der = csr_with(
926 vec![SanType::DnsName("fileserver".try_into().unwrap())],
927 Some("fileserver"),
928 );
929 assert!(
930 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["fileserver"])).is_ok()
931 );
932 }
933
934 #[test]
935 fn common_names_are_recognised_as_host_names_or_not() {
936 assert!(looks_like_dns_name("a.example.com"));
937 assert!(looks_like_dns_name("*.example.com"));
938
939 assert!(!looks_like_dns_name(""));
940 assert!(!looks_like_dns_name("localhost"));
941 assert!(!looks_like_dns_name("rcgen self signed cert"));
942 assert!(!looks_like_dns_name("a.*.example.com"));
943 }
944
945 #[test]
946 fn a_wildcard_csr_matching_its_order_is_accepted() {
947 let der = csr_with(
951 vec![SanType::DnsName("*.example.com".try_into().unwrap())],
952 None,
953 );
954 assert!(
955 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["*.example.com"]))
956 .is_ok()
957 );
958 }
959
960 #[test]
961 fn a_csr_differing_only_in_case_is_refused() {
962 let der = csr_with(
965 vec![SanType::DnsName("A.Example.COM".try_into().unwrap())],
966 None,
967 );
968 assert!(
969 check_csr_matches_order(&parse_csr(&der).unwrap(), &der, &dns(&["a.example.com"]))
970 .is_err()
971 );
972 }
973
974 #[test]
975 fn dn_text_reads_the_string_encodings() {
976 assert_eq!(
977 dn_text(&DnValue::Utf8String("a.example.com".to_string())).as_deref(),
978 Some("a.example.com")
979 );
980 assert_eq!(
981 dn_text(&DnValue::Ia5String("b.example.com".try_into().unwrap())).as_deref(),
982 Some("b.example.com")
983 );
984 assert_eq!(
985 dn_text(&DnValue::PrintableString(
986 "c.example.com".try_into().unwrap()
987 ))
988 .as_deref(),
989 Some("c.example.com")
990 );
991 assert_eq!(
992 dn_text(&DnValue::TeletexString("d.example.com".try_into().unwrap())).as_deref(),
993 Some("d.example.com")
994 );
995 }
996
997 #[test]
998 fn an_unreadable_common_name_becomes_an_other_identifier() {
999 let value = DnValue::BmpString("e.example.com".try_into().unwrap());
1000 assert!(dn_text(&value).is_none());
1001 }
1002}