[go: up one dir, main page]

sct 0.7.1

Certificate transparency SCT verification library
Documentation
//! # SCT.rs: SCT verification library
//! This library implements verification of Signed Certificate Timestamps.
//! These are third-party assurances that a particular certificate has
//! been included in a Certificate Transparency log.
//!
//! See RFC6962 for the details of the formats implemented here.
//!
//! It is intended to be useful to libraries which perform certificate
//! validation, OCSP libraries, and TLS libraries.

#![forbid(unsafe_code, unstable_features)]
#![deny(
    trivial_casts,
    trivial_numeric_casts,
    missing_docs,
    unused_import_braces,
    unused_extern_crates,
    unused_qualifications
)]
#![no_std]

extern crate alloc;

use alloc::{vec, vec::Vec};

/// Describes a CT log
///
/// This structure contains some metadata fields not used by the library.
/// Rationale: it makes sense to keep this metadata with the other
/// values for review purposes.
#[derive(Debug)]
pub struct Log<'a> {
    /// The operator's name/description of the log.
    /// This field is not used by the library.
    pub description: &'a str,

    /// The certificate submission url.
    /// This field is not used by the library.
    pub url: &'a str,

    /// Which entity operates the log.
    /// This field is not used by the library.
    pub operated_by: &'a str,

    /// Public key usable for verifying certificates.
    /// TODO: fixme format of this; should be a SPKI
    /// so the `id` is verifiable, but currently is a
    /// raw public key (like, an ECPoint or RSAPublicKey).
    pub key: &'a [u8],

    /// Key hash, which is SHA256 applied to the SPKI
    /// encoding.
    pub id: [u8; 32],

    /// The log's maximum merge delay.
    /// This field is not used by the library.
    pub max_merge_delay: usize,
}

/// How sct.rs reports errors.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
    /// The SCT was somehow misencoded, truncated or otherwise corrupt.
    MalformedSct,

    /// The SCT contained an invalid signature.
    InvalidSignature,

    /// The SCT was signed in the future.  Clock skew?
    TimestampInFuture,

    /// The SCT had a version that this library does not handle.
    UnsupportedSctVersion,

    /// The SCT was refers to an unknown log.
    UnknownLog,
}

impl Error {
    /// Applies a suggested policy for error handling:
    ///
    /// Returns `true` if the error should end processing
    /// for whatever the SCT is attached to (like, abort a TLS
    /// handshake).
    ///
    /// Returns `false` if this error should be a 'soft failure'
    /// -- the SCT is unverifiable with this library and set of
    /// logs.
    pub fn should_be_fatal(&self) -> bool {
        !matches!(self, Error::UnknownLog | Error::UnsupportedSctVersion)
    }
}

fn lookup(logs: &[&Log], id: &[u8]) -> Result<usize, Error> {
    for (i, l) in logs.iter().enumerate() {
        if id == l.id {
            return Ok(i);
        }
    }

    Err(Error::UnknownLog)
}

fn decode_u64(inp: untrusted::Input) -> u64 {
    let b = inp.as_slice_less_safe();
    assert_eq!(b.len(), 8);
    (b[0] as u64) << 56
        | (b[1] as u64) << 48
        | (b[2] as u64) << 40
        | (b[3] as u64) << 32
        | (b[4] as u64) << 24
        | (b[5] as u64) << 16
        | (b[6] as u64) << 8
        | (b[7] as u64)
}

fn decode_u16(inp: untrusted::Input) -> u16 {
    let b = inp.as_slice_less_safe();
    assert_eq!(b.len(), 2);
    (b[0] as u16) << 8 | (b[1] as u16)
}

fn write_u64(v: u64, out: &mut Vec<u8>) {
    out.push((v >> 56) as u8);
    out.push((v >> 48) as u8);
    out.push((v >> 40) as u8);
    out.push((v >> 32) as u8);
    out.push((v >> 24) as u8);
    out.push((v >> 16) as u8);
    out.push((v >> 8) as u8);
    out.push(v as u8);
}

fn write_u24(v: u32, out: &mut Vec<u8>) {
    out.push((v >> 16) as u8);
    out.push((v >> 8) as u8);
    out.push(v as u8);
}

fn write_u16(v: u16, out: &mut Vec<u8>) {
    out.push((v >> 8) as u8);
    out.push(v as u8);
}

