luct_scanner/report/
generate.rs1use crate::{Report, Scanner, ScannerError, ScannerImpl, SctReport, SthReport};
2use chrono::DateTime;
3use futures::future::join_all;
4use luct_core::{
5 CertificateChain, LogId,
6 store::{AsyncStoreRead, AsyncStoreWrite},
7 v1::{self, SignedCertificateTimestamp},
8};
9use std::sync::Arc;
10use web_time::{SystemTime, UNIX_EPOCH};
11
12impl<S: ScannerImpl> Scanner<S> {
13 pub async fn collect_report_pem(&self, data: &str) -> Result<Report, ScannerError> {
14 let cert_chain = Arc::new(CertificateChain::from_pem_chain(data)?);
15
16 if self.config.validate_cert_chain {
17 cert_chain.verify_chain()?;
18 }
19
20 self.collect_report(cert_chain).await
21 }
22
23 pub async fn collect_report(
25 &self,
26 chain: Arc<CertificateChain>,
27 ) -> Result<Report, ScannerError> {
28 let cert = chain.cert();
29 let cert_fp = cert.fingerprint_sha256();
30
31 let report = match self.report_store.get(cert_fp.clone()).await {
32 Some(report) => {
33 tracing::debug!("Found report for {} in cache", cert_fp.to_string());
34
35 match self.update_report(report, &chain).await {
38 Err(()) => {
39 tracing::info!(
40 "Found an invalid report (likely generated by outdated version). Will generate fresh report"
41 );
42 self.create_report(chain).await
43 }
44 Ok(report) => report,
45 }
46 }
47 None => {
48 tracing::debug!("Could not find report for {} in cache", cert_fp.to_string());
49 self.create_report(chain).await
50 }
51 };
52
53 let report = self.evaluate_policy(report, (self.time_source)());
54 if report.get_error().is_none() {
55 self.report_store.insert(cert_fp, report.clone()).await;
56 }
57
58 Ok(report)
59 }
60
61 async fn create_report(&self, chain: Arc<CertificateChain>) -> Report {
62 let cert = chain.cert();
63
64 let mut report = Report::from(chain.as_ref());
65
66 let embedded_scts = match cert.extract_scts_v1() {
67 Err(err) => {
68 return report
69 .error_description(format!("Failed to parse SCTs from certificate:s {}", err));
70 }
71 Ok(scts) => scts,
72 };
73
74 let sct_reports = join_all(
75 embedded_scts
76 .into_iter()
77 .map(|sct| self.collect_embedded_sct_report(sct, &chain)),
78 )
79 .await;
80
81 report.scts = sct_reports;
82 report
83 }
84
85 async fn collect_embedded_sct_report(
86 &self,
87 sct: SignedCertificateTimestamp,
88 chain: &Arc<CertificateChain>,
89 ) -> SctReport {
90 let now = SystemTime::now();
91 let report = SctReport::new(sct.log_id());
92
93 let Some(log) = self.logs.get(&sct.log_id()) else {
95 return report.error_description("Unknown log id".to_string());
96 };
97 let log_name = log.client().log().description().to_string();
98 let report = report.log_name(log_name);
99
100 if let Err(err) = log.client().log().validate_sct_v1(chain, &sct, true) {
102 return report.error_description(format!("Failed to validate signature: {}", err));
103 };
104 let report = report.signature_validation_time(
105 DateTime::from_timestamp_millis(
106 now.duration_since(UNIX_EPOCH).unwrap().as_millis() as i64
107 )
108 .unwrap()
109 .into(),
110 );
111
112 let fresh_sth = match self.update_fresh_sth(now, log, chain.cert()).await {
114 Ok(sth) => sth,
115 Err(err) => {
116 return report.error_description(format!("Failed to fetch a fresh STH: {}", err));
117 }
118 };
119 let report = report.latest_sth(SthReport::from(&fresh_sth));
120
121 let leaf = match chain.as_leaf_v1(&sct, true) {
122 Err(err) => {
123 return report.error_description(err.to_string());
124 }
125 Ok(leaf) => leaf,
126 };
127
128 let oldest_sth = log.oldest_viable_sth(&sct).await.unwrap_or(fresh_sth);
130 let report = match log.check_sct_inclusion(&sct, &oldest_sth, &leaf).await {
131 Ok(index) => report.index(index),
132 Err(err) => return report.error_description(err.to_string()),
133 };
134
135 report.inclusion_proof(SthReport::from(&oldest_sth))
136 }
137
138 async fn update_report(
139 &self,
140 mut report: Report,
141 chain: &Arc<CertificateChain>,
142 ) -> Result<Report, ()> {
143 let new_sct_reports = join_all(
144 report
145 .scts
146 .drain(..)
147 .map(|sct_report| self.update_sct_report(sct_report, chain)),
148 )
149 .await;
150
151 report.scts = new_sct_reports.into_iter().collect::<Result<_, _>>()?;
152 Ok(report)
153 }
154
155 async fn update_sct_report(
156 &self,
157 report: SctReport,
158 chain: &Arc<CertificateChain>,
159 ) -> Result<SctReport, ()> {
160 let now = SystemTime::now();
161
162 let log_id = v1::LogId::try_from(report.log_id.as_str())?;
164 let log_id = LogId::V1(log_id);
165
166 let Some(log) = self.logs.get(&log_id) else {
167 return Ok(report.error_description("Unknown log id".to_string()));
168 };
169
170 let fresh_sth = match self.update_fresh_sth(now, log, chain.cert()).await {
171 Ok(sth) => sth,
172 Err(err) => {
173 return Ok(
174 report.error_description(format!("Failed to fetch a fresh STH: {}", err))
175 );
176 }
177 };
178
179 Ok(report.latest_sth(SthReport::from(&fresh_sth)))
180 }
181}