we (web engine): Experimental web browser project to understand the limits of Claude
1//! X.509 certificate parsing and chain validation (RFC 5280).
2//!
3//! Parses PEM and DER-encoded X.509v3 certificates, extracts subject/issuer
4//! distinguished names, validity periods, extensions, and public keys.
5//! Supports certificate chain validation with RSA and ECDSA signature
6//! verification against an embedded root CA store.
7
8use crate::asn1::{
9 self, Asn1Error, DerParser, Tlv, OID_AUTHORITY_KEY_IDENTIFIER, OID_BASIC_CONSTRAINTS,
10 OID_COMMON_NAME, OID_COUNTRY_NAME, OID_ECDSA_WITH_SHA256, OID_ECDSA_WITH_SHA384, OID_KEY_USAGE,
11 OID_ORGANIZATIONAL_UNIT_NAME, OID_ORGANIZATION_NAME, OID_SHA256_WITH_RSA, OID_SHA384_WITH_RSA,
12 OID_SHA512_WITH_RSA, OID_SUBJECT_ALT_NAME, OID_SUBJECT_KEY_IDENTIFIER, TAG_BOOLEAN,
13 TAG_CLASS_CONTEXT, TAG_CONSTRUCTED, TAG_GENERALIZED_TIME, TAG_INTEGER, TAG_SEQUENCE, TAG_SET,
14 TAG_UTC_TIME,
15};
16use crate::ecdsa::{EcdsaPublicKey, EcdsaSignature};
17use crate::rsa::{HashAlgorithm, RsaPublicKey};
18use crate::sha2::{sha256, sha384};
19
20use core::fmt;
21
22// ---------------------------------------------------------------------------
23// Error type
24// ---------------------------------------------------------------------------
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum X509Error {
28 /// ASN.1 parsing error.
29 Asn1(Asn1Error),
30 /// Invalid PEM encoding.
31 InvalidPem,
32 /// Invalid Base64 encoding.
33 InvalidBase64,
34 /// Invalid certificate structure.
35 InvalidCertificate,
36 /// Unsupported certificate version.
37 UnsupportedVersion,
38 /// Unsupported signature algorithm.
39 UnsupportedAlgorithm,
40 /// Certificate signature verification failed.
41 SignatureVerificationFailed,
42 /// Certificate has expired or is not yet valid.
43 CertificateExpired,
44 /// Certificate is not yet valid.
45 CertificateNotYetValid,
46 /// Certificate chain is incomplete or invalid.
47 InvalidChain,
48 /// Basic Constraints violation (non-CA cert used as issuer).
49 NotCaCertificate,
50 /// Issuer/subject mismatch in chain.
51 IssuerMismatch,
52 /// No trusted root found for the chain.
53 UntrustedRoot,
54 /// RSA verification error.
55 Rsa(String),
56 /// ECDSA verification error.
57 Ecdsa(String),
58}
59
60impl fmt::Display for X509Error {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 Self::Asn1(e) => write!(f, "ASN.1 error: {e}"),
64 Self::InvalidPem => write!(f, "invalid PEM encoding"),
65 Self::InvalidBase64 => write!(f, "invalid Base64 encoding"),
66 Self::InvalidCertificate => write!(f, "invalid certificate structure"),
67 Self::UnsupportedVersion => write!(f, "unsupported certificate version"),
68 Self::UnsupportedAlgorithm => write!(f, "unsupported signature algorithm"),
69 Self::SignatureVerificationFailed => write!(f, "signature verification failed"),
70 Self::CertificateExpired => write!(f, "certificate has expired"),
71 Self::CertificateNotYetValid => write!(f, "certificate is not yet valid"),
72 Self::InvalidChain => write!(f, "invalid certificate chain"),
73 Self::NotCaCertificate => write!(f, "certificate is not a CA"),
74 Self::IssuerMismatch => write!(f, "issuer/subject mismatch in chain"),
75 Self::UntrustedRoot => write!(f, "no trusted root found"),
76 Self::Rsa(e) => write!(f, "RSA error: {e}"),
77 Self::Ecdsa(e) => write!(f, "ECDSA error: {e}"),
78 }
79 }
80}
81
82impl From<Asn1Error> for X509Error {
83 fn from(e: Asn1Error) -> Self {
84 Self::Asn1(e)
85 }
86}
87
88pub type Result<T> = core::result::Result<T, X509Error>;
89
90// ---------------------------------------------------------------------------
91// DateTime — simple comparable time representation
92// ---------------------------------------------------------------------------
93
94/// A simple date-time representation for certificate validity checking.
95/// Supports comparison without system clock access.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct DateTime {
98 pub year: u16,
99 pub month: u8,
100 pub day: u8,
101 pub hour: u8,
102 pub minute: u8,
103 pub second: u8,
104}
105
106impl DateTime {
107 /// Create a new DateTime.
108 pub fn new(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Self {
109 Self {
110 year,
111 month,
112 day,
113 hour,
114 minute,
115 second,
116 }
117 }
118
119 /// Convert to a comparable tuple for ordering.
120 fn as_tuple(&self) -> (u16, u8, u8, u8, u8, u8) {
121 (
122 self.year,
123 self.month,
124 self.day,
125 self.hour,
126 self.minute,
127 self.second,
128 )
129 }
130}
131
132impl PartialOrd for DateTime {
133 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
134 Some(self.cmp(other))
135 }
136}
137
138impl Ord for DateTime {
139 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
140 self.as_tuple().cmp(&other.as_tuple())
141 }
142}
143
144impl fmt::Display for DateTime {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 write!(
147 f,
148 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
149 self.year, self.month, self.day, self.hour, self.minute, self.second
150 )
151 }
152}
153
154/// Parse a UTCTime string (YYMMDDHHMMSSZ) into a DateTime.
155fn parse_utc_time(s: &str) -> Result<DateTime> {
156 if s.len() != 13 || !s.ends_with('Z') {
157 return Err(X509Error::InvalidCertificate);
158 }
159 let bytes = s.as_bytes();
160 let yy = parse_decimal_2(&bytes[0..2])?;
161 let month = parse_decimal_2(&bytes[2..4])?;
162 let day = parse_decimal_2(&bytes[4..6])?;
163 let hour = parse_decimal_2(&bytes[6..8])?;
164 let minute = parse_decimal_2(&bytes[8..10])?;
165 let second = parse_decimal_2(&bytes[10..12])?;
166
167 // RFC 5280: YY >= 50 means 19YY, YY < 50 means 20YY
168 let year = if yy >= 50 {
169 1900 + yy as u16
170 } else {
171 2000 + yy as u16
172 };
173
174 Ok(DateTime::new(
175 year,
176 month as u8,
177 day as u8,
178 hour as u8,
179 minute as u8,
180 second as u8,
181 ))
182}
183
184/// Parse a GeneralizedTime string (YYYYMMDDHHMMSSZ) into a DateTime.
185fn parse_generalized_time(s: &str) -> Result<DateTime> {
186 if s.len() != 15 || !s.ends_with('Z') {
187 return Err(X509Error::InvalidCertificate);
188 }
189 let bytes = s.as_bytes();
190 let year = parse_decimal_4(&bytes[0..4])?;
191 let month = parse_decimal_2(&bytes[4..6])?;
192 let day = parse_decimal_2(&bytes[6..8])?;
193 let hour = parse_decimal_2(&bytes[8..10])?;
194 let minute = parse_decimal_2(&bytes[10..12])?;
195 let second = parse_decimal_2(&bytes[12..14])?;
196
197 Ok(DateTime::new(
198 year,
199 month as u8,
200 day as u8,
201 hour as u8,
202 minute as u8,
203 second as u8,
204 ))
205}
206
207fn parse_decimal_2(bytes: &[u8]) -> Result<u32> {
208 if bytes.len() != 2 {
209 return Err(X509Error::InvalidCertificate);
210 }
211 let d1 = digit(bytes[0])?;
212 let d2 = digit(bytes[1])?;
213 Ok(d1 * 10 + d2)
214}
215
216fn parse_decimal_4(bytes: &[u8]) -> Result<u16> {
217 if bytes.len() != 4 {
218 return Err(X509Error::InvalidCertificate);
219 }
220 let d1 = digit(bytes[0])? as u16;
221 let d2 = digit(bytes[1])? as u16;
222 let d3 = digit(bytes[2])? as u16;
223 let d4 = digit(bytes[3])? as u16;
224 Ok(d1 * 1000 + d2 * 100 + d3 * 10 + d4)
225}
226
227fn digit(b: u8) -> Result<u32> {
228 if b.is_ascii_digit() {
229 Ok((b - b'0') as u32)
230 } else {
231 Err(X509Error::InvalidCertificate)
232 }
233}
234
235// ---------------------------------------------------------------------------
236// Base64 decoder
237// ---------------------------------------------------------------------------
238
239fn base64_decode(input: &str) -> Result<Vec<u8>> {
240 const DECODE_TABLE: [u8; 256] = {
241 let mut table = [0xFFu8; 256];
242 let mut i = 0u8;
243 // A-Z
244 while i < 26 {
245 table[(b'A' + i) as usize] = i;
246 i += 1;
247 }
248 // a-z
249 i = 0;
250 while i < 26 {
251 table[(b'a' + i) as usize] = 26 + i;
252 i += 1;
253 }
254 // 0-9
255 i = 0;
256 while i < 10 {
257 table[(b'0' + i) as usize] = 52 + i;
258 i += 1;
259 }
260 table[b'+' as usize] = 62;
261 table[b'/' as usize] = 63;
262 table[b'=' as usize] = 0xFE; // padding marker
263 table
264 };
265
266 // Strip whitespace and collect valid base64 characters.
267 let clean: Vec<u8> = input
268 .bytes()
269 .filter(|&b| !b.is_ascii_whitespace())
270 .collect();
271
272 if clean.is_empty() {
273 return Ok(Vec::new());
274 }
275
276 // Must be a multiple of 4.
277 if !clean.len().is_multiple_of(4) {
278 return Err(X509Error::InvalidBase64);
279 }
280
281 let mut out = Vec::with_capacity(clean.len() * 3 / 4);
282 let chunks = clean.chunks_exact(4);
283
284 for chunk in chunks {
285 let a = DECODE_TABLE[chunk[0] as usize];
286 let b = DECODE_TABLE[chunk[1] as usize];
287 let c = DECODE_TABLE[chunk[2] as usize];
288 let d = DECODE_TABLE[chunk[3] as usize];
289
290 // Check for invalid characters (0xFF).
291 if a == 0xFF || b == 0xFF || c == 0xFF || d == 0xFF {
292 return Err(X509Error::InvalidBase64);
293 }
294
295 // Mask out padding markers for decoding.
296 let a_val = if a == 0xFE { 0 } else { a };
297 let b_val = if b == 0xFE { 0 } else { b };
298 let c_val = if c == 0xFE { 0 } else { c };
299 let d_val = if d == 0xFE { 0 } else { d };
300
301 // First two characters must not be padding.
302 if a == 0xFE || b == 0xFE {
303 return Err(X509Error::InvalidBase64);
304 }
305
306 let triple = ((a_val as u32) << 18)
307 | ((b_val as u32) << 12)
308 | ((c_val as u32) << 6)
309 | (d_val as u32);
310
311 out.push((triple >> 16) as u8);
312 if c != 0xFE {
313 out.push((triple >> 8) as u8);
314 }
315 if d != 0xFE {
316 out.push(triple as u8);
317 }
318 }
319
320 Ok(out)
321}
322
323// ---------------------------------------------------------------------------
324// PEM decoding
325// ---------------------------------------------------------------------------
326
327const PEM_CERT_BEGIN: &str = "-----BEGIN CERTIFICATE-----";
328const PEM_CERT_END: &str = "-----END CERTIFICATE-----";
329
330/// Decode a single PEM-encoded certificate, returning DER bytes.
331pub fn pem_decode(pem: &str) -> Result<Vec<u8>> {
332 let certs = pem_decode_multi(pem)?;
333 if certs.len() != 1 {
334 return Err(X509Error::InvalidPem);
335 }
336 Ok(certs.into_iter().next().unwrap())
337}
338
339/// Decode multiple PEM-encoded certificates from a bundle.
340pub fn pem_decode_multi(pem: &str) -> Result<Vec<Vec<u8>>> {
341 let mut results = Vec::new();
342 let mut remaining = pem;
343
344 while let Some(begin_pos) = remaining.find(PEM_CERT_BEGIN) {
345 remaining = &remaining[begin_pos + PEM_CERT_BEGIN.len()..];
346
347 let end_pos = remaining.find(PEM_CERT_END).ok_or(X509Error::InvalidPem)?;
348 let b64_data = &remaining[..end_pos];
349 remaining = &remaining[end_pos + PEM_CERT_END.len()..];
350
351 let der = base64_decode(b64_data)?;
352 if der.is_empty() {
353 return Err(X509Error::InvalidPem);
354 }
355 results.push(der);
356 }
357
358 if results.is_empty() {
359 return Err(X509Error::InvalidPem);
360 }
361
362 Ok(results)
363}
364
365// ---------------------------------------------------------------------------
366// Distinguished Name (DN)
367// ---------------------------------------------------------------------------
368
369/// A parsed X.500 Distinguished Name.
370#[derive(Debug, Clone, Default, PartialEq, Eq)]
371pub struct DistinguishedName {
372 pub common_name: Option<String>,
373 pub organization: Option<String>,
374 pub organizational_unit: Option<String>,
375 pub country: Option<String>,
376 /// The raw DER-encoded bytes of the Name for comparison.
377 pub raw: Vec<u8>,
378}
379
380impl DistinguishedName {
381 fn parse(tlv: &Tlv<'_>) -> Result<Self> {
382 let mut dn = DistinguishedName {
383 raw: tlv.raw.to_vec(),
384 ..Default::default()
385 };
386
387 // Name ::= SEQUENCE OF RelativeDistinguishedName
388 // RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
389 // AttributeTypeAndValue ::= SEQUENCE { type OID, value ANY }
390 let rdns = tlv.sequence_items()?;
391 for rdn_set in &rdns {
392 if rdn_set.tag != TAG_SET {
393 continue;
394 }
395 let attrs = rdn_set.sequence_items()?;
396 for attr in &attrs {
397 if attr.tag != TAG_SEQUENCE {
398 continue;
399 }
400 let items = attr.sequence_items()?;
401 if items.len() < 2 {
402 continue;
403 }
404 let oid = match items[0].as_oid() {
405 Ok(o) => o,
406 Err(_) => continue,
407 };
408 let value = match items[1].as_string() {
409 Ok(s) => s.to_string(),
410 Err(_) => continue,
411 };
412
413 if oid.matches(OID_COMMON_NAME) {
414 dn.common_name = Some(value);
415 } else if oid.matches(OID_ORGANIZATION_NAME) {
416 dn.organization = Some(value);
417 } else if oid.matches(OID_ORGANIZATIONAL_UNIT_NAME) {
418 dn.organizational_unit = Some(value);
419 } else if oid.matches(OID_COUNTRY_NAME) {
420 dn.country = Some(value);
421 }
422 }
423 }
424
425 Ok(dn)
426 }
427}
428
429impl fmt::Display for DistinguishedName {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 let mut parts = Vec::new();
432 if let Some(cn) = &self.common_name {
433 parts.push(format!("CN={cn}"));
434 }
435 if let Some(o) = &self.organization {
436 parts.push(format!("O={o}"));
437 }
438 if let Some(ou) = &self.organizational_unit {
439 parts.push(format!("OU={ou}"));
440 }
441 if let Some(c) = &self.country {
442 parts.push(format!("C={c}"));
443 }
444 write!(f, "{}", parts.join(", "))
445 }
446}
447
448// ---------------------------------------------------------------------------
449// Signature Algorithm
450// ---------------------------------------------------------------------------
451
452/// Supported signature algorithms for X.509 certificates.
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454pub enum SignatureAlgorithm {
455 Sha256WithRsa,
456 Sha384WithRsa,
457 Sha512WithRsa,
458 EcdsaWithSha256,
459 EcdsaWithSha384,
460}
461
462fn parse_signature_algorithm(tlv: &Tlv<'_>) -> Result<SignatureAlgorithm> {
463 let items = tlv.sequence_items()?;
464 if items.is_empty() {
465 return Err(X509Error::InvalidCertificate);
466 }
467 let oid = items[0].as_oid()?;
468
469 if oid.matches(OID_SHA256_WITH_RSA) {
470 Ok(SignatureAlgorithm::Sha256WithRsa)
471 } else if oid.matches(OID_SHA384_WITH_RSA) {
472 Ok(SignatureAlgorithm::Sha384WithRsa)
473 } else if oid.matches(OID_SHA512_WITH_RSA) {
474 Ok(SignatureAlgorithm::Sha512WithRsa)
475 } else if oid.matches(OID_ECDSA_WITH_SHA256) {
476 Ok(SignatureAlgorithm::EcdsaWithSha256)
477 } else if oid.matches(OID_ECDSA_WITH_SHA384) {
478 Ok(SignatureAlgorithm::EcdsaWithSha384)
479 } else {
480 Err(X509Error::UnsupportedAlgorithm)
481 }
482}
483
484// ---------------------------------------------------------------------------
485// Extensions
486// ---------------------------------------------------------------------------
487
488/// Basic Constraints extension.
489#[derive(Debug, Clone, PartialEq, Eq)]
490pub struct BasicConstraints {
491 pub ca: bool,
492 pub path_len_constraint: Option<u32>,
493}
494
495/// Key Usage flags (bit flags from the KeyUsage BIT STRING).
496#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct KeyUsage {
498 pub digital_signature: bool,
499 pub key_encipherment: bool,
500 pub key_cert_sign: bool,
501 pub crl_sign: bool,
502 pub raw_bits: Vec<u8>,
503}
504
505/// Subject Alternative Name types.
506#[derive(Debug, Clone, PartialEq, Eq)]
507pub enum SubjectAltName {
508 DnsName(String),
509 IpAddress(Vec<u8>),
510 Other(u8, Vec<u8>),
511}
512
513/// Parsed X.509v3 extensions.
514#[derive(Debug, Clone, Default)]
515pub struct Extensions {
516 pub basic_constraints: Option<BasicConstraints>,
517 pub key_usage: Option<KeyUsage>,
518 pub subject_alt_names: Vec<SubjectAltName>,
519 pub subject_key_identifier: Option<Vec<u8>>,
520 pub authority_key_identifier: Option<Vec<u8>>,
521}
522
523fn parse_extensions(ext_data: &[u8]) -> Result<Extensions> {
524 let mut exts = Extensions::default();
525
526 let mut parser = DerParser::new(ext_data);
527 while parser.has_more() {
528 let ext_seq = parser.read_tlv()?;
529 if ext_seq.tag != TAG_SEQUENCE {
530 continue;
531 }
532 let items = ext_seq.sequence_items()?;
533 if items.len() < 2 {
534 continue;
535 }
536
537 let oid = match items[0].as_oid() {
538 Ok(o) => o,
539 Err(_) => continue,
540 };
541
542 // The value is either items[1] (OCTET STRING) or items[2] if items[1] is critical BOOLEAN.
543 let value_tlv = if items.len() >= 3 && items[1].tag == TAG_BOOLEAN {
544 &items[2]
545 } else {
546 &items[1]
547 };
548
549 // Extension value is wrapped in an OCTET STRING.
550 let ext_value = if value_tlv.tag == 0x04 {
551 // TAG_OCTET_STRING
552 value_tlv.value
553 } else {
554 continue;
555 };
556
557 if oid.matches(OID_BASIC_CONSTRAINTS) {
558 exts.basic_constraints = parse_basic_constraints(ext_value).ok();
559 } else if oid.matches(OID_KEY_USAGE) {
560 exts.key_usage = parse_key_usage(ext_value).ok();
561 } else if oid.matches(OID_SUBJECT_ALT_NAME) {
562 exts.subject_alt_names = parse_subject_alt_names(ext_value).unwrap_or_default();
563 } else if oid.matches(OID_SUBJECT_KEY_IDENTIFIER) {
564 exts.subject_key_identifier = parse_subject_key_identifier(ext_value).ok();
565 } else if oid.matches(OID_AUTHORITY_KEY_IDENTIFIER) {
566 exts.authority_key_identifier = parse_authority_key_identifier(ext_value).ok();
567 }
568 // Unknown extensions are silently ignored.
569 }
570
571 Ok(exts)
572}
573
574fn parse_basic_constraints(data: &[u8]) -> Result<BasicConstraints> {
575 let seq = asn1::parse_one(data)?;
576 if seq.tag != TAG_SEQUENCE {
577 // Empty SEQUENCE is valid (ca=false, no path len).
578 return Err(X509Error::InvalidCertificate);
579 }
580
581 let items = seq.sequence_items()?;
582 let mut ca = false;
583 let mut path_len_constraint = None;
584
585 if !items.is_empty() && items[0].tag == TAG_BOOLEAN {
586 ca = items[0].as_boolean()?;
587 }
588 if items.len() >= 2 && items[1].tag == TAG_INTEGER {
589 let int_bytes = items[1].as_positive_integer()?;
590 if int_bytes.len() <= 4 {
591 let mut val: u32 = 0;
592 for &b in int_bytes {
593 val = (val << 8) | b as u32;
594 }
595 path_len_constraint = Some(val);
596 }
597 }
598 // Special case: if only one item and it's an INTEGER, it's path_len (ca defaults to false)
599 if items.len() == 1 && items[0].tag == TAG_INTEGER {
600 let int_bytes = items[0].as_positive_integer()?;
601 if int_bytes.len() <= 4 {
602 let mut val: u32 = 0;
603 for &b in int_bytes {
604 val = (val << 8) | b as u32;
605 }
606 path_len_constraint = Some(val);
607 }
608 }
609
610 Ok(BasicConstraints {
611 ca,
612 path_len_constraint,
613 })
614}
615
616fn parse_key_usage(data: &[u8]) -> Result<KeyUsage> {
617 let tlv = asn1::parse_one(data)?;
618 let (unused_bits, bits) = tlv.as_bit_string()?;
619
620 let byte0 = if bits.is_empty() { 0u8 } else { bits[0] };
621 let _ = unused_bits; // We use the bits as-is for known positions.
622
623 Ok(KeyUsage {
624 digital_signature: byte0 & 0x80 != 0,
625 key_encipherment: byte0 & 0x20 != 0,
626 key_cert_sign: byte0 & 0x04 != 0,
627 crl_sign: byte0 & 0x02 != 0,
628 raw_bits: bits.to_vec(),
629 })
630}
631
632fn parse_subject_alt_names(data: &[u8]) -> Result<Vec<SubjectAltName>> {
633 let seq = asn1::parse_one(data)?;
634 let items = seq.sequence_items()?;
635 let mut names = Vec::new();
636
637 for item in &items {
638 if !item.is_context_specific() {
639 continue;
640 }
641 match item.context_tag() {
642 Some(2) => {
643 // dNSName [2] IA5String
644 if let Ok(s) = core::str::from_utf8(item.value) {
645 names.push(SubjectAltName::DnsName(s.to_string()));
646 }
647 }
648 Some(7) => {
649 // iPAddress [7] OCTET STRING
650 names.push(SubjectAltName::IpAddress(item.value.to_vec()));
651 }
652 Some(tag) => {
653 names.push(SubjectAltName::Other(tag, item.value.to_vec()));
654 }
655 None => {}
656 }
657 }
658
659 Ok(names)
660}
661
662fn parse_subject_key_identifier(data: &[u8]) -> Result<Vec<u8>> {
663 let tlv = asn1::parse_one(data)?;
664 Ok(tlv.as_octet_string()?.to_vec())
665}
666
667fn parse_authority_key_identifier(data: &[u8]) -> Result<Vec<u8>> {
668 // AuthorityKeyIdentifier ::= SEQUENCE {
669 // keyIdentifier [0] KeyIdentifier OPTIONAL,
670 // ...
671 // }
672 let seq = asn1::parse_one(data)?;
673 let mut parser = DerParser::new(seq.value);
674 while parser.has_more() {
675 let tlv = parser.read_tlv()?;
676 if tlv.is_context_specific() && tlv.context_tag() == Some(0) {
677 return Ok(tlv.value.to_vec());
678 }
679 }
680 Err(X509Error::InvalidCertificate)
681}
682
683// ---------------------------------------------------------------------------
684// X.509 Certificate
685// ---------------------------------------------------------------------------
686
687/// A parsed X.509 certificate.
688#[derive(Debug, Clone)]
689pub struct Certificate {
690 /// Certificate version (0 = v1, 1 = v2, 2 = v3).
691 pub version: u8,
692 /// Serial number bytes.
693 pub serial_number: Vec<u8>,
694 /// Signature algorithm used by the issuer.
695 pub signature_algorithm: SignatureAlgorithm,
696 /// Issuer distinguished name.
697 pub issuer: DistinguishedName,
698 /// Validity: not before.
699 pub not_before: DateTime,
700 /// Validity: not after.
701 pub not_after: DateTime,
702 /// Subject distinguished name.
703 pub subject: DistinguishedName,
704 /// Subject public key info (raw DER bytes).
705 pub subject_public_key_info: Vec<u8>,
706 /// V3 extensions.
707 pub extensions: Extensions,
708 /// The raw DER bytes of the TBSCertificate (for signature verification).
709 pub tbs_certificate_raw: Vec<u8>,
710 /// The signature value bytes.
711 pub signature_value: Vec<u8>,
712 /// The full DER-encoded certificate.
713 pub raw: Vec<u8>,
714}
715
716impl Certificate {
717 /// Parse a certificate from DER-encoded bytes.
718 pub fn from_der(data: &[u8]) -> Result<Self> {
719 let outer = asn1::parse_one(data)?;
720 if outer.tag != TAG_SEQUENCE {
721 return Err(X509Error::InvalidCertificate);
722 }
723
724 let items = outer.sequence_items()?;
725 if items.len() != 3 {
726 return Err(X509Error::InvalidCertificate);
727 }
728
729 // items[0] = TBSCertificate (SEQUENCE)
730 // items[1] = signatureAlgorithm (SEQUENCE)
731 // items[2] = signatureValue (BIT STRING)
732
733 let tbs = &items[0];
734 let sig_alg_outer = &items[1];
735 let sig_val_tlv = &items[2];
736
737 if tbs.tag != TAG_SEQUENCE {
738 return Err(X509Error::InvalidCertificate);
739 }
740
741 let tbs_certificate_raw = tbs.raw.to_vec();
742
743 // Parse the outer signature algorithm (used to verify).
744 let signature_algorithm = parse_signature_algorithm(sig_alg_outer)?;
745
746 // Parse signature value.
747 let (unused_bits, sig_bytes) = sig_val_tlv.as_bit_string()?;
748 if unused_bits != 0 {
749 return Err(X509Error::InvalidCertificate);
750 }
751 let signature_value = sig_bytes.to_vec();
752
753 // Parse TBSCertificate fields.
754 let mut tbs_parser = DerParser::new(tbs.value);
755
756 // version [0] EXPLICIT INTEGER DEFAULT v1
757 let version = if tbs_parser.has_more() {
758 let peeked = tbs_parser.peek_tag()?;
759 if peeked == (TAG_CLASS_CONTEXT | TAG_CONSTRUCTED) {
760 let ver_wrapper = tbs_parser.read_tlv()?;
761 let ver_tlv = asn1::parse_one(ver_wrapper.value)?;
762 let ver_bytes = ver_tlv.as_integer()?;
763 if ver_bytes.len() != 1 {
764 return Err(X509Error::InvalidCertificate);
765 }
766 ver_bytes[0]
767 } else {
768 0 // Default v1
769 }
770 } else {
771 return Err(X509Error::InvalidCertificate);
772 };
773
774 if version > 2 {
775 return Err(X509Error::UnsupportedVersion);
776 }
777
778 // serialNumber INTEGER
779 let serial_tlv = tbs_parser.read_expect(TAG_INTEGER)?;
780 let serial_number = serial_tlv.value.to_vec();
781
782 // signature AlgorithmIdentifier (inner — must match outer)
783 let inner_sig_alg = tbs_parser.read_expect(TAG_SEQUENCE)?;
784 let inner_algorithm = parse_signature_algorithm(&inner_sig_alg)?;
785 if inner_algorithm != signature_algorithm {
786 return Err(X509Error::InvalidCertificate);
787 }
788
789 // issuer Name
790 let issuer_tlv = tbs_parser.read_expect(TAG_SEQUENCE)?;
791 let issuer = DistinguishedName::parse(&issuer_tlv)?;
792
793 // validity SEQUENCE { notBefore Time, notAfter Time }
794 let validity_tlv = tbs_parser.read_expect(TAG_SEQUENCE)?;
795 let validity_items = validity_tlv.sequence_items()?;
796 if validity_items.len() != 2 {
797 return Err(X509Error::InvalidCertificate);
798 }
799 let not_before = parse_time(&validity_items[0])?;
800 let not_after = parse_time(&validity_items[1])?;
801
802 // subject Name
803 let subject_tlv = tbs_parser.read_expect(TAG_SEQUENCE)?;
804 let subject = DistinguishedName::parse(&subject_tlv)?;
805
806 // subjectPublicKeyInfo SEQUENCE
807 let spki_tlv = tbs_parser.read_expect(TAG_SEQUENCE)?;
808 let subject_public_key_info = spki_tlv.raw.to_vec();
809
810 // extensions [3] EXPLICIT SEQUENCE OF Extension (v3 only)
811 let mut extensions = Extensions::default();
812 if version == 2 {
813 // Look for [3] CONSTRUCTED context tag.
814 if let Some(ext_wrapper) = tbs_parser.read_optional_context(3, true)? {
815 // The wrapper contains a SEQUENCE of Extensions.
816 let ext_seq = asn1::parse_one(ext_wrapper.value)?;
817 if ext_seq.tag == TAG_SEQUENCE {
818 extensions = parse_extensions(ext_seq.value)?;
819 }
820 }
821 }
822
823 Ok(Certificate {
824 version,
825 serial_number,
826 signature_algorithm,
827 issuer,
828 not_before,
829 not_after,
830 subject,
831 subject_public_key_info,
832 extensions,
833 tbs_certificate_raw,
834 signature_value,
835 raw: data.to_vec(),
836 })
837 }
838
839 /// Parse a certificate from PEM-encoded data.
840 pub fn from_pem(pem: &str) -> Result<Self> {
841 let der = pem_decode(pem)?;
842 Self::from_der(&der)
843 }
844
845 /// Check if this certificate is valid at the given time.
846 pub fn is_valid_at(&self, now: &DateTime) -> bool {
847 now >= &self.not_before && now <= &self.not_after
848 }
849
850 /// Check if this certificate is a CA (has Basic Constraints with ca=true).
851 pub fn is_ca(&self) -> bool {
852 self.extensions
853 .basic_constraints
854 .as_ref()
855 .is_some_and(|bc| bc.ca)
856 }
857
858 /// Check if this certificate is self-signed (subject == issuer by raw DER).
859 pub fn is_self_signed(&self) -> bool {
860 self.subject.raw == self.issuer.raw
861 }
862
863 /// Verify that this certificate's signature was produced by `issuer_cert`.
864 pub fn verify_signature(&self, issuer_cert: &Certificate) -> Result<()> {
865 let tbs_data = &self.tbs_certificate_raw;
866 let sig_data = &self.signature_value;
867
868 match self.signature_algorithm {
869 SignatureAlgorithm::Sha256WithRsa
870 | SignatureAlgorithm::Sha384WithRsa
871 | SignatureAlgorithm::Sha512WithRsa => {
872 let hash_alg = match self.signature_algorithm {
873 SignatureAlgorithm::Sha256WithRsa => HashAlgorithm::Sha256,
874 SignatureAlgorithm::Sha384WithRsa => HashAlgorithm::Sha384,
875 SignatureAlgorithm::Sha512WithRsa => HashAlgorithm::Sha512,
876 _ => unreachable!(),
877 };
878 let rsa_key = RsaPublicKey::from_der(&issuer_cert.subject_public_key_info)
879 .map_err(|e| X509Error::Rsa(format!("{e}")))?;
880
881 rsa_key
882 .verify_pkcs1v15(hash_alg, tbs_data, sig_data)
883 .map_err(|e| X509Error::Rsa(format!("{e}")))?;
884
885 Ok(())
886 }
887 SignatureAlgorithm::EcdsaWithSha256 | SignatureAlgorithm::EcdsaWithSha384 => {
888 let ec_key = EcdsaPublicKey::from_spki_der(&issuer_cert.subject_public_key_info)
889 .map_err(|e| X509Error::Ecdsa(format!("{e}")))?;
890
891 let ecdsa_sig = EcdsaSignature::from_der(sig_data)
892 .map_err(|e| X509Error::Ecdsa(format!("{e}")))?;
893
894 // Hash the TBS data with the appropriate algorithm and verify.
895 let hash = match self.signature_algorithm {
896 SignatureAlgorithm::EcdsaWithSha256 => sha256(tbs_data).to_vec(),
897 SignatureAlgorithm::EcdsaWithSha384 => sha384(tbs_data).to_vec(),
898 _ => unreachable!(),
899 };
900
901 ec_key
902 .verify_prehashed(&hash, &ecdsa_sig)
903 .map_err(|e| X509Error::Ecdsa(format!("{e}")))?;
904
905 Ok(())
906 }
907 }
908 }
909
910 /// Verify a self-signed certificate's own signature.
911 pub fn verify_self_signed(&self) -> Result<()> {
912 self.verify_signature(self)
913 }
914}
915
916fn parse_time(tlv: &Tlv<'_>) -> Result<DateTime> {
917 match tlv.tag {
918 TAG_UTC_TIME => {
919 let s = tlv.as_utc_time()?;
920 parse_utc_time(s)
921 }
922 TAG_GENERALIZED_TIME => {
923 let s = tlv.as_generalized_time()?;
924 parse_generalized_time(s)
925 }
926 _ => Err(X509Error::InvalidCertificate),
927 }
928}
929
930// ---------------------------------------------------------------------------
931// Certificate chain validation
932// ---------------------------------------------------------------------------
933
934/// Validate a certificate chain.
935///
936/// `chain` should be ordered leaf-first: chain[0] is the leaf, chain[last] is closest to root.
937/// `trust_anchors` are the trusted root CA certificates.
938/// `now` is the current time for validity checking.
939///
940/// Returns Ok(()) if the chain is valid.
941pub fn validate_chain(
942 chain: &[Certificate],
943 trust_anchors: &[Certificate],
944 now: &DateTime,
945) -> Result<()> {
946 if chain.is_empty() {
947 return Err(X509Error::InvalidChain);
948 }
949
950 // Validate each certificate in the chain is within its validity period.
951 for cert in chain {
952 if now < &cert.not_before {
953 return Err(X509Error::CertificateNotYetValid);
954 }
955 if now > &cert.not_after {
956 return Err(X509Error::CertificateExpired);
957 }
958 }
959
960 // Verify the chain of signatures.
961 // For each cert[i], verify its signature using cert[i+1].
962 for i in 0..chain.len() - 1 {
963 let cert = &chain[i];
964 let issuer = &chain[i + 1];
965
966 // Check issuer/subject linkage.
967 if cert.issuer.raw != issuer.subject.raw {
968 return Err(X509Error::IssuerMismatch);
969 }
970
971 // Non-leaf certs must be CAs.
972 if !issuer.is_ca() {
973 return Err(X509Error::NotCaCertificate);
974 }
975
976 // Verify the signature.
977 cert.verify_signature(issuer)
978 .map_err(|_| X509Error::SignatureVerificationFailed)?;
979 }
980
981 // The last cert in the chain should either be a trust anchor itself, or
982 // its issuer should be a trust anchor.
983 let top_cert = &chain[chain.len() - 1];
984
985 // Check if the top cert is itself a trust anchor.
986 for anchor in trust_anchors {
987 if top_cert.raw == anchor.raw {
988 return Ok(());
989 }
990 }
991
992 // Check if the top cert is signed by a trust anchor.
993 for anchor in trust_anchors {
994 if top_cert.issuer.raw == anchor.subject.raw {
995 // Verify signature.
996 if top_cert.verify_signature(anchor).is_ok() {
997 // Also check the anchor's validity.
998 if now < &anchor.not_before {
999 return Err(X509Error::CertificateNotYetValid);
1000 }
1001 if now > &anchor.not_after {
1002 return Err(X509Error::CertificateExpired);
1003 }
1004 return Ok(());
1005 }
1006 }
1007 }
1008
1009 Err(X509Error::UntrustedRoot)
1010}
1011
1012// ---------------------------------------------------------------------------
1013// Embedded Root CA Store
1014// ---------------------------------------------------------------------------
1015
1016/// ISRG Root X1 (Let's Encrypt) — RSA 4096-bit, valid 2015-2035.
1017pub const ISRG_ROOT_X1_PEM: &str = "-----BEGIN CERTIFICATE-----
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
1047-----END CERTIFICATE-----";
1048
1049/// DigiCert Global Root G2 — RSA 2048-bit, valid 2013-2038.
1050pub const DIGICERT_GLOBAL_ROOT_G2_PEM: &str = "-----BEGIN CERTIFICATE-----
1051MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
1052MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
1053d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
1054MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
1055MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
1056b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
10579w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
10582/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
10591x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
1060q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
1061tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
1062vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
1063BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
10645uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
10651Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
1066NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
1067Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
10688rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
1069pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
1070MrY=
1071-----END CERTIFICATE-----";
1072
1073/// GlobalSign Root CA - R3 — RSA 2048-bit, valid 2009-2029.
1074pub const GLOBALSIGN_ROOT_R3_PEM: &str = "-----BEGIN CERTIFICATE-----
1075MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
1076A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
1077Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
1078MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
1079A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
1080hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
1081RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
1082gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
1083KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
1084QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
1085XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
1086DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
1087LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
1088RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
1089jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
10906fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
1091mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
1092Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
1093WD9f
1094-----END CERTIFICATE-----";
1095
1096/// Load the embedded root CA certificates.
1097pub fn root_ca_store() -> Result<Vec<Certificate>> {
1098 let mut roots = Vec::new();
1099 for pem in &[
1100 ISRG_ROOT_X1_PEM,
1101 DIGICERT_GLOBAL_ROOT_G2_PEM,
1102 GLOBALSIGN_ROOT_R3_PEM,
1103 ] {
1104 roots.push(Certificate::from_pem(pem)?);
1105 }
1106 Ok(roots)
1107}
1108
1109// ---------------------------------------------------------------------------
1110// Tests
1111// ---------------------------------------------------------------------------
1112
1113#[cfg(test)]
1114mod tests {
1115 use super::*;
1116
1117 // -------------------------------------------------------------------
1118 // Base64 tests
1119 // -------------------------------------------------------------------
1120
1121 #[test]
1122 fn base64_decode_basic() {
1123 assert_eq!(base64_decode("").ok(), Some(vec![]));
1124 // Padding results still produce correct output.
1125 assert_eq!(base64_decode("YQ==").unwrap(), b"a");
1126 assert_eq!(base64_decode("YWI=").unwrap(), b"ab");
1127 assert_eq!(base64_decode("YWJj").unwrap(), b"abc");
1128 assert_eq!(base64_decode("YWJjZA==").unwrap(), b"abcd");
1129 }
1130
1131 #[test]
1132 fn base64_decode_with_whitespace() {
1133 // "YWJj" + "ZA==" = base64("abcd"), with whitespace inserted.
1134 let encoded = "YWJj\nZA==";
1135 assert_eq!(base64_decode(encoded).unwrap(), b"abcd");
1136 }
1137
1138 #[test]
1139 fn base64_decode_invalid() {
1140 // Not a multiple of 4 after stripping whitespace.
1141 assert!(base64_decode("YWJ").is_err());
1142 // Invalid character.
1143 assert!(base64_decode("YW@j").is_err());
1144 }
1145
1146 // -------------------------------------------------------------------
1147 // PEM tests
1148 // -------------------------------------------------------------------
1149
1150 #[test]
1151 fn pem_decode_single_cert() {
1152 // Use ISRG Root X1.
1153 let der = pem_decode(ISRG_ROOT_X1_PEM).unwrap();
1154 // DER should start with SEQUENCE tag 0x30.
1155 assert_eq!(der[0], 0x30);
1156 assert!(der.len() > 100);
1157 }
1158
1159 #[test]
1160 fn pem_decode_multi_cert() {
1161 let bundle = format!("{}\n{}", ISRG_ROOT_X1_PEM, DIGICERT_GLOBAL_ROOT_G2_PEM);
1162 let certs = pem_decode_multi(&bundle).unwrap();
1163 assert_eq!(certs.len(), 2);
1164 assert_eq!(certs[0][0], 0x30);
1165 assert_eq!(certs[1][0], 0x30);
1166 }
1167
1168 #[test]
1169 fn pem_decode_invalid_no_markers() {
1170 assert!(pem_decode("not a certificate").is_err());
1171 }
1172
1173 #[test]
1174 fn pem_decode_invalid_missing_end() {
1175 let bad = "-----BEGIN CERTIFICATE-----\nYWJj\n";
1176 assert!(pem_decode(bad).is_err());
1177 }
1178
1179 // -------------------------------------------------------------------
1180 // DateTime tests
1181 // -------------------------------------------------------------------
1182
1183 #[test]
1184 fn datetime_ordering() {
1185 let d1 = DateTime::new(2020, 1, 1, 0, 0, 0);
1186 let d2 = DateTime::new(2020, 1, 1, 0, 0, 1);
1187 let d3 = DateTime::new(2021, 1, 1, 0, 0, 0);
1188 let d4 = DateTime::new(2020, 6, 15, 12, 30, 0);
1189
1190 assert!(d1 < d2);
1191 assert!(d2 < d4);
1192 assert!(d4 < d3);
1193 assert!(d1 < d3);
1194 assert_eq!(d1, d1.clone());
1195 }
1196
1197 #[test]
1198 fn parse_utc_time_valid() {
1199 let dt = parse_utc_time("150604110438Z").unwrap();
1200 assert_eq!(dt.year, 2015);
1201 assert_eq!(dt.month, 6);
1202 assert_eq!(dt.day, 4);
1203 assert_eq!(dt.hour, 11);
1204 assert_eq!(dt.minute, 4);
1205 assert_eq!(dt.second, 38);
1206 }
1207
1208 #[test]
1209 fn parse_utc_time_1900s() {
1210 // YY >= 50 means 19YY.
1211 let dt = parse_utc_time("980901120000Z").unwrap();
1212 assert_eq!(dt.year, 1998);
1213 }
1214
1215 #[test]
1216 fn parse_generalized_time_valid() {
1217 let dt = parse_generalized_time("20350604110438Z").unwrap();
1218 assert_eq!(dt.year, 2035);
1219 assert_eq!(dt.month, 6);
1220 assert_eq!(dt.day, 4);
1221 }
1222
1223 // -------------------------------------------------------------------
1224 // Certificate parsing tests
1225 // -------------------------------------------------------------------
1226
1227 #[test]
1228 fn parse_isrg_root_x1() {
1229 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1230
1231 assert_eq!(cert.version, 2); // v3
1232 assert_eq!(cert.subject.common_name.as_deref(), Some("ISRG Root X1"));
1233 assert_eq!(
1234 cert.subject.organization.as_deref(),
1235 Some("Internet Security Research Group")
1236 );
1237 assert_eq!(cert.subject.country.as_deref(), Some("US"));
1238
1239 // Self-signed: issuer == subject.
1240 assert!(cert.is_self_signed());
1241 assert_eq!(cert.issuer.common_name, cert.subject.common_name);
1242
1243 // Validity.
1244 assert_eq!(cert.not_before.year, 2015);
1245 assert_eq!(cert.not_after.year, 2035);
1246
1247 // Signature algorithm.
1248 assert_eq!(cert.signature_algorithm, SignatureAlgorithm::Sha256WithRsa);
1249
1250 // CA cert.
1251 assert!(cert.is_ca());
1252
1253 // Extensions.
1254 assert!(cert.extensions.basic_constraints.is_some());
1255 let bc = cert.extensions.basic_constraints.as_ref().unwrap();
1256 assert!(bc.ca);
1257
1258 assert!(cert.extensions.key_usage.is_some());
1259 let ku = cert.extensions.key_usage.as_ref().unwrap();
1260 assert!(ku.key_cert_sign);
1261 assert!(ku.crl_sign);
1262
1263 assert!(cert.extensions.subject_key_identifier.is_some());
1264 }
1265
1266 #[test]
1267 fn parse_digicert_global_root_g2() {
1268 let cert = Certificate::from_pem(DIGICERT_GLOBAL_ROOT_G2_PEM).unwrap();
1269
1270 assert_eq!(cert.version, 2);
1271 assert_eq!(
1272 cert.subject.common_name.as_deref(),
1273 Some("DigiCert Global Root G2")
1274 );
1275 assert_eq!(cert.subject.organization.as_deref(), Some("DigiCert Inc"));
1276 assert_eq!(cert.subject.country.as_deref(), Some("US"));
1277
1278 assert!(cert.is_self_signed());
1279 assert!(cert.is_ca());
1280
1281 assert_eq!(cert.not_before.year, 2013);
1282 assert_eq!(cert.not_after.year, 2038);
1283
1284 assert_eq!(cert.signature_algorithm, SignatureAlgorithm::Sha256WithRsa);
1285 }
1286
1287 #[test]
1288 fn parse_globalsign_root_r3() {
1289 let cert = Certificate::from_pem(GLOBALSIGN_ROOT_R3_PEM).unwrap();
1290
1291 assert_eq!(cert.version, 2);
1292 assert_eq!(cert.subject.common_name.as_deref(), Some("GlobalSign"));
1293 assert_eq!(cert.subject.organization.as_deref(), Some("GlobalSign"));
1294
1295 assert!(cert.is_self_signed());
1296 assert!(cert.is_ca());
1297
1298 assert_eq!(cert.not_before.year, 2009);
1299 assert_eq!(cert.not_after.year, 2029);
1300
1301 assert_eq!(cert.signature_algorithm, SignatureAlgorithm::Sha256WithRsa);
1302 }
1303
1304 // -------------------------------------------------------------------
1305 // Signature verification tests
1306 // -------------------------------------------------------------------
1307
1308 #[test]
1309 fn verify_isrg_root_x1_self_signed() {
1310 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1311 cert.verify_self_signed().unwrap();
1312 }
1313
1314 #[test]
1315 fn verify_digicert_global_root_g2_self_signed() {
1316 let cert = Certificate::from_pem(DIGICERT_GLOBAL_ROOT_G2_PEM).unwrap();
1317 cert.verify_self_signed().unwrap();
1318 }
1319
1320 #[test]
1321 fn verify_globalsign_root_r3_self_signed() {
1322 let cert = Certificate::from_pem(GLOBALSIGN_ROOT_R3_PEM).unwrap();
1323 cert.verify_self_signed().unwrap();
1324 }
1325
1326 // -------------------------------------------------------------------
1327 // Validity checks
1328 // -------------------------------------------------------------------
1329
1330 #[test]
1331 fn certificate_validity_check() {
1332 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1333
1334 // Valid in 2025.
1335 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1336 assert!(cert.is_valid_at(&now));
1337
1338 // Before notBefore (June 4, 2015).
1339 let before = DateTime::new(2015, 1, 1, 0, 0, 0);
1340 assert!(!cert.is_valid_at(&before));
1341
1342 // After notAfter (June 4, 2035).
1343 let after = DateTime::new(2036, 1, 1, 0, 0, 0);
1344 assert!(!cert.is_valid_at(&after));
1345 }
1346
1347 // -------------------------------------------------------------------
1348 // Chain validation tests
1349 // -------------------------------------------------------------------
1350
1351 #[test]
1352 fn chain_validation_self_signed_root() {
1353 let roots = root_ca_store().unwrap();
1354 let isrg = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1355
1356 // A chain of just the root should validate against the trust store.
1357 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1358 validate_chain(&[isrg], &roots, &now).unwrap();
1359 }
1360
1361 #[test]
1362 fn chain_validation_reject_expired() {
1363 let roots = root_ca_store().unwrap();
1364
1365 // GlobalSign R3 expires in 2029, so check at 2030.
1366 let globalsign = Certificate::from_pem(GLOBALSIGN_ROOT_R3_PEM).unwrap();
1367 let future = DateTime::new(2030, 1, 1, 0, 0, 0);
1368 let result = validate_chain(&[globalsign], &roots, &future);
1369 assert!(result.is_err());
1370 }
1371
1372 #[test]
1373 fn chain_validation_reject_untrusted() {
1374 // Create a minimal "self-signed" scenario where the cert isn't in the trust store.
1375 // Use a trust store that doesn't contain our cert.
1376 let isrg = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1377 let digicert = Certificate::from_pem(DIGICERT_GLOBAL_ROOT_G2_PEM).unwrap();
1378
1379 // Use only DigiCert as trust anchor, try to validate ISRG root.
1380 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1381 let result = validate_chain(&[isrg], &[digicert], &now);
1382 assert_eq!(result, Err(X509Error::UntrustedRoot));
1383 }
1384
1385 #[test]
1386 fn chain_validation_two_cert_chain() {
1387 // We construct a two-cert chain: [DigiCert G2, ISRG X1] with ISRG as "issuer".
1388 // This won't actually validate because DigiCert isn't issued by ISRG,
1389 // but we test the issuer mismatch detection.
1390 let isrg = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1391 let digicert = Certificate::from_pem(DIGICERT_GLOBAL_ROOT_G2_PEM).unwrap();
1392
1393 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1394 let result = validate_chain(&[digicert, isrg.clone()], &[isrg], &now);
1395 assert_eq!(result, Err(X509Error::IssuerMismatch));
1396 }
1397
1398 #[test]
1399 fn chain_validation_empty_chain() {
1400 let roots = root_ca_store().unwrap();
1401 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1402 let result = validate_chain(&[], &roots, &now);
1403 assert_eq!(result, Err(X509Error::InvalidChain));
1404 }
1405
1406 // -------------------------------------------------------------------
1407 // Root CA store
1408 // -------------------------------------------------------------------
1409
1410 #[test]
1411 fn root_ca_store_loads() {
1412 let roots = root_ca_store().unwrap();
1413 assert_eq!(roots.len(), 3);
1414
1415 // All should be self-signed CAs.
1416 for root in &roots {
1417 assert!(root.is_self_signed());
1418 assert!(root.is_ca());
1419 }
1420 }
1421
1422 #[test]
1423 fn root_ca_store_all_valid_now() {
1424 let roots = root_ca_store().unwrap();
1425 let now = DateTime::new(2025, 6, 15, 12, 0, 0);
1426 for root in &roots {
1427 assert!(root.is_valid_at(&now), "root {} not valid", root.subject);
1428 }
1429 }
1430
1431 // -------------------------------------------------------------------
1432 // Display impls
1433 // -------------------------------------------------------------------
1434
1435 #[test]
1436 fn distinguished_name_display() {
1437 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1438 let display = format!("{}", cert.subject);
1439 assert!(display.contains("CN=ISRG Root X1"));
1440 assert!(display.contains("O=Internet Security Research Group"));
1441 assert!(display.contains("C=US"));
1442 }
1443
1444 #[test]
1445 fn datetime_display() {
1446 let dt = DateTime::new(2025, 3, 12, 14, 30, 0);
1447 assert_eq!(format!("{dt}"), "2025-03-12T14:30:00Z");
1448 }
1449
1450 // -------------------------------------------------------------------
1451 // Subject public key info parsing
1452 // -------------------------------------------------------------------
1453
1454 #[test]
1455 fn spki_bytes_parse_as_rsa_key() {
1456 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1457 // The SPKI should parse as an RSA key.
1458 let key = RsaPublicKey::from_der(&cert.subject_public_key_info);
1459 assert!(key.is_ok(), "Failed to parse RSA key from SPKI");
1460 }
1461
1462 // -------------------------------------------------------------------
1463 // Serial number
1464 // -------------------------------------------------------------------
1465
1466 #[test]
1467 fn serial_number_not_empty() {
1468 let cert = Certificate::from_pem(ISRG_ROOT_X1_PEM).unwrap();
1469 assert!(!cert.serial_number.is_empty());
1470 }
1471
1472 // -------------------------------------------------------------------
1473 // Edge cases
1474 // -------------------------------------------------------------------
1475
1476 #[test]
1477 fn reject_truncated_der() {
1478 let der = pem_decode(ISRG_ROOT_X1_PEM).unwrap();
1479 // Truncate the DER.
1480 let truncated = &der[..100];
1481 let result = Certificate::from_der(truncated);
1482 assert!(result.is_err());
1483 }
1484
1485 #[test]
1486 fn reject_garbage_der() {
1487 let result = Certificate::from_der(&[0xFF, 0xFF, 0xFF]);
1488 assert!(result.is_err());
1489 }
1490}