1#![forbid(unsafe_code)]
2
3use crate::utils::base64::Base64;
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6use std::fmt::{self, Display};
7use url::Url;
8
9mod cert;
10mod cert_chain;
11pub mod log_list;
12pub(crate) mod signature;
13pub mod store;
14pub mod tiling;
15pub mod tree;
16pub(crate) mod utils;
17pub mod v1;
18mod version;
19
20pub use cert::{Certificate, CertificateError, Fingerprint};
21pub use cert_chain::CertificateChain;
22pub use signature::{HashAlgorithm, SignatureAlgorithm, SignatureValidationError};
23pub use version::Version;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CtLog {
30 config: CtLogConfig,
31 log_id: LogId,
32}
33
34impl CtLog {
35 pub fn new(config: CtLogConfig) -> Self {
36 let log_id = match config.version() {
37 Version::V1 => LogId::V1(v1::LogId(Sha256::digest(&config.key.0).into())),
38 };
39
40 Self { config, log_id }
41 }
42
43 pub fn log_id(&self) -> &LogId {
44 &self.log_id
45 }
46
47 pub fn config(&self) -> &CtLogConfig {
48 &self.config
49 }
50
51 pub fn description(&self) -> &str {
52 &self.config.description
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct CtLogConfig {
59 description: String,
61
62 #[serde(default)]
63 version: Version,
65
66 url: Url,
71
72 key: Base64<Vec<u8>>,
74
75 mmd: u64,
77
78 tile_url: Option<Url>,
80}
81
82impl CtLogConfig {
83 pub fn url(&self) -> &Url {
85 &self.url
86 }
87
88 pub fn fetch_url(&self) -> &Url {
90 self.url()
91 }
92
93 pub fn tile_url(&self) -> &Option<Url> {
95 &self.tile_url
96 }
97
98 pub fn is_tiling(&self) -> bool {
100 self.tile_url.is_some()
101 }
102
103 pub fn version(&self) -> &Version {
105 &self.version
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
110pub enum LogId {
111 V1(v1::LogId),
112}
113
114impl Display for LogId {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 LogId::V1(log_id) => write!(f, "{log_id}"),
118 }
119 }
120}
121
122impl From<v1::LogId> for LogId {
123 fn from(value: v1::LogId) -> Self {
124 Self::V1(value)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130
131 use super::*;
132 use base64::{Engine, prelude::BASE64_STANDARD};
133
134 const ARGON2025H1: &str = "{
135 \"description\": \"Google Argon\",
136 \"url\": \"https://ct.googleapis.com/logs/us1/argon2025h1/\",
137 \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIIKh+WdoqOTblJji4WiH5AltIDUzODyvFKrXCBjw/Rab0/98J4LUh7dOJEY7+66+yCNSICuqRAX+VPnV8R1Fmg==\",
138 \"mmd\": 86400
139 }
140 ";
141
142 const ARGON2025H2: &str = "{
143 \"description\": \"Google Argon\",
144 \"version\": 1,
145 \"url\": \"https://ct.googleapis.com/logs/us1/argon2025h2/\",
146 \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEr+TzlCzfpie1/rJhgxnIITojqKk9VK+8MZoc08HjtsLzD8e5yjsdeWVhIiWCVk6Y6KomKTYeKGBv6xVu93zQug==\",
147 \"mmd\": 86400
148 }
149 ";
150
151 pub(crate) const ARGON2025H1_STH2806: &str = "{
152 \"tree_size\":1425614114,
153 \"timestamp\":1751114416696,
154 \"sha256_root_hash\":\"LHtW79pwJohJF5Yn/tyozEroOnho4u3JAGn7WeHSR54=\",
155 \"tree_head_signature\":\"BAMARzBFAiEAg4w8LlTFKd3KL6lo5Zde9OupHYNN0DDk8U54PenirI4CIHL8ucpkJw5zFLh8UvLA+Zf+f8Ms+tLsVtzHuqnO0qjm\"
156 }";
157
158 pub(crate)const ARGON2025H1_STH2906: &str = "{
159 \"tree_size\":1425633154,
160 \"timestamp\":1751189445313,
161 \"sha256_root_hash\":\"iH90iBSqmtLLTcCwu74RYyJ0rd3oXtLbXlBNqKcJUXA=\",
162 \"tree_head_signature\":\"BAMARjBEAiAA/UmelqZIfpd5vBs0CJZGx8kAqUhNppLX/rBVk15DWwIgbyecvj2CUl4YzAEWEoFmUwL9KkrZBZQcQgSNEFDqIgc=\"
163 }";
164
165 pub(crate) const CERT_CHAIN_GOOGLE_COM: &str = include_str!("../../testdata/google-chain.pem");
166
167 pub(crate) fn get_log_argon2025h1() -> CtLog {
168 let config = serde_json::from_str(ARGON2025H1).unwrap();
169 CtLog::new(config)
170 }
171
172 pub(crate) fn get_log_argon2025h2() -> CtLog {
173 let config = serde_json::from_str(ARGON2025H2).unwrap();
174 CtLog::new(config)
175 }
176
177 #[test]
178 fn ct_log_toml_parse() {
179 let log = get_log_argon2025h1();
180
181 let test_log_id = BASE64_STANDARD
182 .decode("TnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8=")
183 .unwrap();
184
185 let LogId::V1(log_id) = log.log_id();
186 assert_eq!(log_id.0.to_vec(), test_log_id)
187 }
188}