sct/
lib.rs

1//! # SCT.rs: SCT verification library
2//! This library implements verification of Signed Certificate Timestamps.
3//! These are third-party assurances that a particular certificate has
4//! been included in a Certificate Transparency log.
5//!
6//! See RFC6962 for the details of the formats implemented here.
7//!
8//! It is intended to be useful to libraries which perform certificate
9//! validation, OCSP libraries, and TLS libraries.
10
11#![forbid(unsafe_code, unstable_features)]
12#![deny(
13    trivial_casts,
14    trivial_numeric_casts,
15    missing_docs,
16    unused_import_braces,
17    unused_extern_crates,
18    unused_qualifications
19)]
20#![no_std]
21
22extern crate alloc;
23
24use alloc::{vec, vec::Vec};
25
26/// Describes a CT log
27///
28/// This structure contains some metadata fields not used by the library.
29/// Rationale: it makes sense to keep this metadata with the other
30/// values for review purposes.
31#[derive(Debug)]
32pub struct Log<'a> {
33    /// The operator's name/description of the log.
34    /// This field is not used by the library.
35    pub description: &'a str,
36
37    /// The certificate submission url.
38    /// This field is not used by the library.
39    pub url: &'a str,
40
41    /// Which entity operates the log.
42    /// This field is not used by the library.
43    pub operated_by: &'a str,
44
45    /// Public key usable for verifying certificates.
46    /// TODO: fixme format of this; should be a SPKI
47    /// so the `id` is verifiable, but currently is a
48    /// raw public key (like, an ECPoint or RSAPublicKey).
49    pub key: &'a [u8],
50
51    /// Key hash, which is SHA256 applied to the SPKI
52    /// encoding.
53    pub id: [u8; 32],
54
55    /// The log's maximum merge delay.
56    /// This field is not used by the library.
57    pub max_merge_delay: usize,
58}
59
60/// How sct.rs reports errors.
61#[derive(Debug, PartialEq, Clone, Copy)]
62pub enum Error {
63    /// The SCT was somehow misencoded, truncated or otherwise corrupt.
64    MalformedSct,
65
66    /// The SCT contained an invalid signature.
67    InvalidSignature,
68
69    /// The SCT was signed in the future.  Clock skew?
70    TimestampInFuture,
71
72    /// The SCT had a version that this library does not handle.
73    UnsupportedSctVersion,
74
75    /// The SCT was refers to an unknown log.
76    UnknownLog,
77}
78
79impl Error {
80    /// Applies a suggested policy for error handling:
81    ///
82    /// Returns `true` if the error should end processing
83    /// for whatever the SCT is attached to (like, abort a TLS
84    /// handshake).
85    ///
86    /// Returns `false` if this error should be a 'soft failure'
87    /// -- the SCT is unverifiable with this library and set of
88    /// logs.
89    pub fn should_be_fatal(&self) -> bool {
90        !matches!(self, Error::UnknownLog | Error::UnsupportedSctVersion)
91    }
92}
93
94fn lookup(logs: &[&Log], id: &[u8]) -> Result<usize, Error> {
95    for (i, l) in logs.iter().enumerate() {
96        if id == l.id {
97            return Ok(i);
98        }
99    }
100
101    Err(Error::UnknownLog)
102}
103
104fn decode_u64(inp: untrusted::Input) -> u64 {
105    let b = inp.as_slice_less_safe();
106    assert_eq!(b.len(), 8);
107    (b[0] as u64) << 56
108        | (b[1] as u64) << 48
109        | (b[2] as u64) << 40
110        | (b[3] as u64) << 32
111        | (b[4] as u64) << 24
112        | (b[5] as u64) << 16
113        | (b[6] as u64) << 8
114        | (b[7] as u64)
115}
116
117fn decode_u16(inp: untrusted::Input) -> u16 {
118    let b = inp.as_slice_less_safe();
119    assert_eq!(b.len(), 2);
120    (b[0] as u16) << 8 | (b[1] as u16)
121}
122
123fn write_u64(v: u64, out: &mut Vec<u8>) {
124    out.push((v >> 56) as u8);
125    out.push((v >> 48) as u8);
126    out.push((v >> 40) as u8);
127    out.push((v >> 32) as u8);
128    out.push((v >> 24) as u8);
129    out.push((v >> 16) as u8);
130    out.push((v >> 8) as u8);
131    out.push(v as u8);
132}
133
134fn write_u24(v: u32, out: &mut Vec<u8>) {
135    out.push((v >> 16) as u8);
136    out.push((v >> 8) as u8);
137    out.push(v as u8);
138}
139
140fn write_u16(v: u16, out: &mut Vec<u8>) {
141    out.push((v >> 8) as u8);
142    out.push(v as u8);
143}
144
145struct Sct<'a> {
146    log_id: &'a [u8],
147    timestamp: u64,
148    sig_alg: u16,
149    sig: &'a [u8],
150    exts: &'a [u8],
151}
152
153const ECDSA_SHA256: u16 = 0x0403;
154const ECDSA_SHA384: u16 = 0x0503;
155const RSA_PKCS1_SHA256: u16 = 0x0401;
156const RSA_PKCS1_SHA384: u16 = 0x0501;
157const SCT_V1: u8 = 0u8;
158const SCT_TIMESTAMP: u8 = 0u8;
159const SCT_X509_ENTRY: [u8; 2] = [0, 0];
160
161impl<'a> Sct<'a> {
162    fn verify(&self, key: &[u8], cert: &[u8]) -> Result<(), Error> {
163        let alg: &dyn ring::signature::VerificationAlgorithm = match self.sig_alg {
164            ECDSA_SHA256 => &ring::signature::ECDSA_P256_SHA256_ASN1,
165            ECDSA_SHA384 => &ring::signature::ECDSA_P384_SHA384_ASN1,
166            RSA_PKCS1_SHA256 => &ring::signature::RSA_PKCS1_2048_8192_SHA256,
167            RSA_PKCS1_SHA384 => &ring::signature::RSA_PKCS1_2048_8192_SHA384,
168            _ => return Err(Error::InvalidSignature),
169        };
170
171        let mut data = vec![SCT_V1, SCT_TIMESTAMP];
172        write_u64(self.timestamp, &mut data);
173        data.extend_from_slice(&SCT_X509_ENTRY);
174        write_u24(cert.len() as u32, &mut data);
175        data.extend_from_slice(cert);
176        write_u16(self.exts.len() as u16, &mut data);
177        data.extend_from_slice(self.exts);
178
179        let key = ring::signature::UnparsedPublicKey::new(alg, key);
180
181        key.verify(&data, self.sig)
182            .map_err(|_| Error::InvalidSignature)
183    }
184
185    fn parse(enc: &'a [u8]) -> Result<Sct<'a>, Error> {
186        let inp = untrusted::Input::from(enc);
187
188        inp.read_all(Error::MalformedSct, |rd| {
189            let version = rd.read_byte().map_err(|_| Error::MalformedSct)?;
190            if version != 0 {
191                return Err(Error::UnsupportedSctVersion);
192            }
193
194            let id = rd.read_bytes(32).map_err(|_| Error::MalformedSct)?;
195            let timestamp = rd
196                .read_bytes(8)
197                .map_err(|_| Error::MalformedSct)
198                .map(decode_u64)?;
199
200            let ext_len = rd
201                .read_bytes(2)
202                .map_err(|_| Error::MalformedSct)
203                .map(decode_u16)?;
204            let exts = rd
205                .read_bytes(ext_len as usize)
206                .map_err(|_| Error::MalformedSct)?;
207
208            let sig_alg = rd
209                .read_bytes(2)
210                .map_err(|_| Error::MalformedSct)
211                .map(decode_u16)?;
212            let sig_len = rd
213                .read_bytes(2)
214                .map_err(|_| Error::MalformedSct)
215                .map(decode_u16)?;
216            let sig = rd
217                .read_bytes(sig_len as usize)
218                .map_err(|_| Error::MalformedSct)?;
219
220            let ret = Sct {
221                log_id: id.as_slice_less_safe(),
222                timestamp,
223                sig_alg,
224                sig: sig.as_slice_less_safe(),
225                exts: exts.as_slice_less_safe(),
226            };
227
228            Ok(ret)
229        })
230    }
231}
232
233/// Verifies that the SCT `sct` (a `SignedCertificateTimestamp` encoding)
234/// is a correctly signed timestamp for `cert` (a DER-encoded X.509 end-entity
235/// certificate) valid `at_time`.  `logs` describe the CT logs trusted by
236/// the caller to sign such an SCT.
237///
238/// On success, this function returns the log used as an index into `logs`.
239/// Otherwise, it returns an `Error`.
240pub fn verify_sct(cert: &[u8], sct: &[u8], at_time: u64, logs: &[&Log]) -> Result<usize, Error> {
241    let sct = Sct::parse(sct)?;
242    let i = lookup(logs, sct.log_id)?;
243    let log = logs[i];
244    sct.verify(log.key, cert)?;
245
246    if sct.timestamp > at_time {
247        return Err(Error::TimestampInFuture);
248    }
249
250    Ok(i)
251}
252
253#[cfg(test)]
254mod tests;
255#[cfg(test)]
256mod tests_generated;
257#[cfg(test)]
258mod tests_google;