we (web engine): Experimental web browser project to understand the limits of Claude
at http-parser 1490 lines 52 kB view raw
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}