Skip to main content

x509/
x509.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3
4#[cfg(feature = "alloc")]
5extern crate alloc;
6#[cfg(feature = "alloc")]
7use alloc::vec::Vec;
8use core::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Error {
12    InvalidDer,
13    InvalidCertificate,
14    InvalidSpki,
15    InvalidPublicKey,
16    Truncated,
17    InvalidOid,
18    InvalidExtension,
19    InvalidValidity,
20    InvalidTime,
21}
22
23impl fmt::Display for Error {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::InvalidDer => f.write_str("invalid DER encoding"),
27            Self::InvalidCertificate => f.write_str("invalid X.509 certificate structure"),
28            Self::InvalidSpki => f.write_str("invalid SubjectPublicKeyInfo"),
29            Self::InvalidPublicKey => f.write_str("invalid public key BIT STRING"),
30            Self::Truncated => f.write_str("truncated DER input"),
31            Self::InvalidOid => f.write_str("invalid OID encoding"),
32            Self::InvalidExtension => f.write_str("invalid extension"),
33            Self::InvalidValidity => f.write_str("invalid validity period"),
34            Self::InvalidTime => f.write_str("invalid time encoding"),
35        }
36    }
37}
38
39#[cfg(feature = "std")]
40impl std::error::Error for Error {}
41
42#[derive(Debug)]
43struct Tlv<'a> {
44    tag: u8,
45    raw: &'a [u8],
46    value: &'a [u8],
47}
48
49fn read_tlv(data: &[u8]) -> Result<Tlv<'_>, Error> {
50    if data.is_empty() {
51        return Err(Error::Truncated);
52    }
53    let tag = data[0];
54    let consumed = 1;
55
56    if data.len() <= consumed {
57        return Err(Error::Truncated);
58    }
59    let len_byte = data[consumed];
60
61    let (len, len_size) = if len_byte & 0x80 == 0 {
62        (len_byte as usize, 1)
63    } else {
64        let num_bytes = (len_byte & 0x7f) as usize;
65        if num_bytes == 0 || num_bytes > core::mem::size_of::<usize>() {
66            return Err(Error::InvalidDer);
67        }
68        if data.len() <= consumed + num_bytes {
69            return Err(Error::Truncated);
70        }
71        if num_bytes > 1 && data[consumed + 1] == 0 {
72            return Err(Error::InvalidDer);
73        }
74        let mut l = 0usize;
75        for i in 0..num_bytes {
76            l = (l << 8) | data[consumed + 1 + i] as usize;
77        }
78        if l < 128 {
79            return Err(Error::InvalidDer);
80        }
81        (l, 1 + num_bytes)
82    };
83
84    let start = consumed + len_size;
85    let end = start.checked_add(len).ok_or(Error::InvalidDer)?;
86    if end > data.len() {
87        return Err(Error::Truncated);
88    }
89
90    Ok(Tlv {
91        tag,
92        raw: &data[..end],
93        value: &data[start..end],
94    })
95}
96
97fn skip_tlv(data: &[u8], offset: &mut usize) -> Result<(), Error> {
98    let tlv = read_tlv(&data[*offset..])?;
99    *offset += tlv.raw.len();
100    Ok(())
101}
102
103fn skip_tag(data: &[u8], offset: &mut usize, expected_tag: u8) -> Result<(), Error> {
104    if *offset >= data.len() || data[*offset] != expected_tag {
105        return Err(Error::InvalidCertificate);
106    }
107    skip_tlv(data, offset)
108}
109
110/// Extract raw public key bytes from a SubjectPublicKeyInfo DER blob.
111///
112/// The SPKI DER contains a BIT STRING that holds the raw key material
113/// (uncompressed point for P-256, 32-byte key for Ed25519, etc.).
114/// This walks the DER structure to locate the BIT STRING and returns
115/// its content (minus the leading unused-bits byte).
116pub fn extract_key_from_spki(spki_der: &[u8]) -> Result<&[u8], Error> {
117    let spki = read_tlv(spki_der)?;
118    if spki.tag != 0x30 {
119        return Err(Error::InvalidSpki);
120    }
121    // Two formats:
122    // Full:  30 [len] 30 [algid_len] ... 03 [key_len] ...
123    // Stripped (webpki-roots): 30 [algid_len] ... 03 [key_len] ...
124    if spki.value.first() == Some(&0x30) {
125        // Full format: skip the outer AlgorithmIdentifier SEQUENCE.
126        let mut offset = 0;
127        skip_tlv(spki.value, &mut offset)?;
128        let inner = &spki.value[offset..];
129        let bs = read_tlv(inner)?;
130        if bs.tag != 0x03 {
131            return Err(Error::InvalidSpki);
132        }
133        if bs.value.is_empty() {
134            return Err(Error::InvalidPublicKey);
135        }
136        Ok(&bs.value[1..])
137    } else {
138        // Stripped format: spki IS the AlgorithmIdentifier; BIT STRING follows.
139        let after = &spki_der[spki.raw.len()..];
140        if after.is_empty() {
141            return Err(Error::InvalidPublicKey);
142        }
143        let bs = read_tlv(after)?;
144        if bs.tag != 0x03 {
145            return Err(Error::InvalidSpki);
146        }
147        if bs.value.is_empty() {
148            return Err(Error::InvalidPublicKey);
149        }
150        Ok(&bs.value[1..])
151    }
152}
153
154/// Extract the SubjectPublicKeyInfo DER from an X.509 certificate.
155///
156/// Returns the raw DER bytes of the `subjectPublicKeyInfo` field inside the
157/// `tbsCertificate` SEQUENCE.
158pub fn extract_spki_from_cert<'a>(cert_der: &'a [u8]) -> Result<&'a [u8], Error> {
159    let fields = walk_tbs(cert_der)?;
160    Ok(fields.spki)
161}
162
163/// Extract raw public key bytes from an X.509 certificate (DER-encoded).
164///
165/// Returns the key material (e.g. uncompressed P-256 point, Ed25519 key
166/// bytes) stripped of the unused-bits byte from the BIT STRING encoding.
167#[cfg(feature = "alloc")]
168pub fn extract_public_key_from_cert(cert_der: &[u8]) -> Result<Vec<u8>, Error> {
169    let spki = extract_spki_from_cert(cert_der)?;
170    let key = extract_key_from_spki(spki)?;
171    Ok(key.to_vec())
172}
173
174// ── OID constants ──────────────────────────────────────────────────────────
175
176/// 2.5.29.17 – Subject Alternative Name
177pub const OID_SAN: &[u8] = &[0x55, 0x1d, 0x11];
178/// 2.5.29.15 – Key Usage
179pub const OID_KEY_USAGE: &[u8] = &[0x55, 0x1d, 0x0f];
180/// 2.5.29.37 – Extended Key Usage
181pub const OID_EKU: &[u8] = &[0x55, 0x1d, 0x25];
182/// 2.5.29.19 – Basic Constraints
183pub const OID_BASIC_CONSTRAINTS: &[u8] = &[0x55, 0x1d, 0x13];
184/// 1.3.6.1.5.5.7.3.1 – serverAuth EKU
185pub const OID_EKU_SERVER_AUTH: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01];
186
187// Signature algorithm OIDs
188/// 1.3.101.112 – id-Ed25519
189pub const OID_ED25519: &[u8] = &[0x2b, 0x65, 0x70];
190/// 1.2.840.10045.4.3.2 – ecdsa-with-SHA256
191pub const OID_ECDSA_SHA256: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02];
192/// 1.2.840.10045.4.3.3 – ecdsa-with-SHA384
193pub const OID_ECDSA_SHA384: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03];
194// EC public key OID (not signature, but used in SPKI)
195pub const OID_EC_PUBLIC_KEY: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
196/// 1.2.840.113549.1.1.11 – sha256WithRSAEncryption
197pub const OID_RSA_SHA256: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b];
198/// 1.2.840.113549.1.1.12 – sha384WithRSAEncryption
199pub const OID_RSA_SHA384: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c];
200/// 1.2.840.113549.1.1.13 – sha512WithRSAEncryption
201pub const OID_RSA_SHA512: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d];
202/// 1.2.840.113549.1.1.10 – id-RSASSA-PSS
203pub const OID_RSA_PSS: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a];
204/// 1.2.840.10045.2.1 – ecPublicKey (algorithm OID in SPKI)
205pub const OID_EC_PUBLIC_KEY_ALG: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
206
207// ── TBS navigation ─────────────────────────────────────────────────────────
208
209/// Fields of the TBSCertificate SEQUENCE returned by `walk_tbs`.
210struct TbsFields<'a> {
211    /// Raw bytes of the issuer Name (SEQUENCE OF SET OF AttributeTypeAndValue).
212    issuer_dn: &'a [u8],
213    /// Raw bytes of the subject Name.
214    subject_dn: &'a [u8],
215    /// Raw bytes of the subjectPublicKeyInfo.
216    spki: &'a [u8],
217}
218
219/// Walk the top-level fields of an X.509 TBSCertificate and return field
220/// references.
221fn walk_tbs(cert_der: &[u8]) -> Result<TbsFields<'_>, Error> {
222    let cert = read_tlv(cert_der)?;
223    if cert.tag != 0x30 {
224        return Err(Error::InvalidCertificate);
225    }
226    let tbs = read_tlv(cert.value)?;
227    if tbs.tag != 0x30 {
228        return Err(Error::InvalidCertificate);
229    }
230    let inner = tbs.value;
231    let mut offset = 0;
232
233    if offset >= inner.len() {
234        return Err(Error::InvalidCertificate);
235    }
236
237    // [0] version (optional, context-specific constructed)
238    if inner[offset] == 0xa0 {
239        skip_tlv(inner, &mut offset)?;
240    }
241
242    // serialNumber (INTEGER)
243    skip_tag(inner, &mut offset, 0x02)?;
244    // signature (AlgorithmIdentifier SEQUENCE)
245    skip_tag(inner, &mut offset, 0x30)?;
246
247    // issuer (Name SEQUENCE)
248    if offset >= inner.len() || inner[offset] != 0x30 {
249        return Err(Error::InvalidCertificate);
250    }
251    let issuer_tlv = read_tlv(&inner[offset..])?;
252    let issuer_dn = &inner[offset..offset + issuer_tlv.raw.len()];
253
254    // Extract issuer raw bytes (including tag+length)
255    offset += issuer_tlv.raw.len();
256
257    // validity (SEQUENCE)
258    skip_tag(inner, &mut offset, 0x30)?;
259
260    // subject (Name SEQUENCE)
261    if offset >= inner.len() || inner[offset] != 0x30 {
262        return Err(Error::InvalidCertificate);
263    }
264    let subject_tlv = read_tlv(&inner[offset..])?;
265    let subject_dn = &inner[offset..offset + subject_tlv.raw.len()];
266    offset += subject_tlv.raw.len();
267
268    // Skip optional context-specific fields: [1] issuerUniqueID,
269    // [2] subjectUniqueID, [3] extensions
270    while offset < inner.len() && (inner[offset] & 0xa0) == 0xa0 {
271        skip_tlv(inner, &mut offset)?;
272    }
273
274    // subjectPublicKeyInfo (SEQUENCE)
275    if offset >= inner.len() || inner[offset] != 0x30 {
276        return Err(Error::InvalidCertificate);
277    }
278    let spki_tlv = read_tlv(&inner[offset..])?;
279    let spki = &inner[offset..offset + spki_tlv.raw.len()];
280
281    Ok(TbsFields {
282        issuer_dn,
283        subject_dn,
284        spki,
285    })
286}
287
288/// Find the value of an extension by OID.
289///
290/// Returns the raw bytes of the extension's `extnValue` (the content inside
291/// the OCTET STRING), or `None` if the extension is not present.
292///
293/// The certificate must contain the `[3] extensions` field.
294pub fn find_extension<'a>(cert_der: &'a [u8], oid: &[u8]) -> Option<&'a [u8]> {
295    let (tbs_inner, extensions_start) = find_extensions_section(cert_der)?;
296    // extensions is EXPLICIT [3], so the wrapper's value is the Extensions SEQUENCE.
297    let wrapper = read_tlv(&tbs_inner[extensions_start..]).ok()?;
298    let ext_seq = read_tlv(wrapper.value).ok()?;
299    find_oid_in_extensions(ext_seq.value, oid)
300}
301
302/// Locate the `[3] extensions` field in the TBSCertificate and return
303/// `(tbs_inner, offset_to_extensions_a3)`.
304fn find_extensions_section(cert_der: &[u8]) -> Option<(&[u8], usize)> {
305    let cert = read_tlv(cert_der).ok()?;
306    if cert.tag != 0x30 {
307        return None;
308    }
309    let tbs = read_tlv(cert.value).ok()?;
310    if tbs.tag != 0x30 {
311        return None;
312    }
313    let inner = tbs.value;
314    let mut offset = 0;
315
316    if offset < inner.len() && inner[offset] == 0xa0 {
317        let _ = skip_tlv(inner, &mut offset);
318    }
319    let _ = skip_tag(inner, &mut offset, 0x02);
320    let _ = skip_tag(inner, &mut offset, 0x30);
321    let _ = skip_tag(inner, &mut offset, 0x30);
322    let _ = skip_tag(inner, &mut offset, 0x30);
323    let _ = skip_tag(inner, &mut offset, 0x30);
324
325    // Skip optional [1] issuerUniqueID, [2] subjectUniqueID (IMPLICIT).
326    while offset < inner.len()
327        && (inner[offset] == 0x81 || inner[offset] == 0x82 || inner[offset] == 0xa1 || inner[offset] == 0xa2)
328    {
329        let _ = skip_tlv(inner, &mut offset);
330    }
331
332    // subjectPublicKeyInfo (SEQUENCE)
333    let _ = skip_tag(inner, &mut offset, 0x30);
334
335    // [3] extensions
336    if offset < inner.len() && inner[offset] == 0xa3 {
337        Some((inner, offset))
338    } else {
339        None
340    }
341}
342
343fn find_oid_in_extensions<'a>(extensions: &'a [u8], oid: &[u8]) -> Option<&'a [u8]> {
344    let mut offset = 0;
345    while offset < extensions.len() {
346        let ext = read_tlv(&extensions[offset..]).ok()?;
347        offset += ext.raw.len();
348        if ext.tag != 0x30 {
349            continue;
350        }
351        let mut off = 0;
352        // extnID (OID)
353        let oid_tlv = read_tlv(&ext.value[off..]).ok()?;
354        if oid_tlv.tag != 0x06 {
355            continue;
356        }
357        off += oid_tlv.raw.len();
358
359        // critical (BOOLEAN, optional, tag 0x01)
360        if off < ext.value.len() && ext.value[off] == 0x01 {
361            let bool_tlv = read_tlv(&ext.value[off..]).ok()?;
362            off += bool_tlv.raw.len();
363        }
364
365        // extnValue (OCTET STRING)
366        if off >= ext.value.len() || ext.value[off] != 0x04 {
367            continue;
368        }
369        let val_tlv = read_tlv(&ext.value[off..]).ok()?;
370
371        if oid_tlv.value == oid {
372            return Some(val_tlv.value);
373        }
374    }
375    None
376}
377
378// ── DN extraction ──────────────────────────────────────────────────────────
379
380/// Extract the raw DER-encoded issuer Distinguished Name from an X.509
381/// certificate.
382///
383/// Returns the content of the issuer Name SEQUENCE (the RDNs), without the
384/// outer SEQUENCE tag and length bytes.
385pub fn extract_issuer_dn(cert_der: &[u8]) -> Result<&[u8], Error> {
386    let fields = walk_tbs(cert_der)?;
387    // fields.issuer_dn includes the outer SEQUENCE TLV; return just the value.
388    let issuer_tlv = read_tlv(fields.issuer_dn)?;
389    Ok(issuer_tlv.value)
390}
391
392/// Extract the raw DER-encoded subject Distinguished Name from an X.509
393/// certificate.
394///
395/// Returns the content of the subject Name SEQUENCE (the RDNs), without the
396/// outer SEQUENCE tag and length bytes.
397pub fn extract_subject_dn(cert_der: &[u8]) -> Result<&[u8], Error> {
398    let fields = walk_tbs(cert_der)?;
399    let subject_tlv = read_tlv(fields.subject_dn)?;
400    Ok(subject_tlv.value)
401}
402
403/// Compare two Distinguished Name byte slices for equality, normalising the
404/// SET/SET-OF structure.
405///
406/// X.509 DNs can encode the same logical set of attributes with different SET
407/// granularity (e.g. a single SET with two attributes vs two SETs with one
408/// attribute each).  This function flattens both DNs into a list of `(OID,
409/// value)` pairs, sorts them, and compares the sorted lists.
410///
411/// Returns `true` if the DNs are semantically equal.
412pub fn dn_equal(a: &[u8], b: &[u8]) -> bool {
413    let pairs_a = flatten_dn(a);
414    let pairs_b = flatten_dn(b);
415    if pairs_a.len() != pairs_b.len() {
416        return false;
417    }
418    for (pa, pb) in pairs_a.iter().zip(pairs_b.iter()) {
419        if pa.0 != pb.0 || pa.1 != pb.1 {
420            return false;
421        }
422    }
423    true
424}
425
426/// Debug helper: format a DN as a string of "OID=value" pairs.
427#[cfg(feature = "alloc")]
428pub fn debug_dn_pairs(dn: &[u8]) -> alloc::string::String {
429    let pairs = flatten_dn(dn);
430    let parts: alloc::vec::Vec<_> = pairs
431        .into_iter()
432        .map(|(oid, val)| {
433            let oid_name = oid_to_name(oid);
434            let val_str = if val.iter().all(|b| b.is_ascii_graphic() || *b == b' ') {
435                alloc::string::String::from_utf8_lossy(val).into_owned()
436            } else {
437                alloc::format!("{:02x?}", val)
438            };
439            alloc::format!("{oid_name}={val_str}")
440        })
441        .collect();
442    parts.join(", ")
443}
444
445fn oid_to_name(oid: &[u8]) -> &'static str {
446    match oid {
447        &[0x55, 0x04, 0x03] => "CN",
448        &[0x55, 0x04, 0x06] => "C",
449        &[0x55, 0x04, 0x07] => "L",
450        &[0x55, 0x04, 0x08] => "ST",
451        &[0x55, 0x04, 0x0a] => "O",
452        &[0x55, 0x04, 0x0b] => "OU",
453        _ => "(unknown OID)",
454    }
455}
456
457/// Flatten a DN content (SETs of SEQUENCEs of AttributeTypeAndValue) into
458/// sorted (OID, value) pairs.
459///
460/// Each element is `(OID bytes, attribute value bytes)`.  The result is
461/// sorted to enable order-independent comparison.
462fn flatten_dn(dn: &[u8]) -> alloc::vec::Vec<(&[u8], &[u8])> {
463    let mut pairs = alloc::vec::Vec::new();
464    let mut offset = 0;
465    while offset < dn.len() && dn[offset] == 0x31 {
466        // Each RDN is a SET (0x31)
467        let rdn = match read_tlv(&dn[offset..]) {
468            Ok(t) => t,
469            Err(_) => break,
470        };
471        offset += rdn.raw.len();
472
473        // Within each RDN, extract SEQUENCEs (AttributeTypeAndValue)
474        let mut rdn_off = 0;
475        while rdn_off < rdn.value.len() && rdn.value[rdn_off] == 0x30 {
476            let attr = match read_tlv(&rdn.value[rdn_off..]) {
477                Ok(t) => t,
478                Err(_) => break,
479            };
480            rdn_off += attr.raw.len();
481
482            // AttributeTypeAndValue ::= SEQUENCE { type OID, value ANY }
483            if let Some((oid, val)) = parse_attribute(attr.value) {
484                pairs.push((oid, val));
485            }
486        }
487    }
488    // Sort by OID then by value for stable comparison
489    pairs.sort_by(|a, b| a.0.cmp(b.0).then(a.1.cmp(b.1)));
490    pairs
491}
492
493/// Parse a single AttributeTypeAndValue SEQUENCE into (OID, value).
494fn parse_attribute(attr_value: &[u8]) -> Option<(&[u8], &[u8])> {
495    let mut off = 0;
496    let oid_tlv = read_tlv(&attr_value[off..]).ok()?;
497    if oid_tlv.tag != 0x06 {
498        return None;
499    }
500    off += oid_tlv.raw.len();
501    if off >= attr_value.len() {
502        return None;
503    }
504    // The value is whatever TLV follows the OID (can be PrintableString,
505    // UTF8String, IA5String, TeletexString, BMPString, etc.)
506    let val_tlv = read_tlv(&attr_value[off..]).ok()?;
507    Some((oid_tlv.value, val_tlv.value))
508}
509
510// ── Signature algorithm ────────────────────────────────────────────────────
511
512/// Extract the signature algorithm OID from an X.509 certificate.
513///
514/// Returns the raw OID bytes from the `signatureAlgorithm` field of the
515/// certificate (NOT the TBSCertificate's inner signature field — those are
516/// the same per RFC 5280 but the outer one is what covers the signed data).
517pub fn extract_signature_algorithm_oid(cert_der: &[u8]) -> Result<&[u8], Error> {
518    let cert = read_tlv(cert_der)?;
519    if cert.tag != 0x30 {
520        return Err(Error::InvalidCertificate);
521    }
522    let tbs = read_tlv(cert.value)?;
523    if tbs.tag != 0x30 {
524        return Err(Error::InvalidCertificate);
525    }
526    // After TBSCertificate comes signatureAlgorithm (SEQUENCE)
527    let sig_alg_offset = tbs.raw.len();
528    if sig_alg_offset >= cert.value.len() {
529        return Err(Error::InvalidCertificate);
530    }
531    let sig_alg = read_tlv(&cert.value[sig_alg_offset..])?;
532    if sig_alg.tag != 0x30 {
533        return Err(Error::InvalidCertificate);
534    }
535    // First element of AlgorithmIdentifier is the OID
536    let oid = read_tlv(sig_alg.value)?;
537    if oid.tag != 0x06 {
538        return Err(Error::InvalidCertificate);
539    }
540    Ok(oid.value)
541}
542
543/// Extract the raw TBSCertificate bytes (tag + length + value) from a
544/// DER-encoded X.509 certificate.
545///
546/// This is the portion of the certificate that is covered by the signature.
547pub fn extract_tbs_cert(cert_der: &[u8]) -> Result<&[u8], Error> {
548    let cert = read_tlv(cert_der)?;
549    if cert.tag != 0x30 {
550        return Err(Error::InvalidCertificate);
551    }
552    let tbs = read_tlv(cert.value)?;
553    if tbs.tag != 0x30 {
554        return Err(Error::InvalidCertificate);
555    }
556    Ok(tbs.raw)
557}
558
559/// Extract the signature value from a DER-encoded X.509 certificate.
560///
561/// Returns the raw signature bytes (the content of the BIT STRING, with the
562/// leading unused-bits byte stripped).
563pub fn extract_signature_value(cert_der: &[u8]) -> Result<&[u8], Error> {
564    let cert = read_tlv(cert_der)?;
565    if cert.tag != 0x30 {
566        return Err(Error::InvalidCertificate);
567    }
568    let tbs = read_tlv(cert.value)?;
569    if tbs.tag != 0x30 {
570        return Err(Error::InvalidCertificate);
571    }
572    let after_tbs = &cert.value[tbs.raw.len()..];
573    let sig_alg = read_tlv(after_tbs)?;
574    if sig_alg.tag != 0x30 {
575        return Err(Error::InvalidCertificate);
576    }
577    let after_sig_alg = &after_tbs[sig_alg.raw.len()..];
578    let sig = read_tlv(after_sig_alg)?;
579    if sig.tag != 0x03 {
580        return Err(Error::InvalidCertificate);
581    }
582    if sig.value.is_empty() {
583        return Err(Error::InvalidCertificate);
584    }
585    Ok(&sig.value[1..])
586}
587
588// ── SAN (Subject Alternative Name) ─────────────────────────────────────────
589
590/// Parse DNS names from the Subject Alternative Name extension.
591///
592/// Returns the raw bytes of each dNSName entry.
593#[cfg(feature = "alloc")]
594pub fn parse_san_dns_names(cert_der: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
595    let san_value = find_extension(cert_der, OID_SAN).ok_or(Error::InvalidExtension)?;
596    let general_names = read_tlv(san_value)?;
597    if general_names.tag != 0x30 {
598        return Err(Error::InvalidExtension);
599    }
600    let mut dns_names = Vec::new();
601    let mut offset = 0;
602    while offset < general_names.value.len() {
603        if general_names.value[offset] == 0x82 {
604            // [2] dNSName (context-specific, implicit, IA5String)
605            let dns = read_tlv(&general_names.value[offset..])?;
606            dns_names.push(dns.value.to_vec());
607            offset += dns.raw.len();
608        } else {
609            skip_tlv(general_names.value, &mut offset)?;
610        }
611    }
612    Ok(dns_names)
613}
614
615/// Check whether any SAN dNSName matches the given server name, without
616/// allocating.
617///
618/// Supports wildcard DNS names (`*.example.com`). Returns `Ok(true)` on
619/// match, `Ok(false)` on no match (or missing SAN extension), `Err` on
620/// parse failure.
621pub fn check_san_dns_name(cert_der: &[u8], server_name: &str) -> Result<bool, Error> {
622    let san_value = match find_extension(cert_der, OID_SAN) {
623        Some(v) => v,
624        None => return Ok(false),
625    };
626    let general_names = read_tlv(san_value)?;
627    if general_names.tag != 0x30 {
628        return Err(Error::InvalidExtension);
629    }
630    let mut offset = 0;
631    while offset < general_names.value.len() {
632        if general_names.value[offset] == 0x82 {
633            let dns = read_tlv(&general_names.value[offset..])?;
634            if dns_name_matches(dns.value, server_name) {
635                return Ok(true);
636            }
637            offset += dns.raw.len();
638        } else {
639            skip_tlv(general_names.value, &mut offset)?;
640        }
641    }
642    Ok(false)
643}
644
645fn dns_name_matches(san_entry: &[u8], server_name: &str) -> bool {
646    let Ok(san_str) = core::str::from_utf8(san_entry) else {
647        return false;
648    };
649    server_name_matches_wildcard(san_str, server_name)
650}
651
652fn server_name_matches_wildcard(dns_name: &str, server_name: &str) -> bool {
653    let dns_name = dns_name.to_ascii_lowercase();
654    let server_name = server_name.to_ascii_lowercase();
655
656    if let Some(rest) = dns_name.strip_prefix("*.") {
657        let Some(dot_pos) = server_name.find('.') else {
658            return false;
659        };
660        let suffix = &server_name[dot_pos..];
661        rest.eq_ignore_ascii_case(suffix)
662            && !server_name[..dot_pos].is_empty()
663            && server_name[dot_pos + 1..].contains('.') == rest.contains('.')
664    } else {
665        dns_name == server_name
666    }
667}
668
669// ── Key Usage ──────────────────────────────────────────────────────────────
670
671/// Key usage bit positions
672pub mod key_usage {
673    pub const DIGITAL_SIGNATURE: u8 = 0;
674    pub const KEY_ENCIPHERMENT: u8 = 2;
675    pub const KEY_CERT_SIGN: u8 = 5;
676}
677
678/// Parse the Key Usage extension and return the raw bit mask as a `u16`.
679///
680/// Returns `None` if the extension is not present.
681pub fn parse_key_usage(cert_der: &[u8]) -> Option<u16> {
682    let ku_value = find_extension(cert_der, OID_KEY_USAGE)?;
683    let bs = read_tlv(ku_value).ok()?;
684    if bs.tag != 0x03 {
685        return None;
686    }
687    let unused = bs.value.first().copied().unwrap_or(0);
688    let bits = &bs.value[1..];
689    let mut mask: u16 = 0;
690    for (i, &byte) in bits.iter().enumerate() {
691        mask |= (byte as u16) << (8 * i);
692    }
693    // Clear the unused bits at the most significant end
694    if unused > 0 {
695        mask &= !(0xffff << (16 - unused as usize));
696    }
697    Some(mask)
698}
699
700// ── Extended Key Usage ─────────────────────────────────────────────────────
701
702/// Check whether the Extended Key Usage extension includes `serverAuth`
703/// (1.3.6.1.5.5.7.3.1).
704///
705/// Returns `None` if the EKU extension is absent, `Some(true)` if serverAuth
706/// is present, `Some(false)` if it is not.
707pub fn has_eku_server_auth(cert_der: &[u8]) -> Option<bool> {
708    let eku_value = find_extension(cert_der, OID_EKU)?;
709    let seq = read_tlv(eku_value).ok()?;
710    if seq.tag != 0x30 {
711        return Some(false);
712    }
713    let mut offset = 0;
714    while offset < seq.value.len() {
715        let oid = read_tlv(&seq.value[offset..]).ok()?;
716        if oid.tag == 0x06 && oid.value == OID_EKU_SERVER_AUTH {
717            return Some(true);
718        }
719        offset += oid.raw.len();
720    }
721    Some(false)
722}
723
724// ── Basic Constraints ──────────────────────────────────────────────────────
725
726/// Check whether the certificate is a CA via the Basic Constraints extension.
727///
728/// Returns `None` if the extension is absent (cert is NOT a CA per RFC 5280),
729/// `Some(true)` if `cA` is TRUE, `Some(false)` if `cA` is FALSE.
730pub fn is_ca(cert_der: &[u8]) -> Option<bool> {
731    let bc_value = find_extension(cert_der, OID_BASIC_CONSTRAINTS)?;
732    let seq = read_tlv(bc_value).ok()?;
733    if seq.tag != 0x30 {
734        return Some(false);
735    }
736    // cA BOOLEAN (tag 0x01, value 0xff for TRUE, 0x00 for FALSE)
737    if seq.value.first() == Some(&0x01) {
738        let bool_tlv = read_tlv(seq.value).ok()?;
739        return Some(bool_tlv.value == [0xff]);
740    }
741    Some(false)
742}
743
744// ── Validity / Time ────────────────────────────────────────────────────────
745
746/// Parsed X.509 time (UTCTime or GeneralizedTime).
747#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748pub struct X509Time {
749    pub year: u16,
750    pub month: u8,
751    pub day: u8,
752    pub hour: u8,
753    pub minute: u8,
754    pub second: u8,
755}
756
757impl X509Time {
758    /// Convert to Unix timestamp (seconds since 1970-01-01 00:00:00 UTC).
759    pub fn to_unix_seconds(self) -> u64 {
760        let days = days_from_civil(self.year as i32, self.month, self.day);
761        let unix_epoch_days = days_from_civil(1970, 1, 1);
762        let day_offset = (days - unix_epoch_days) as u64;
763        day_offset * 86400 + self.hour as u64 * 3600 + self.minute as u64 * 60 + self.second as u64
764    }
765}
766
767/// Parse the validity period from an X.509 certificate.
768///
769/// Returns `(not_before, not_after)` as `X509Time` values.
770pub fn parse_validity(cert_der: &[u8]) -> Result<(X509Time, X509Time), Error> {
771    let cert = read_tlv(cert_der)?;
772    if cert.tag != 0x30 {
773        return Err(Error::InvalidCertificate);
774    }
775    let tbs = read_tlv(cert.value)?;
776    if tbs.tag != 0x30 {
777        return Err(Error::InvalidCertificate);
778    }
779    let inner = tbs.value;
780    let mut offset = 0;
781
782    // version (optional)
783    if offset < inner.len() && inner[offset] == 0xa0 {
784        skip_tlv(inner, &mut offset)?;
785    }
786    skip_tag(inner, &mut offset, 0x02)?; // serialNumber
787    skip_tag(inner, &mut offset, 0x30)?; // signature
788    skip_tag(inner, &mut offset, 0x30)?; // issuer
789
790    // validity (SEQUENCE)
791    if offset >= inner.len() || inner[offset] != 0x30 {
792        return Err(Error::InvalidValidity);
793    }
794    let validity = read_tlv(&inner[offset..])?;
795
796    let mut voff = 0;
797    let not_before = parse_x509_time_element(&validity.value, &mut voff)?;
798    let not_after = parse_x509_time_element(&validity.value, &mut voff)?;
799
800    Ok((not_before, not_after))
801}
802
803fn parse_x509_time_element(data: &[u8], offset: &mut usize) -> Result<X509Time, Error> {
804    // UTCTime (tag 0x17) or GeneralizedTime (tag 0x18)
805    if *offset >= data.len() {
806        return Err(Error::InvalidTime);
807    }
808    let tag = data[*offset];
809    if tag != 0x17 && tag != 0x18 {
810        return Err(Error::InvalidTime);
811    }
812    let tlv = read_tlv(&data[*offset..])?;
813    *offset += tlv.raw.len();
814
815    let bytes = tlv.value;
816
817    if tag == 0x17 {
818        // UTCTime: YYMMDDHHMMSSZ (13 bytes) or YYMMDDHHMMZ (11 bytes)
819        if bytes.len() < 11 || bytes.last() != Some(&b'Z') {
820            return Err(Error::InvalidTime);
821        }
822        let yy = parse_two_digits(bytes, 0)?;
823        let year = if yy >= 50 { 1900 + yy as u16 } else { 2000 + yy as u16 };
824        Ok(X509Time {
825            year,
826            month: parse_two_digits(bytes, 2)?,
827            day: parse_two_digits(bytes, 4)?,
828            hour: parse_two_digits(bytes, 6)?,
829            minute: parse_two_digits(bytes, 8)?,
830            second: if bytes.len() >= 13 {
831                parse_two_digits(bytes, 10)?
832            } else {
833                0
834            },
835        })
836    } else {
837        // GeneralizedTime: YYYYMMDDHHMMSSZ (15 bytes)
838        if bytes.len() < 15 || bytes.last() != Some(&b'Z') {
839            return Err(Error::InvalidTime);
840        }
841        Ok(X509Time {
842            year: parse_four_digits(bytes, 0)?,
843            month: parse_two_digits(bytes, 4)?,
844            day: parse_two_digits(bytes, 6)?,
845            hour: parse_two_digits(bytes, 8)?,
846            minute: parse_two_digits(bytes, 10)?,
847            second: parse_two_digits(bytes, 12)?,
848        })
849    }
850}
851
852fn parse_two_digits(bytes: &[u8], pos: usize) -> Result<u8, Error> {
853    if pos + 1 >= bytes.len() {
854        return Err(Error::InvalidTime);
855    }
856    let hi = digit(bytes[pos])?;
857    let lo = digit(bytes[pos + 1])?;
858    Ok(hi * 10 + lo)
859}
860
861fn parse_four_digits(bytes: &[u8], pos: usize) -> Result<u16, Error> {
862    let hi = parse_two_digits(bytes, pos)? as u16;
863    let lo = parse_two_digits(bytes, pos + 2)? as u16;
864    Ok(hi * 100 + lo)
865}
866
867fn digit(b: u8) -> Result<u8, Error> {
868    if b.is_ascii_digit() {
869        Ok(b - b'0')
870    } else {
871        Err(Error::InvalidTime)
872    }
873}
874
875/// Number of days since 0000-03-01 (proleptic Gregorian).
876///
877/// Based on Howard Hinnant's algorithm.
878fn days_from_civil(y: i32, m: u8, d: u8) -> i32 {
879    let y = y as i32 - i32::from(m <= 2);
880    let era = if y >= 0 { y } else { y - 399 } / 400;
881    let yoe = (y - era * 400) as u32;
882    let doy = (153 * (if m as u32 > 2 { m as u32 - 3 } else { m as u32 + 9 }) + 2) / 5 + d as u32 - 1;
883    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
884    era as i32 * 146097 + doe as i32
885}
886
887// ── SPKI algorithm ─────────────────────────────────────────────────────────
888
889/// Extract the algorithm OID from a SubjectPublicKeyInfo DER blob.
890///
891/// This is the OID inside the AlgorithmIdentifier, e.g. `1.2.840.10045.2.1`
892/// for EC public keys or `1.3.101.112` for Ed25519.
893pub fn extract_spki_algorithm_oid(spki_der: &[u8]) -> Result<&[u8], Error> {
894    let top = read_tlv(spki_der)?;
895    if top.tag != 0x30 {
896        return Err(Error::InvalidSpki);
897    }
898    // If the content starts with 0x30, we have the full SPKI SEQUENCE
899    // wrapping AlgorithmIdentifier + BIT STRING. Unwrap one level.
900    // If it starts with 0x06, we have the AlgorithmIdentifier directly
901    // (e.g. from webpki-roots TrustAnchor format).
902    let alg_id = if top.value.first() == Some(&0x30) {
903        read_tlv(top.value)?
904    } else {
905        top
906    };
907    if alg_id.tag != 0x30 {
908        return Err(Error::InvalidSpki);
909    }
910    let oid = read_tlv(alg_id.value)?;
911    if oid.tag != 0x06 {
912        return Err(Error::InvalidSpki);
913    }
914    Ok(oid.value)
915}
916
917#[cfg(test)]
918mod tests {
919    use super::*;
920
921    fn decode_hex(s: &str) -> Vec<u8> {
922        (0..s.len())
923            .step_by(2)
924            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
925            .collect()
926    }
927
928    #[test]
929    fn parse_empty() {
930        assert_eq!(read_tlv(b"").unwrap_err(), Error::Truncated);
931    }
932
933    #[test]
934    fn parse_truncated_tag() {
935        assert_eq!(read_tlv(b"\x30").unwrap_err(), Error::Truncated);
936    }
937
938    #[test]
939    fn parse_truncated_value() {
940        assert_eq!(read_tlv(b"\x30\x05").unwrap_err(), Error::Truncated);
941    }
942
943    #[test]
944    fn parse_null() {
945        let tlv = read_tlv(b"\x05\x00").unwrap();
946        assert_eq!(tlv.tag, 0x05);
947        assert!(tlv.value.is_empty());
948        assert_eq!(tlv.raw, b"\x05\x00");
949    }
950
951    #[test]
952    fn parse_integer() {
953        let tlv = read_tlv(b"\x02\x03\x01\x00\x01").unwrap();
954        assert_eq!(tlv.tag, 0x02);
955        assert_eq!(tlv.value, b"\x01\x00\x01");
956        assert_eq!(tlv.raw, b"\x02\x03\x01\x00\x01");
957    }
958
959    #[test]
960    fn parse_long_form_length() {
961        let payload = [0x42u8; 256];
962        let mut raw = vec![0x04, 0x82, 0x01, 0x00];
963        raw.extend_from_slice(&payload);
964        let tlv = read_tlv(&raw).unwrap();
965        assert_eq!(tlv.tag, 0x04);
966        assert_eq!(tlv.value.len(), 256);
967    }
968
969    #[test]
970    fn parse_sequence() {
971        let tlv = read_tlv(b"\x30\x06\x02\x01\x01\x02\x01\x02").unwrap();
972        assert_eq!(tlv.tag, 0x30);
973        assert_eq!(tlv.value, b"\x02\x01\x01\x02\x01\x02");
974    }
975
976    #[test]
977    fn spki_roundtrip() {
978        let der = decode_hex(
979            "3059301306072a8648ce3d020106082a8648ce3d030107034200\
980             04deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\
981             deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\
982             deadbeefdeadbeefdeadbeefdeadbeef",
983        );
984        let key = extract_key_from_spki(&der).unwrap();
985        assert_eq!(key.len(), 65);
986        assert_eq!(key[0], 0x04);
987    }
988
989    #[test]
990    fn extract_from_real_cert() {
991        let der = include_bytes!("tests/p256_cert.der");
992        let spki = extract_spki_from_cert(der).unwrap();
993        let key = extract_key_from_spki(spki).unwrap();
994        assert_eq!(key.len(), 65);
995        assert_eq!(key[0], 0x04);
996    }
997
998    #[test]
999    #[cfg(feature = "alloc")]
1000    fn extract_pk_alloc() {
1001        let der = include_bytes!("tests/p256_cert.der");
1002        let pk = extract_public_key_from_cert(der).unwrap();
1003        assert_eq!(pk.len(), 65);
1004        assert_eq!(pk[0], 0x04);
1005    }
1006
1007    #[test]
1008    fn reject_invalid_spki() {
1009        assert_eq!(extract_key_from_spki(b"\x05\x00").unwrap_err(), Error::InvalidSpki);
1010    }
1011
1012    #[test]
1013    fn reject_truncated_cert() {
1014        assert_eq!(extract_spki_from_cert(b"\x30\x03\x02\x01").unwrap_err(), Error::Truncated);
1015    }
1016
1017    #[test]
1018    fn time_parsing_utc_time() {
1019        let utc = b"\x17\x0d230101120000Z";
1020        let mut offset = 0;
1021        let t = parse_x509_time_element(utc, &mut offset).unwrap();
1022        assert_eq!(t.year, 2023);
1023        assert_eq!(t.month, 1);
1024        assert_eq!(t.day, 1);
1025        assert_eq!(t.hour, 12);
1026        assert_eq!(t.minute, 0);
1027        assert_eq!(t.second, 0);
1028        // 2023-01-01 12:00:00 UTC = 1672574400
1029        assert_eq!(t.to_unix_seconds(), 1672574400);
1030    }
1031
1032    #[test]
1033    fn time_parsing_generalized_time() {
1034        let gt = b"\x18\x0f20230101120000Z";
1035        let mut offset = 0;
1036        let t = parse_x509_time_element(gt, &mut offset).unwrap();
1037        assert_eq!(t.year, 2023);
1038        assert_eq!(t.month, 1);
1039        assert_eq!(t.day, 1);
1040        assert_eq!(t.hour, 12);
1041        assert_eq!(t.minute, 0);
1042        assert_eq!(t.second, 0);
1043        assert_eq!(t.to_unix_seconds(), 1672574400);
1044    }
1045
1046    #[test]
1047    fn time_parsing_pre_2000_utc() {
1048        // UTCTime with YY >= 50 => 19YY
1049        let utc = b"\x17\x0d990101000000Z";
1050        let mut offset = 0;
1051        let t = parse_x509_time_element(utc, &mut offset).unwrap();
1052        assert_eq!(t.year, 1999);
1053        assert_eq!(t.month, 1);
1054        assert_eq!(t.day, 1);
1055    }
1056
1057    #[test]
1058    fn validity_from_cert() {
1059        let der = include_bytes!("tests/p256_cert.der");
1060        let (nb, na) = parse_validity(der).unwrap();
1061        assert!(nb.year > 2000);
1062        assert!(na.year > nb.year);
1063    }
1064
1065    #[test]
1066    fn extract_dns_names() {
1067        // A cert with SAN containing "example.com" and "www.example.com"
1068        // For simplicity, test the basic structure
1069        let der = include_bytes!("tests/p256_cert.der");
1070        // This test cert may or may not have SAN; just check it doesn't crash
1071        let _ = parse_san_dns_names(der);
1072    }
1073}