Skip to main content

kc/
der.rs

1//! Just enough DER to fill in a certificate record's attributes.
2//!
3//! A keychain stores several fields of an X.509 certificate alongside the
4//! certificate itself: the subject and issuer names, the serial number, the
5//! subject key identifier, and a hash of the public key. Every one of them is a
6//! *copy of bytes already in the certificate*, so this module locates fields and
7//! hands back slices. It does not decode, validate, or re-encode anything, and it
8//! is not a general ASN.1 library — that boundary is what keeps it small enough
9//! to be obviously right.
10//!
11//! Field positions come from RFC 5280:
12//!
13//! ```text
14//! Certificate      ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signature }
15//! TBSCertificate   ::= SEQUENCE { [0] version OPTIONAL, serialNumber, signature,
16//!                                 issuer, validity, subject,
17//!                                 subjectPublicKeyInfo, ... [3] extensions }
18//! ```
19
20use crate::error::{Error, Result};
21
22/// A tag-length-value element, with its contents located inside the input.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Tlv {
25    pub tag: u8,
26    /// Offset of the first content byte.
27    pub start: usize,
28    /// Offset one past the last content byte.
29    pub end: usize,
30    /// Offset one past the whole element, header included.
31    pub next: usize,
32}
33
34impl Tlv {
35    pub fn contents<'a>(&self, data: &'a [u8]) -> &'a [u8] {
36        &data[self.start..self.end]
37    }
38
39    /// The element including its tag and length, which is what the keychain
40    /// stores for a name.
41    pub fn element<'a>(&self, data: &'a [u8], header_start: usize) -> &'a [u8] {
42        &data[header_start..self.end]
43    }
44
45    pub fn len(&self) -> usize {
46        self.end - self.start
47    }
48
49    pub fn is_empty(&self) -> bool {
50        self.len() == 0
51    }
52}
53
54/// Universal and context tags this module names.
55pub mod tag {
56    pub const INTEGER: u8 = 0x02;
57    pub const BIT_STRING: u8 = 0x03;
58    pub const OCTET_STRING: u8 = 0x04;
59    pub const OID: u8 = 0x06;
60    pub const SEQUENCE: u8 = 0x30;
61    pub const SET: u8 = 0x31;
62    /// `[0]` constructed: the optional version in a `TBSCertificate`.
63    pub const CONTEXT_0: u8 = 0xa0;
64    /// `[3]` constructed: the extensions.
65    pub const CONTEXT_3: u8 = 0xa3;
66}
67
68/// Read one element at `at`.
69pub fn read(data: &[u8], at: usize) -> Result<Tlv> {
70    let tag = *data
71        .get(at)
72        .ok_or_else(|| malformed("element runs past the end"))?;
73    let first = *data
74        .get(at + 1)
75        .ok_or_else(|| malformed("element has no length"))?;
76
77    let (length, header) = if first < 0x80 {
78        (first as usize, 2)
79    } else {
80        let count = (first & 0x7f) as usize;
81        if count == 0 || count > 4 {
82            return Err(malformed("unsupported DER length encoding"));
83        }
84        let bytes = data
85            .get(at + 2..at + 2 + count)
86            .ok_or_else(|| malformed("truncated DER length"))?;
87        (
88            bytes
89                .iter()
90                .fold(0usize, |value, byte| (value << 8) | *byte as usize),
91            2 + count,
92        )
93    };
94
95    let start = at + header;
96    let end = start
97        .checked_add(length)
98        .ok_or_else(|| malformed("DER length overflows"))?;
99    if end > data.len() {
100        return Err(malformed("DER element extends past the input"));
101    }
102    Ok(Tlv {
103        tag,
104        start,
105        end,
106        next: end,
107    })
108}
109
110/// Read an element and require its tag.
111fn read_tagged(data: &[u8], at: usize, expected: u8) -> Result<Tlv> {
112    let tlv = read(data, at)?;
113    if tlv.tag != expected {
114        return Err(malformed(format!(
115            "expected tag 0x{expected:02x} at offset {at}, found 0x{:02x}",
116            tlv.tag
117        )));
118    }
119    Ok(tlv)
120}
121
122fn malformed(detail: impl Into<String>) -> Error {
123    Error::format(format!("malformed certificate: {}", detail.into()))
124}
125
126/// The parts of a certificate a keychain record stores.
127///
128/// Every field borrows the certificate's own bytes; nothing is re-encoded.
129#[derive(Debug, Clone)]
130pub struct Certificate<'a> {
131    /// `serialNumber`, the INTEGER's content bytes — leading zero included, the
132    /// way the keychain stores it.
133    pub serial_number: &'a [u8],
134    /// `issuer`, the whole `Name` element including its SEQUENCE header.
135    pub issuer: &'a [u8],
136    /// `subject`, likewise.
137    pub subject: &'a [u8],
138    /// The `subjectPublicKey` BIT STRING contents: what `PublicKeyHash` hashes.
139    pub subject_public_key: &'a [u8],
140    /// The `subjectKeyIdentifier` extension's OCTET STRING contents, when present.
141    pub subject_key_identifier: Option<&'a [u8]>,
142    /// The first `commonName` in the subject, for a default label.
143    pub common_name: Option<String>,
144}
145
146/// `id-ce-subjectKeyIdentifier` (2.5.29.14).
147const OID_SUBJECT_KEY_IDENTIFIER: [u8; 3] = [0x55, 0x1d, 0x0e];
148
149/// `id-at-commonName` (2.5.4.3).
150const OID_COMMON_NAME: [u8; 3] = [0x55, 0x04, 0x03];
151
152impl<'a> Certificate<'a> {
153    pub fn parse(data: &'a [u8]) -> Result<Self> {
154        let certificate = read_tagged(data, 0, tag::SEQUENCE)?;
155        let tbs = read_tagged(data, certificate.start, tag::SEQUENCE)?;
156
157        // An explicit version is optional; skip it when present.
158        let mut at = tbs.start;
159        let first = read(data, at)?;
160        if first.tag == tag::CONTEXT_0 {
161            at = first.next;
162        }
163
164        let serial = read_tagged(data, at, tag::INTEGER)?;
165        let signature = read_tagged(data, serial.next, tag::SEQUENCE)?;
166        let issuer_at = signature.next;
167        let issuer = read_tagged(data, issuer_at, tag::SEQUENCE)?;
168        let validity = read_tagged(data, issuer.next, tag::SEQUENCE)?;
169        let subject_at = validity.next;
170        let subject = read_tagged(data, subject_at, tag::SEQUENCE)?;
171        let spki = read_tagged(data, subject.next, tag::SEQUENCE)?;
172
173        // SubjectPublicKeyInfo ::= SEQUENCE { algorithm, subjectPublicKey }
174        let algorithm = read_tagged(data, spki.start, tag::SEQUENCE)?;
175        let public_key = read_tagged(data, algorithm.next, tag::BIT_STRING)?;
176        // The first content byte counts unused bits and is not part of the key.
177        let subject_public_key = data
178            .get(public_key.start + 1..public_key.end)
179            .ok_or_else(|| malformed("empty subject public key"))?;
180
181        Ok(Self {
182            serial_number: serial.contents(data),
183            issuer: issuer.element(data, issuer_at),
184            subject: subject.element(data, subject_at),
185            subject_public_key,
186            subject_key_identifier: subject_key_identifier(data, &tbs, public_key.next)?,
187            common_name: common_name(data, subject.contents(data)),
188        })
189    }
190
191    /// `PublicKeyHash`: SHA-1 of the public key bits.
192    ///
193    /// Of the SubjectPublicKey BIT STRING contents, *not* of the whole
194    /// SubjectPublicKeyInfo — checked against a certificate macOS imported.
195    pub fn public_key_hash(&self) -> [u8; 20] {
196        use sha1::Digest as _;
197        sha1::Sha1::digest(self.subject_public_key).into()
198    }
199}
200
201/// Walk the extensions for a subject key identifier.
202fn subject_key_identifier<'a>(
203    data: &'a [u8],
204    tbs: &Tlv,
205    mut at: usize,
206) -> Result<Option<&'a [u8]>> {
207    // The optional [1], [2] and [3] fields follow the public key.
208    while at < tbs.end {
209        let field = read(data, at)?;
210        if field.tag != tag::CONTEXT_3 {
211            at = field.next;
212            continue;
213        }
214
215        let extensions = read_tagged(data, field.start, tag::SEQUENCE)?;
216        let mut extension_at = extensions.start;
217        while extension_at < extensions.end {
218            let extension = read_tagged(data, extension_at, tag::SEQUENCE)?;
219            let oid = read_tagged(data, extension.start, tag::OID)?;
220            let mut value_at = oid.next;
221            // An optional critical BOOLEAN may sit between the OID and the value.
222            let value = loop {
223                let next = read(data, value_at)?;
224                if next.tag == tag::OCTET_STRING {
225                    break next;
226                }
227                value_at = next.next;
228                if value_at >= extension.end {
229                    return Err(malformed("extension has no value"));
230                }
231            };
232
233            if oid.contents(data) == OID_SUBJECT_KEY_IDENTIFIER {
234                // The value is a DER OCTET STRING wrapping another one.
235                let inner = read_tagged(data, value.start, tag::OCTET_STRING)?;
236                return Ok(Some(inner.contents(data)));
237            }
238            extension_at = extension.next;
239        }
240        return Ok(None);
241    }
242    Ok(None)
243}
244
245/// The first `commonName` attribute value in a `Name`.
246fn common_name(data: &[u8], name_contents: &[u8]) -> Option<String> {
247    // Name ::= SEQUENCE OF RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
248    let base = name_contents.as_ptr() as usize - data.as_ptr() as usize;
249    let mut at = base;
250    let end = base + name_contents.len();
251    while at < end {
252        let rdn = read(data, at).ok()?;
253        if rdn.tag == tag::SET {
254            let mut pair_at = rdn.start;
255            while pair_at < rdn.end {
256                let pair = read(data, pair_at).ok()?;
257                let oid = read(data, pair.start).ok()?;
258                if oid.tag == tag::OID && oid.contents(data) == OID_COMMON_NAME {
259                    let value = read(data, oid.next).ok()?;
260                    return Some(String::from_utf8_lossy(value.contents(data)).into_owned());
261                }
262                pair_at = pair.next;
263            }
264        }
265        at = rdn.next;
266    }
267    None
268}
269
270/// A PKCS#8 `PrivateKeyInfo`, enough of it to describe the key.
271#[derive(Debug, Clone)]
272pub struct PrivateKeyInfo<'a> {
273    /// The algorithm OID's content bytes.
274    pub algorithm_oid: &'a [u8],
275    /// The `privateKey` OCTET STRING contents: an `RSAPrivateKey` for RSA.
276    pub private_key: &'a [u8],
277}
278
279/// `rsaEncryption` (1.2.840.113549.1.1.1).
280pub const OID_RSA_ENCRYPTION: [u8; 9] = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
281
282/// `id-ecPublicKey` (1.2.840.10045.2.1).
283pub const OID_EC_PUBLIC_KEY: [u8; 7] = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
284
285impl<'a> PrivateKeyInfo<'a> {
286    pub fn parse(data: &'a [u8]) -> Result<Self> {
287        let info = read_tagged(data, 0, tag::SEQUENCE)?;
288        let version = read_tagged(data, info.start, tag::INTEGER)?;
289        let algorithm = read_tagged(data, version.next, tag::SEQUENCE)?;
290        let oid = read_tagged(data, algorithm.start, tag::OID)?;
291        let key = read_tagged(data, algorithm.next, tag::OCTET_STRING)?;
292        Ok(Self {
293            algorithm_oid: oid.contents(data),
294            private_key: key.contents(data),
295        })
296    }
297
298    pub fn is_rsa(&self) -> bool {
299        self.algorithm_oid == OID_RSA_ENCRYPTION
300    }
301
302    pub fn is_ec(&self) -> bool {
303        self.algorithm_oid == OID_EC_PUBLIC_KEY
304    }
305
306    /// Key size in bits: the RSA modulus length, for an RSA key.
307    ///
308    /// `RSAPrivateKey ::= SEQUENCE { version, modulus, ... }`
309    pub fn rsa_key_size_in_bits(&self) -> Result<u32> {
310        if !self.is_rsa() {
311            return Err(malformed("key size is only computed for RSA keys"));
312        }
313        let sequence = read_tagged(self.private_key, 0, tag::SEQUENCE)?;
314        let version = read_tagged(self.private_key, sequence.start, tag::INTEGER)?;
315        let modulus = read_tagged(self.private_key, version.next, tag::INTEGER)?;
316        let bytes = modulus.contents(self.private_key);
317        // DER integers are signed, so a leading zero pads a high bit.
318        let significant = bytes
319            .iter()
320            .position(|byte| *byte != 0)
321            .unwrap_or(bytes.len());
322        Ok(((bytes.len() - significant) * 8) as u32)
323    }
324}
325
326/// Decode PEM if the input looks like it, otherwise pass DER through.
327///
328/// Accepts any label, so a certificate, a `PRIVATE KEY` and an `RSA PRIVATE KEY`
329/// all work; the caller checks what it actually got.
330pub fn pem_or_der(data: &[u8]) -> Result<Vec<u8>> {
331    let text = match std::str::from_utf8(data) {
332        Ok(text) if text.contains("-----BEGIN") => text,
333        // Not text, or no PEM header: treat it as DER.
334        _ => return Ok(data.to_vec()),
335    };
336
337    let body: String = text
338        .lines()
339        .skip_while(|line| !line.starts_with("-----BEGIN"))
340        .skip(1)
341        .take_while(|line| !line.starts_with("-----END"))
342        .collect();
343    use base64::Engine as _;
344    base64::engine::general_purpose::STANDARD
345        .decode(body.trim())
346        .map_err(|error| malformed(format!("invalid base64 in PEM: {error}")))
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    /// A self-signed RSA certificate, the one used to check these fields against
354    /// the record macOS wrote when it imported the matching identity.
355    const CERTIFICATE: &str = concat!(
356        "MIICzDCCAbQCCQDjBOPvAqI4rTANBgkqhkiG9w0BAQsFADAoMRkwFwYDVQQDDBBrYyBpZGVudGl0",
357        "eSB0ZXN0MQswCQYDVQQKDAJrYzAeFw0yNjA3MjUxMjAzMTJaFw0zNjA3MjIxMjAzMTJaMCgxGTAX",
358        "BgNVBAMMEGtjIGlkZW50aXR5IHRlc3QxCzAJBgNVBAoMAmtjMIIBIjANBgkqhkiG9w0BAQEFAAOC",
359        "AQ8AMIIBCgKCAQEAovSsY3lsY/7mA8gwXu4KAiEgI2Gv4+nEifdtGQFOMOMPl7EG9mqZo3A7SD6B",
360        "HyZFwGdfkZvP4uUXCM2z54EbV64FdpyFGll96yIgW6nTy7WOHHL3s8myi1uWVWd0hfxYnJ9FRimV",
361        "o4Y6mQK6anZs/WeiUKR+nQYSydYiCYJEzRY7xZVrSDrd2gcxzQ14okhx7VoWKN3pJhpT5Ot6HnvZ",
362        "PUTPpacEEXDNnHSVlOF5wK1rAejp8X7FOgqBfbNKY8WJgPbtOqY5luv72PbBWJ4ueFLc3LlbQOVe",
363        "GFqyIV4JqlADewaHNV4E1ZRHG369fZuHC/3/mWmHbphRBwz4ZyNZ1QIDAQABMA0GCSqGSIb3DQEB",
364        "CwUAA4IBAQBXNRJTBjM45WJb/8yJyBF7VplpoV0UAUdLoEGV4u7yOZ0E4CVixq7WIy0Z443EtFjc",
365        "0j5reSenUntTkDL82YlhRJQW0swfDuapNquBz/HD3GZviPzVboI7hgpimZdKV085iS3OmYP++HM4",
366        "iZWFXA4FqQmZMa8BqArugAroPM2HH3/1ZmskhibcAaXx5zBK2kHAAMo9eFHujEaGyQaHMQKeyUNo",
367        "aQmcoSi099cPIyLD1DJ2G2ue1WSZVqKNEvZ6ueXKLOT9pO8rKlKD3QRCdaV2hpxn4o+6h8c7/o86",
368        "JkwsmuiKjVzhZLc3d0/r9OsTObFwOhBDzb0dyX581TQEOd0l",
369    );
370
371    fn certificate() -> Vec<u8> {
372        use base64::Engine as _;
373        base64::engine::general_purpose::STANDARD
374            .decode(CERTIFICATE)
375            .unwrap()
376    }
377
378    #[test]
379    fn certificate_fields_match_the_record_macos_wrote() {
380        let der = certificate();
381        let parsed = Certificate::parse(&der).unwrap();
382
383        // Every expected value here is what macOS stored in the certificate
384        // record when it imported this identity.
385        assert_eq!(hex::encode(parsed.serial_number), "00e304e3ef02a238ad");
386        assert_eq!(parsed.subject.len(), 42);
387        assert_eq!(parsed.issuer, parsed.subject, "self-signed");
388        assert_eq!(
389            hex::encode(&parsed.subject[..24]),
390            "30283119301706035504030c106b63206964656e74697479"
391        );
392        assert_eq!(
393            hex::encode(parsed.public_key_hash()),
394            "665e69a00dd3a6d68498c89ef3263702a475066d"
395        );
396        assert_eq!(
397            parsed.subject_key_identifier, None,
398            "this certificate has no SKI"
399        );
400        assert_eq!(parsed.common_name.as_deref(), Some("kc identity test"));
401    }
402
403    #[test]
404    fn the_public_key_hash_covers_the_bit_string_not_the_whole_spki() {
405        use sha1::Digest as _;
406        let der = certificate();
407        let parsed = Certificate::parse(&der).unwrap();
408
409        // A 2048-bit RSA key: the BIT STRING holds a 270-byte RSAPublicKey.
410        assert_eq!(parsed.subject_public_key.len(), 270);
411        assert_eq!(
412            parsed.subject_public_key[0], 0x30,
413            "an RSAPublicKey SEQUENCE"
414        );
415        // Hashing the enclosing SubjectPublicKeyInfo gives a different answer,
416        // which is the mistake this pins down.
417        let spki_hash: [u8; 20] = sha1::Sha1::digest(&der[..]).into();
418        assert_ne!(parsed.public_key_hash(), spki_hash);
419    }
420
421    #[test]
422    fn lengths_are_read_in_both_short_and_long_form() {
423        // Short form: one length byte.
424        let short = [0x02u8, 0x01, 0x07];
425        let tlv = read(&short, 0).unwrap();
426        assert_eq!(tlv.tag, tag::INTEGER);
427        assert_eq!(tlv.contents(&short), &[0x07]);
428
429        // Long form: 0x82 means two length bytes follow.
430        let mut long = vec![0x04u8, 0x82, 0x01, 0x00];
431        long.extend(std::iter::repeat_n(0xabu8, 256));
432        let tlv = read(&long, 0).unwrap();
433        assert_eq!(tlv.len(), 256);
434        assert_eq!(tlv.next, 260);
435    }
436
437    #[test]
438    fn malformed_input_is_an_error_not_a_panic() {
439        assert!(read(&[], 0).is_err());
440        assert!(read(&[0x30], 0).is_err());
441        assert!(read(&[0x30, 0x05, 0x00], 0).is_err(), "length past the end");
442        assert!(
443            read(&[0x30, 0x89, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0).is_err(),
444            "9-byte length"
445        );
446        assert!(Certificate::parse(b"not a certificate").is_err());
447        assert!(PrivateKeyInfo::parse(&[0x30, 0x00]).is_err());
448    }
449
450    #[test]
451    fn a_pkcs8_key_that_is_not_a_key_is_rejected() {
452        // Real keys are exercised in `tests/keychain_identity.rs`, against ones
453        // openssl generates; here only the failure paths.
454        assert!(PrivateKeyInfo::parse(&[]).is_err());
455        assert!(PrivateKeyInfo::parse(&[0x30, 0x03, 0x02, 0x01, 0x00]).is_err());
456
457        let certificate = certificate();
458        // A certificate is a SEQUENCE too, but not a PrivateKeyInfo.
459        assert!(PrivateKeyInfo::parse(&certificate).is_err());
460    }
461
462    #[test]
463    fn pem_and_der_are_both_accepted() {
464        let der = certificate();
465        assert_eq!(pem_or_der(&der).unwrap(), der, "DER passes through");
466
467        use base64::Engine as _;
468        let encoded = base64::engine::general_purpose::STANDARD.encode(&der);
469        let pem = format!(
470            "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n",
471            encoded
472                .as_bytes()
473                .chunks(64)
474                .map(|line| String::from_utf8_lossy(line).into_owned())
475                .collect::<Vec<_>>()
476                .join("\n")
477        );
478        assert_eq!(pem_or_der(pem.as_bytes()).unwrap(), der);
479
480        // Junk that claims to be PEM is an error rather than silent garbage.
481        assert!(
482            pem_or_der(b"-----BEGIN CERTIFICATE-----\n!!!\n-----END CERTIFICATE-----").is_err()
483        );
484    }
485}