tlog_tiles/
lib.rs

1// Ported from "mod" (https://pkg.go.dev/golang.org/x/mod)
2// Copyright 2009 The Go Authors
3// Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
4//
5// This ports code from the original Go project "mod" and adapts it to Rust idioms.
6//
7// Modifications and Rust implementation Copyright (c) 2025 Cloudflare, Inc.
8// Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
9
10//! # tlog tiles
11//!
12//! Implementation of the [C2SP tlog-tiles](https://c2sp.org/tlog-tiles) and [C2SP checkpoint](https://c2sp.org/tlog-checkpoint) specifications.
13//!
14//! This file contains code ported from the original project [tlog](https://pkg.go.dev/golang.org/x/mod/sumdb/tlog).
15//!
16//! References:
17//! - [ct_test.go](https://cs.opensource.google/go/x/mod/+/refs/tags/v0.21.0:sumdb/tlog/ct_test.go)
18
19pub mod checkpoint;
20pub mod tile;
21pub mod tlog;
22
23pub use checkpoint::*;
24pub use tile::*;
25pub use tlog::*;
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30    use base64::prelude::*;
31    use serde::{Deserialize, Deserializer};
32    use std::fs::File;
33    use std::io::Read;
34    use std::path::PathBuf;
35    use url::form_urlencoded;
36
37    #[derive(Deserialize)]
38    struct CtTree {
39        #[serde(rename = "tree_size")]
40        size: u64,
41        #[serde(rename = "sha256_root_hash")]
42        hash: Hash,
43    }
44
45    #[derive(Deserialize)]
46    struct CtEntries {
47        entries: Vec<CtEntry>,
48    }
49
50    #[derive(Deserialize)]
51    struct CtEntry {
52        #[serde(rename = "leaf_input", deserialize_with = "from_base64")]
53        data: Vec<u8>,
54    }
55
56    fn from_base64<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
57        let base64 = String::deserialize(d)?;
58        BASE64_STANDARD
59            .decode(&base64)
60            .map_err(serde::de::Error::custom)
61    }
62
63    #[derive(Deserialize)]
64    struct CtRecordProof {
65        #[serde(rename = "audit_path")]
66        proof: Vec<Hash>,
67    }
68
69    #[derive(Deserialize)]
70    struct CtTreeProof {
71        consistency: Vec<Hash>,
72    }
73
74    // Returns vendored HTTP responses from CT logs for use in tests.
75    fn http_get<T: for<'de> Deserialize<'de>>(url: &str) -> T {
76        let basename = &url
77            .rsplit_once('/')
78            .unwrap()
79            .1
80            .to_string()
81            .replace(|c| !char::is_ascii_alphanumeric(&c), "-");
82
83        let path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests/http_get", basename]
84            .iter()
85            .collect();
86        let mut file = File::open(path).expect("Unable to open file");
87        let mut body = String::new();
88
89        file.read_to_string(&mut body).expect("Unable to read file");
90
91        serde_json::from_str(&body).expect("File contained invalid JSON")
92    }
93
94    #[test]
95    fn test_certificate_transparency() -> Result<(), Error> {
96        let root: CtTree = http_get("http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth");
97
98        let leaf: CtEntries = http_get(
99            "http://ct.googleapis.com/logs/argon2020/ct/v1/get-entries?start=10000&end=10000",
100        );
101
102        let hash = tlog::record_hash(&leaf.entries[0].data);
103
104        let url = format!(
105            "http://ct.googleapis.com/logs/argon2020/ct/v1/get-proof-by-hash?tree_size={}&hash={}",
106            root.size,
107            form_urlencoded::byte_serialize(hash.to_string().as_bytes()).collect::<String>()
108        );
109        let rp: CtRecordProof = http_get(&url);
110
111        tlog::check_record(&rp.proof, root.size, root.hash, 10000, hash)?;
112
113        let url = format!(
114        "http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth-consistency?first=3654490&second={}",
115        root.size);
116        let tp: CtTreeProof = http_get(&url);
117
118        let oh = Hash::parse_hash("AuIZ5V6sDUj1vn3Y1K85oOaQ7y+FJJKtyRTl1edIKBQ=")?;
119        tlog::check_tree(&tp.consistency, root.size, root.hash, 3_654_490, oh)?;
120
121        Ok(())
122    }
123}