x509_parser/extensions/
sct.rs

1//! Certificate transparency [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962)
2//!
3//! Code borrowed from tls-parser crate (file <https://github.com/rusticata/tls-parser/blob/tls-parser-0.11.0/src/certificate_transparency.rs>)
4
5use std::convert::TryInto;
6
7use asn1_rs::FromDer;
8use der_parser::error::BerError;
9use nom::bytes::streaming::take;
10use nom::combinator::{complete, map_parser};
11use nom::multi::{length_data, many1};
12use nom::number::streaming::{be_u16, be_u64, be_u8};
13use nom::IResult;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct SignedCertificateTimestamp<'a> {
17    pub version: CtVersion,
18    pub id: CtLogID<'a>,
19    pub timestamp: u64,
20    pub extensions: CtExtensions<'a>,
21    pub signature: DigitallySigned<'a>,
22}
23
24/// Certificate Transparency Version as defined in
25/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2)
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct CtVersion(pub u8);
28
29impl CtVersion {
30    pub const V1: CtVersion = CtVersion(0);
31}
32
33/// LogID as defined in
34/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2)
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct CtLogID<'a> {
37    pub key_id: &'a [u8; 32],
38}
39
40/// CtExtensions as defined in
41/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2)
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct CtExtensions<'a>(pub &'a [u8]);
44
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct DigitallySigned<'a> {
47    pub hash_alg_id: u8,
48    pub sign_alg_id: u8,
49    pub data: &'a [u8],
50}
51
52/// Parses a list of Signed Certificate Timestamp entries
53pub fn parse_ct_signed_certificate_timestamp_list(
54    i: &[u8],
55) -> IResult<&[u8], Vec<SignedCertificateTimestamp>, BerError> {
56    // use nom::HexDisplay;
57    // eprintln!("{}", i.to_hex(16));
58    let (rem, b) = <&[u8]>::from_der(i)?;
59    let (b, sct_len) = be_u16(b)?;
60    let (_, sct_list) = map_parser(
61        take(sct_len as usize),
62        many1(complete(parse_ct_signed_certificate_timestamp)),
63    )(b)?;
64    Ok((rem, sct_list))
65}
66
67/// Parses as single Signed Certificate Timestamp entry
68pub fn parse_ct_signed_certificate_timestamp(
69    i: &[u8],
70) -> IResult<&[u8], SignedCertificateTimestamp, BerError> {
71    map_parser(
72        length_data(be_u16),
73        parse_ct_signed_certificate_timestamp_content,
74    )(i)
75}
76
77pub(crate) fn parse_ct_signed_certificate_timestamp_content(
78    i: &[u8],
79) -> IResult<&[u8], SignedCertificateTimestamp, BerError> {
80    let (i, version) = be_u8(i)?;
81    let (i, id) = parse_log_id(i)?;
82    let (i, timestamp) = be_u64(i)?;
83    let (i, extensions) = parse_ct_extensions(i)?;
84    let (i, signature) = parse_digitally_signed(i)?;
85    let sct = SignedCertificateTimestamp {
86        version: CtVersion(version),
87        id,
88        timestamp,
89        extensions,
90        signature,
91    };
92    Ok((i, sct))
93}
94
95// Safety: cannot fail, take() returns exactly 32 bytes
96fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, BerError> {
97    let (i, key_id) = take(32usize)(i)?;
98    Ok((
99        i,
100        CtLogID {
101            key_id: key_id
102                .try_into()
103                .expect("take(32) is in sync with key_id size"),
104        },
105    ))
106}
107
108fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions, BerError> {
109    let (i, ext_len) = be_u16(i)?;
110    let (i, ext_data) = take(ext_len as usize)(i)?;
111    Ok((i, CtExtensions(ext_data)))
112}
113
114fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned, BerError> {
115    let (i, hash_alg_id) = be_u8(i)?;
116    let (i, sign_alg_id) = be_u8(i)?;
117    let (i, data) = length_data(be_u16)(i)?;
118    let signed = DigitallySigned {
119        hash_alg_id,
120        sign_alg_id,
121        data,
122    };
123    Ok((i, signed))
124}