use std::sync::Arc; use base64ct::Encoding; use ct::{ api::{responses::GetEntriesResponseEntry, CtApiClient}, merkle::{ consts::EXTRA_DATA_BASE64_BUFFER_SIZE, types::{MerkleLeafType, Version} }, parsing::entry::{parse_entry_extra_data_precert, parse_entry_extra_data_x509} }; use tokio::task::JoinSet; async fn fetch_entries() -> impl Iterator { let client = Arc::new(CtApiClient::new_with_client( "https://oak.ct.letsencrypt.org/2024h2/ct/v1/get-entries", Arc::new(reqwest::Client::new()) )); let mut join_set: JoinSet<_> = JoinSet::new(); for i in 0..12u64 { let client = client.clone(); join_set.spawn(async move { client .get_log_entries(i * 256, (i + 1) * 256 - 1) .await .expect("Request should succeed") }); } 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 entry in fetch_entries().await { // 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"); 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" ) } } }