1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
17use std::sync::{Arc, OnceLock};
18use std::time::{Duration, SystemTime};
19use std::{fmt, ops::Range};
20
21use base64::Engine;
22use ntex_bytes::Bytes;
23use x509_parser::asn1_rs::{Any, Tag};
24use x509_parser::extensions::GeneralName;
25use x509_parser::prelude::{FromDer, X509Certificate};
26use x509_parser::time::ASN1Time;
27
28use crate::x509::{BASE64, Error, digest};
29
30const PEM_BEGIN: &str = "-----BEGIN ";
31const PEM_END: &str = "-----END ";
32const PEM_LABELS: [&str; 2] = ["CERTIFICATE", "X509 CERTIFICATE"];
33
34#[derive(Clone)]
36pub struct Certificate(Arc<Inner>);
37
38struct Inner {
39 der: Bytes,
40 spki: Range<usize>,
41 subject_raw: Range<usize>,
42 issuer_raw: Range<usize>,
43 key_type: Option<KeyType>,
44 version: u32,
45 serial: String,
46 subject: String,
47 issuer: String,
48 subject_cn: Option<String>,
49 san: SubjectAltNames,
50 not_before: SystemTime,
51 not_after: SystemTime,
52 sha1: OnceLock<[u8; digest::SHA1_LEN]>,
53 sha256: OnceLock<[u8; digest::SHA256_LEN]>,
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq)]
58pub struct SubjectAltNames {
59 pub dns: Vec<String>,
61 pub uri: Vec<String>,
63 pub email: Vec<String>,
65 pub ip: Vec<SanIp>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum KeyType {
72 Rsa,
74 EcP256,
76 EcP384,
78 EcP521,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct SanIp(pub Vec<u8>);
85
86impl SanIp {
87 #[must_use]
89 pub fn addr(&self) -> Option<IpAddr> {
90 if let Ok(b) = <[u8; 4]>::try_from(self.0.as_slice()) {
91 Some(IpAddr::V4(Ipv4Addr::from(b)))
92 } else if let Ok(b) = <[u8; 16]>::try_from(self.0.as_slice()) {
93 Some(IpAddr::V6(Ipv6Addr::from(b)))
94 } else {
95 None
96 }
97 }
98}
99
100impl Certificate {
101 pub fn from_der(der: impl Into<Bytes>) -> Result<Self, Error> {
109 let mut der = der.into();
110 let (rest, cert) =
111 X509Certificate::from_der(&der).map_err(|e| Error::InvalidDer(e.to_string()))?;
112 let len = der.len() - rest.len();
113 let version = cert.version().0;
114 if version > 2 {
115 return Err(Error::InvalidDer(format!(
116 "unsupported X.509 version: {version}"
117 )));
118 }
119
120 let spki = cert.tbs_certificate.subject_pki.raw;
121 let spki_start = spki.as_ptr() as usize - der.as_ptr() as usize;
122 let spki = spki_start..spki_start + spki.len();
123 let range = |raw: &[u8]| {
124 let start = raw.as_ptr() as usize - der.as_ptr() as usize;
125 start..start + raw.len()
126 };
127 let subject_raw = range(cert.subject().as_raw());
128 let issuer_raw = range(cert.issuer().as_raw());
129 let key_type = key_type(cert.public_key());
130
131 let subject_cn = cert
132 .subject()
133 .iter_common_name()
134 .next()
135 .and_then(|cn| decode_string(cn.attr_value()));
136
137 let mut san = SubjectAltNames::default();
138 if let Ok(Some(ext)) = cert.subject_alternative_name() {
139 for name in &ext.value.general_names {
140 match name {
141 GeneralName::DNSName(v) => san.dns.push((*v).to_string()),
142 GeneralName::URI(v) => san.uri.push((*v).to_string()),
143 GeneralName::RFC822Name(v) => san.email.push((*v).to_string()),
144 GeneralName::IPAddress(v) => san.ip.push(SanIp(v.to_vec())),
145 _ => {}
146 }
147 }
148 }
149
150 let inner = Inner {
151 spki,
152 subject_raw,
153 issuer_raw,
154 key_type,
155 version: version + 1,
156 serial: serial_hex(cert.raw_serial()),
157 subject: cert.subject().to_string(),
158 issuer: cert.issuer().to_string(),
159 subject_cn,
160 san,
161 not_before: to_system_time(cert.validity().not_before)?,
162 not_after: to_system_time(cert.validity().not_after)?,
163 sha1: OnceLock::new(),
164 sha256: OnceLock::new(),
165 der: Bytes::new(),
166 };
167 drop(cert);
168 der.truncate(len);
169 Ok(Certificate(Arc::new(Inner { der, ..inner })))
170 }
171
172 pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
178 let der = pem_blocks(pem)
179 .next()
180 .ok_or_else(|| Error::InvalidPem("no CERTIFICATE block found".into()))??;
181 Self::from_der(der)
182 }
183
184 pub fn from_pem_multi(pem: &[u8]) -> Result<Vec<Self>, Error> {
189 pem_blocks(pem)
190 .map(|der| der.and_then(Self::from_der))
191 .collect()
192 }
193
194 #[must_use]
196 pub fn der(&self) -> &Bytes {
197 &self.0.der
198 }
199
200 #[must_use]
202 pub fn public_key_der(&self) -> &[u8] {
203 &self.0.der[self.0.spki.clone()]
204 }
205
206 #[must_use]
208 pub fn version(&self) -> u32 {
209 self.0.version
210 }
211
212 #[must_use]
214 pub fn serial_hex(&self) -> &str {
215 &self.0.serial
216 }
217
218 #[must_use]
220 pub fn subject(&self) -> &str {
221 &self.0.subject
222 }
223
224 #[must_use]
226 pub fn issuer(&self) -> &str {
227 &self.0.issuer
228 }
229
230 #[must_use]
232 pub fn subject_der(&self) -> &[u8] {
233 &self.0.der[self.0.subject_raw.clone()]
234 }
235
236 #[must_use]
238 pub fn issuer_der(&self) -> &[u8] {
239 &self.0.der[self.0.issuer_raw.clone()]
240 }
241
242 #[must_use]
245 pub fn is_self_issued(&self) -> bool {
246 self.subject_der() == self.issuer_der()
247 }
248
249 #[must_use]
251 pub fn key_type(&self) -> Option<KeyType> {
252 self.0.key_type
253 }
254
255 #[must_use]
257 pub fn subject_cn(&self) -> Option<&str> {
258 self.0.subject_cn.as_deref()
259 }
260
261 #[must_use]
263 pub fn san(&self) -> &SubjectAltNames {
264 &self.0.san
265 }
266
267 #[must_use]
269 pub fn not_before(&self) -> SystemTime {
270 self.0.not_before
271 }
272
273 #[must_use]
275 pub fn not_after(&self) -> SystemTime {
276 self.0.not_after
277 }
278
279 #[must_use]
281 pub fn sha1(&self) -> &[u8; digest::SHA1_LEN] {
282 self.0.sha1.get_or_init(|| digest::sha1(&self.0.der))
283 }
284
285 #[must_use]
287 pub fn sha256(&self) -> &[u8; digest::SHA256_LEN] {
288 self.0.sha256.get_or_init(|| digest::sha256(&self.0.der))
289 }
290
291 #[must_use]
293 pub fn digest(&self, alg: digest::Algorithm) -> &[u8] {
294 match alg {
295 digest::Algorithm::Sha1 => self.sha1(),
296 digest::Algorithm::Sha256 => self.sha256(),
297 }
298 }
299
300 #[must_use]
302 pub fn thumbprint(&self, alg: digest::Algorithm) -> String {
303 hex::encode(self.digest(alg))
304 }
305
306 #[must_use]
308 pub fn to_pem(&self) -> String {
309 pem_encode("CERTIFICATE", &self.0.der)
310 }
311}
312
313fn key_type(spki: &x509_parser::x509::SubjectPublicKeyInfo<'_>) -> Option<KeyType> {
315 use x509_parser::oid_registry::{
316 OID_EC_P256, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_NIST_EC_P384, OID_NIST_EC_P521,
317 OID_PKCS1_RSAENCRYPTION,
318 };
319
320 let alg = &spki.algorithm;
321 if alg.algorithm == OID_PKCS1_RSAENCRYPTION {
322 Some(KeyType::Rsa)
323 } else if alg.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY {
324 let curve = alg.parameters.as_ref()?.as_oid().ok()?;
325 if curve == OID_EC_P256 {
326 Some(KeyType::EcP256)
327 } else if curve == OID_NIST_EC_P384 {
328 Some(KeyType::EcP384)
329 } else if curve == OID_NIST_EC_P521 {
330 Some(KeyType::EcP521)
331 } else {
332 None
333 }
334 } else {
335 None
336 }
337}
338
339#[must_use]
341pub fn pem_encode(label: &str, der: &[u8]) -> String {
342 let b64 = BASE64.encode(der);
343 let mut pem = String::with_capacity(b64.len() + b64.len() / 64 + 2 * label.len() + 32);
344 pem.push_str("-----BEGIN ");
345 pem.push_str(label);
346 pem.push_str("-----\n");
347 for line in b64.as_bytes().chunks(64) {
348 pem.push_str(std::str::from_utf8(line).unwrap_or_default());
350 pem.push('\n');
351 }
352 pem.push_str("-----END ");
353 pem.push_str(label);
354 pem.push_str("-----\n");
355 pem
356}
357
358impl PartialEq for Certificate {
359 fn eq(&self, other: &Self) -> bool {
360 self.0.der == other.0.der
361 }
362}
363
364impl Eq for Certificate {}
365
366impl fmt::Debug for Certificate {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368 let mut d = f.debug_struct("X509");
369 d.field("serial_number", &self.0.serial)
370 .field("issuer", &self.0.issuer)
371 .field("subject", &self.0.subject);
372 let san = &self.0.san;
373 if !(san.dns.is_empty() && san.uri.is_empty() && san.email.is_empty() && san.ip.is_empty())
374 {
375 let ips: Vec<_> = san
376 .ip
377 .iter()
378 .map(|ip| {
379 ip.addr()
380 .map_or_else(|| hex::encode(&ip.0), |a| a.to_string())
381 })
382 .collect();
383 d.field("san_dns", &san.dns)
384 .field("san_uri", &san.uri)
385 .field("san_email", &san.email)
386 .field("san_ip", &ips);
387 }
388 d.field("not_before", &DisplayTime(self.0.not_before))
389 .field("not_after", &DisplayTime(self.0.not_after))
390 .finish()
391 }
392}
393
394struct DisplayTime(SystemTime);
395
396impl fmt::Debug for DisplayTime {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 let ts = match self.0.duration_since(SystemTime::UNIX_EPOCH) {
399 Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
400 Err(e) => -i64::try_from(e.duration().as_secs()).unwrap_or(i64::MAX),
401 };
402 match ASN1Time::from_timestamp(ts) {
403 Ok(t) => write!(f, "{t}"),
404 Err(_) => write!(f, "{ts}"),
405 }
406 }
407}
408
409fn to_system_time(t: ASN1Time) -> Result<SystemTime, Error> {
410 let ts = t.timestamp();
411 if ts >= 0 {
412 SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(ts.unsigned_abs()))
413 } else {
414 SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(ts.unsigned_abs()))
415 }
416 .ok_or_else(|| Error::InvalidDer(format!("certificate time is out of range: {t}")))
417}
418
419fn serial_hex(raw: &[u8]) -> String {
420 let start = raw.iter().position(|b| *b != 0).unwrap_or(raw.len());
421 if start == raw.len() {
422 "0".to_string()
423 } else {
424 hex::encode_upper(&raw[start..])
425 }
426}
427
428fn decode_string(value: &Any<'_>) -> Option<String> {
430 let data = value.data;
431 match value.tag() {
432 Tag::Utf8String
433 | Tag::PrintableString
434 | Tag::Ia5String
435 | Tag::NumericString
436 | Tag::VisibleString => std::str::from_utf8(data).ok().map(ToString::to_string),
437 Tag::T61String => Some(data.iter().map(|b| char::from(*b)).collect()),
439 Tag::BmpString => {
440 if !data.len().is_multiple_of(2) {
441 return None;
442 }
443 char::decode_utf16(
444 data.as_chunks::<2>()
445 .0
446 .iter()
447 .map(|&c| u16::from_be_bytes(c)),
448 )
449 .collect::<Result<String, _>>()
450 .ok()
451 }
452 Tag::UniversalString => {
453 if !data.len().is_multiple_of(4) {
454 return None;
455 }
456 data.as_chunks::<4>()
457 .0
458 .iter()
459 .map(|&c| char::from_u32(u32::from_be_bytes(c)))
460 .collect()
461 }
462 _ => None,
463 }
464}
465
466fn pem_blocks(pem: &[u8]) -> impl Iterator<Item = Result<Bytes, Error>> + '_ {
468 let text = String::from_utf8_lossy(pem);
469 let mut blocks = Vec::new();
470 let mut rest: &str = &text;
471 while let Some(start) = rest.find(PEM_BEGIN) {
472 let after = &rest[start + PEM_BEGIN.len()..];
473 let Some(label_end) = after.find("-----") else {
474 blocks.push(Err(Error::InvalidPem("malformed BEGIN line".into())));
475 break;
476 };
477 let label = &after[..label_end];
478 let body = &after[label_end + 5..];
479 let end_marker = format!("{PEM_END}{label}-----");
480 let Some(end) = body.find(&end_marker) else {
481 blocks.push(Err(Error::InvalidPem(format!(
482 "missing END line for {label}"
483 ))));
484 break;
485 };
486 if PEM_LABELS.contains(&label) {
487 let b64: Vec<u8> = body[..end]
488 .bytes()
489 .filter(|b| !b.is_ascii_whitespace())
490 .collect();
491 blocks.push(
492 BASE64
493 .decode(&b64)
494 .map(Bytes::from)
495 .map_err(|e| Error::InvalidPem(e.to_string())),
496 );
497 }
498 rest = &body[end + end_marker.len()..];
499 }
500 blocks.into_iter()
501}
502
503#[cfg(test)]
504pub(crate) mod tests {
505 use super::*;
506 #[cfg(all(unix, feature = "openssl"))]
507 use crate::x509::digest::Algorithm;
508
509 use crate::x509::testgen::{self, San};
510
511 fn sample() -> Certificate {
512 let ips = ["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()];
513 let san = San {
514 dns: &["Host.Example"],
515 uri: &["https://Uri"],
516 email: &["[email protected]"],
517 ip: &ips,
518 };
519 testgen::cert("Client1", false, None, &testgen::key(), &san, (-1, 1))
520 }
521
522 #[test]
528 fn metadata() {
529 let c = sample();
530 assert_eq!(c.version(), 3);
531 assert_eq!(c.serial_hex(), "0A0B");
532 assert_eq!(c.subject_cn(), Some("Client1"));
533 assert_eq!(c.subject(), "CN=Client1");
534 assert_eq!(c.issuer(), "CN=Client1");
535 assert!(c.is_self_issued());
536 assert_eq!(c.san().dns, vec!["Host.Example"]);
537 assert_eq!(c.san().uri, vec!["https://Uri"]);
538 assert_eq!(c.san().email, vec!["[email protected]"]);
539 let ips: Vec<_> = c
540 .san()
541 .ip
542 .iter()
543 .map(|i| i.addr().unwrap().to_string())
544 .collect();
545 assert_eq!(ips, vec!["127.0.0.1", "::1"]);
546 assert!(c.not_before() < SystemTime::now());
547 assert!(c.not_before() < c.not_after());
548 let dbg = format!("{c:?}");
549 assert!(dbg.contains("serial_number: \"0A0B\""), "{dbg}");
550 assert!(dbg.contains("127.0.0.1"), "{dbg}");
551 }
552
553 #[cfg(all(unix, feature = "openssl"))]
559 #[test]
560 fn parity_with_openssl() {
561 let c = sample();
562 let x = tls_openssl::x509::X509::from_der(c.der()).unwrap();
563 assert_eq!(c.sha1(), &tls_openssl::sha::sha1(&x.to_der().unwrap()));
564 assert_eq!(
565 c.thumbprint(Algorithm::Sha1),
566 hex::encode(x.digest(tls_openssl::hash::MessageDigest::sha1()).unwrap())
567 );
568 assert_eq!(
569 c.thumbprint(Algorithm::Sha256),
570 hex::encode(
571 x.digest(tls_openssl::hash::MessageDigest::sha256())
572 .unwrap()
573 )
574 );
575 assert_eq!(c.to_pem().as_bytes(), x.to_pem().unwrap());
576 assert_eq!(
577 c.public_key_der(),
578 x.public_key().unwrap().public_key_to_der().unwrap()
579 );
580 let epoch = tls_openssl::asn1::Asn1Time::from_unix(0).unwrap();
581 let d = epoch.diff(x.not_after()).unwrap();
582 let secs = u64::try_from(d.days).unwrap() * 86400 + u64::try_from(d.secs).unwrap();
583 assert_eq!(
584 c.not_after(),
585 SystemTime::UNIX_EPOCH + Duration::from_secs(secs)
586 );
587 }
588
589 #[test]
595 fn pem_roundtrip_and_trailing_data() {
596 let c = sample();
597 let mut doc = b"junk\n".to_vec();
598 doc.extend_from_slice(c.to_pem().as_bytes());
599 doc.extend_from_slice(c.to_pem().as_bytes());
600 assert_eq!(Certificate::from_pem(&doc).unwrap(), c);
601 assert_eq!(Certificate::from_pem_multi(&doc).unwrap().len(), 2);
602 assert_eq!(
603 Certificate::from_pem_multi(b"").unwrap(),
604 Vec::<Certificate>::new()
605 );
606 assert!(matches!(
607 Certificate::from_pem(b"nothing"),
608 Err(Error::InvalidPem(_))
609 ));
610 assert!(matches!(
611 Certificate::from_pem(b"-----BEGIN CERTIFICATE-----\n!!!\n-----END CERTIFICATE-----\n"),
612 Err(Error::InvalidPem(_))
613 ));
614
615 let mut der = c.der().to_vec();
616 let len = der.len();
617 der.extend_from_slice(b"trailing");
618 let c2 = Certificate::from_der(der).unwrap();
619 assert_eq!(c2.der().len(), len);
620 assert_eq!(c2, c);
621 assert!(matches!(
622 Certificate::from_der(&b"garbage"[..]),
623 Err(Error::InvalidDer(_))
624 ));
625 }
626
627 fn sequence(body: &[u8]) -> Vec<u8> {
628 let mut der = vec![0x30];
629 if body.len() < 128 {
630 der.push(u8::try_from(body.len()).unwrap());
631 } else {
632 let len = body.len().to_be_bytes();
633 let start = len.iter().position(|b| *b != 0).unwrap();
634 der.push(0x80 | u8::try_from(len.len() - start).unwrap());
635 der.extend_from_slice(&len[start..]);
636 }
637 der.extend_from_slice(body);
638 der
639 }
640
641 fn edit_tbs(edit: impl FnOnce(&[u8]) -> Vec<u8>) -> Vec<u8> {
642 let cert = Certificate::from_pem(include_bytes!("../../tests/testdata/root.pem")).unwrap();
643 let (_, outer) = Any::from_der(cert.der()).unwrap();
644 let (rest, tbs) = Any::from_der(outer.data).unwrap();
645 let mut body = sequence(&edit(tbs.data));
646 body.extend_from_slice(rest);
647 sequence(&body)
648 }
649
650 #[test]
656 fn unsupported_versions() {
657 for version in [
658 &[0xa0, 3, 2, 1, 3][..],
659 &[0xa0, 7, 2, 5, 0, 0xff, 0xff, 0xff, 0xff][..],
660 ] {
661 let der = edit_tbs(|tbs| {
662 let (rest, _) = Any::from_der(tbs).unwrap();
663 let mut body = version.to_vec();
664 body.extend_from_slice(rest);
665 body
666 });
667 assert!(X509Certificate::from_der(&der).is_ok());
668 assert!(matches!(
669 Certificate::from_der(der),
670 Err(Error::InvalidDer(_))
671 ));
672 }
673 }
674
675 #[test]
681 fn validity_time_range() {
682 for date in [b"15000101000000Z", b"16010101000000Z", b"19600101000000Z"] {
683 let der = edit_tbs(|tbs| {
684 let mut rest = tbs;
685 for _ in 0..4 {
686 rest = Any::from_der(rest).unwrap().0;
687 }
688 let prefix = &tbs[..tbs.len() - rest.len()];
689 let (suffix, validity) = Any::from_der(rest).unwrap();
690 let (not_after, _) = Any::from_der(validity.data).unwrap();
691 let mut times = vec![0x18, 15];
692 times.extend_from_slice(date);
693 times.extend_from_slice(not_after);
694 let mut body = prefix.to_vec();
695 body.extend_from_slice(&sequence(×));
696 body.extend_from_slice(suffix);
697 body
698 });
699 let (_, parsed) = X509Certificate::from_der(&der).unwrap();
700 let seconds = parsed.validity().not_before.timestamp().unsigned_abs();
701 let result = Certificate::from_der(der);
702 if cfg!(windows) && date == b"15000101000000Z" {
703 assert!(matches!(result, Err(Error::InvalidDer(_))));
704 } else {
705 assert_eq!(
706 result.unwrap().not_before(),
707 SystemTime::UNIX_EPOCH - Duration::from_secs(seconds)
708 );
709 }
710 }
711 }
712
713 #[test]
719 fn string_decoding() {
720 let any = |tag, data: &'static [u8]| Any::from_tag_and_data(tag, data);
721 assert_eq!(
722 decode_string(&any(Tag::BmpString, b"\x00A\x00b")).as_deref(),
723 Some("Ab")
724 );
725 assert_eq!(
726 decode_string(&any(Tag::T61String, b"\xe9")).as_deref(),
727 Some("\u{e9}")
728 );
729 assert_eq!(
730 decode_string(&any(Tag::UniversalString, b"\x00\x00\x00A")).as_deref(),
731 Some("A")
732 );
733 assert_eq!(decode_string(&any(Tag::BmpString, b"\x00")), None);
734 assert_eq!(decode_string(&any(Tag::Integer, b"\x01")), None);
735 assert_eq!(SanIp(vec![1, 2, 3]).addr(), None);
736 assert_eq!(serial_hex(&[0, 0]), "0");
737 assert_eq!(
738 to_system_time(ASN1Time::from_timestamp(-10).unwrap()).unwrap(),
739 SystemTime::UNIX_EPOCH - Duration::from_secs(10)
740 );
741 }
742}