use std::sync::Arc; use base64ct::Encoding; use ct::{ merkle::{ consts::{EXTRA_DATA_BASE64_BUFFER_SIZE, LEAF_BASE64_BUFFER_SIZE}, types::{LogEntryType, MerkleLeafType, Version} }, parsing::entry::{parse_entry_extra_data_precert, parse_entry_extra_data_x509} }; use serde::Deserialize; use tokio::task::JoinSet; #[derive(Debug, Deserialize)] pub struct Entry { leaf_input: String, extra_data: String } #[derive(Debug, Deserialize)] pub struct GetEntriesResponse { entries: Vec } async fn fetch_entries() -> impl Iterator { let client = Arc::new(reqwest::Client::new()); let mut join_set: JoinSet = JoinSet::new(); for i in 0..12u32 { let client = client.clone(); join_set.spawn(async move { client .get("https://oak.ct.letsencrypt.org/2024h2/ct/v1/get-entries") .query(&[("start", i * 256), ("end", (i + 1) * 256 - 1)]) .send() .await .expect("Request to ct log should succeed") .json() .await .expect("Request to ct log should parse properly") }); } join_set .join_all() .await .into_iter() .flat_map(|e| e.entries) } #[tokio::test] async fn test_letsencrypt_2024h2_parsing() { let mut buffer = [0u8; EXTRA_DATA_BASE64_BUFFER_SIZE]; for (i, entry) in fetch_entries().await.enumerate() { // Parse leaf_input buffer[..entry.leaf_input.len()].copy_from_slice(entry.leaf_input.as_bytes()); let parsed = base64ct::Base64::decode_in_place(&mut buffer[..entry.leaf_input.len()]) .expect("leaf_input should parse as base64 properly"); let (input, merkle_tree_leaf) = ct::parsing::entry::parse_merkle_tree_leaf(parsed) .expect("leaf_input should parse properly"); // leaf_input Assertions assert_eq!(input.len(), 0, "All input should be parsed"); let MerkleLeafType::TimeStampedEntry { entry_type, .. } = merkle_tree_leaf.leaf_type else { panic!("Merkle tree leaf should be TimestampedEntry"); }; let Version::V1 = merkle_tree_leaf.version else { panic!("Merkle tree leaf should be Version::V1"); }; // Calculate this before we have to mutably borrow the buffer again to avoid // multiple mutable references let parse_entry_extra_data = match entry_type { ct::merkle::types::LogEntryType::X509Entry(..) => parse_entry_extra_data_x509, ct::merkle::types::LogEntryType::PrecertEntry { .. } => parse_entry_extra_data_precert, }; // Parse extra_data buffer[..entry.extra_data.len()].copy_from_slice(entry.extra_data.as_bytes()); let parsed = base64ct::Base64::decode_in_place(&mut buffer[..entry.extra_data.len()]) .expect("extra_data should parse as base64 properly"); let (input, parsed) = parse_entry_extra_data(parsed).expect("extra_data should parse properly"); // extra_data assertions assert_eq!(input.len(), 0, "All input should be parsed at entry {i}"); match parsed { ct::merkle::types::EntryExtraData::X509Certificate(chain) => assert_ne!(chain.len(), 0, "x509 chain should not be 0 length"), // TODO: Verify main_certificate against the tbs certificate loaded from // leaf_entry ct::merkle::types::EntryExtraData::Precertificate(chain_entry) => assert_ne!( chain_entry.certificate_chain.len(), 0, "precert chain should not be 0 length" ) } } }