| //! # 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)] |
| |
| /// 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 { |
| match *self { |
| Error::UnknownLog |
| | Error::UnsupportedSCTVersion => false, |
| _ => true |
| } |
| } |
| } |
| |
| 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::new(); |
| data.push(SCT_V1); |
| data.push(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: timestamp, |
| sig_alg: 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_google; |
| #[cfg(test)] |
| mod tests_generated; |
| #[cfg(test)] |
| mod tests; |