struct Sct<'a> {
    log_id: &'a [u8],
    timestamp: u64,
    sig_alg: u16,
    sig: &'a [u8],
    exts: &'a [u8],
}

const ECDSA_SHA256: u16 = 0x0403;
const ECDSA_SHA384: u16 = 0x0503;
const RSA_PKCS1_SHA256: u16 = 0x0401;
const RSA_PKCS1_SHA384: u16 = 0x0501;
const SCT_V1: u8 = 0u8;
const SCT_TIMESTAMP: u8 = 0u8;
const SCT_X509_ENTRY: [u8; 2] = [0, 0];

impl<'a> Sct<'a> {
    fn verify(&self, key: &[u8], cert: &[u8]) -> Result<(), Error> {
        let alg: &dyn ring::signature::VerificationAlgorithm = match self.sig_alg {
            ECDSA_SHA256 => &ring::signature::ECDSA_P256_SHA256_ASN1,
            ECDSA_SHA384 => &ring::signature::ECDSA_P384_SHA384_ASN1,
            RSA_PKCS1_SHA256 => &ring::signature::RSA_PKCS1_2048_8192_SHA256,
            RSA_PKCS1_SHA384 => &ring::signature::RSA_PKCS1_2048_8192_SHA384,
            _ => return Err(Error::InvalidSignature),
        };

        let mut data = vec![SCT_V1, SCT_TIMESTAMP];
        write_u64(self.timestamp, &mut data);
        data.extend_from_slice(&SCT_X509_ENTRY);
        write_u24(cert.len() as u32, &mut data);
        data.extend_from_slice(cert);
        write_u16(self.exts.len() as u16, &mut data);
        data.extend_from_slice(self.exts);

        let key = ring::signature::UnparsedPublicKey::new(alg, key);

        key.verify(&data, self.sig)
            .map_err(|_| Error::InvalidSignature)
    }

    fn parse(enc: &'a [u8]) -> Result<Sct<'a>, Error> {
        let inp = untrusted::Input::from(enc);

        inp.read_all(Error::MalformedSct, |rd| {
            let version = rd.read_byte().map_err(|_| Error::MalformedSct)?;
            if version != 0 {
                return Err(Error::UnsupportedSctVersion);
            }

            let id = rd.read_bytes(32).map_err(|_| Error::MalformedSct)?;
            let timestamp = rd
                .read_bytes(8)
                .map_err(|_| Error::MalformedSct)
                .map(decode_u64)?;

            let ext_len = rd
                .read_bytes(2)
                .map_err(|_| Error::MalformedSct)
                .map(decode_u16)?;
            let exts = rd
                .read_bytes(ext_len as usize)
                .map_err(|_| Error::MalformedSct)?;

            let sig_alg = rd
                .read_bytes(2)
                .map_err(|_| Error::MalformedSct)
                .map(decode_u16)?;
            let sig_len = rd
                .read_bytes(2)
                .map_err(|_| Error::MalformedSct)
                .map(decode_u16)?;
            let sig = rd
                .read_bytes(sig_len as usize)
                .map_err(|_| Error::MalformedSct)?;

            let ret = Sct {
                log_id: id.as_slice_less_safe(),
                timestamp,
                sig_alg,
                sig: sig.as_slice_less_safe(),
                exts: exts.as_slice_less_safe(),
            };

            Ok(ret)
        })
    }
}

/// Verifies that the SCT `sct` (a `SignedCertificateTimestamp` encoding)
/// is a correctly signed timestamp for `cert` (a DER-encoded X.509 end-entity
/// certificate) valid `at_time`.  `logs` describe the CT logs trusted by
/// the caller to sign such an SCT.
///
/// On success, this function returns the log used as an index into `logs`.
/// Otherwise, it returns an `Error`.
pub fn verify_sct(cert: &[u8], sct: &[u8], at_time: u64, logs: &[&Log]) -> Result<usize, Error> {
    let sct = Sct::parse(sct)?;
    let i = lookup(logs, sct.log_id)?;
    let log = logs[i];
    sct.verify(log.key, cert)?;

    if sct.timestamp > at_time {
        return Err(Error::TimestampInFuture);
    }

    Ok(i)
}

#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_generated;
#[cfg(test)]
mod tests_google;