1use crate::error::{Error, Result};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Tlv {
25 pub tag: u8,
26 pub start: usize,
28 pub end: usize,
30 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 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
54pub 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 pub const CONTEXT_0: u8 = 0xa0;
64 pub const CONTEXT_3: u8 = 0xa3;
66}
67
68pub 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
110fn 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#[derive(Debug, Clone)]
130pub struct Certificate<'a> {
131 pub serial_number: &'a [u8],
134 pub issuer: &'a [u8],
136 pub subject: &'a [u8],
138 pub subject_public_key: &'a [u8],
140 pub subject_key_identifier: Option<&'a [u8]>,
142 pub common_name: Option<String>,
144}
145
146const OID_SUBJECT_KEY_IDENTIFIER: [u8; 3] = [0x55, 0x1d, 0x0e];
148
149const 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 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 let algorithm = read_tagged(data, spki.start, tag::SEQUENCE)?;
175 let public_key = read_tagged(data, algorithm.next, tag::BIT_STRING)?;
176 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 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
201fn subject_key_identifier<'a>(
203 data: &'a [u8],
204 tbs: &Tlv,
205 mut at: usize,
206) -> Result<Option<&'a [u8]>> {
207 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 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 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
245fn common_name(data: &[u8], name_contents: &[u8]) -> Option<String> {
247 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#[derive(Debug, Clone)]
272pub struct PrivateKeyInfo<'a> {
273 pub algorithm_oid: &'a [u8],
275 pub private_key: &'a [u8],
277}
278
279pub const OID_RSA_ENCRYPTION: [u8; 9] = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
281
282pub 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 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 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
326pub 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 _ => 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 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 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 assert_eq!(parsed.subject_public_key.len(), 270);
411 assert_eq!(
412 parsed.subject_public_key[0], 0x30,
413 "an RSAPublicKey SEQUENCE"
414 );
415 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 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 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 assert!(PrivateKeyInfo::parse(&[]).is_err());
455 assert!(PrivateKeyInfo::parse(&[0x30, 0x03, 0x02, 0x01, 0x00]).is_err());
456
457 let certificate = certificate();
458 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 assert!(
482 pem_or_der(b"-----BEGIN CERTIFICATE-----\n!!!\n-----END CERTIFICATE-----").is_err()
483 );
484 }
485}