1use crate::{Scanner, ScannerImpl, Validated};
2use chrono::{DateTime, Local, TimeDelta};
3use luct_core::{LogId, v1::SignedTreeHead};
4use luct_store::StringStoreValue;
5use serde::{Deserialize, Serialize};
6use web_time::{Duration, UNIX_EPOCH};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct Report {
10 pub(crate) ca_issuer: String,
11 pub(crate) ca_subject: String,
12 pub(crate) cert_issuer: String,
13 pub(crate) cert_subject: String,
14 pub(crate) fingerprint: String,
15 pub(crate) ca_fingerprint: String,
16 pub(crate) not_before: DateTime<Local>,
17 pub(crate) not_after: DateTime<Local>,
18 #[serde(skip_serializing_if = "Vec::is_empty", default)]
20 pub(crate) scts: Vec<SctReport>,
21 #[serde(skip_serializing_if = "Option::is_none", default)]
22 pub(crate) error_description: Option<String>,
23}
24
25impl Report {
26 pub fn get_error(&self) -> Option<String> {
27 self.error_description.clone()
28 }
29
30 fn error_description(mut self, err: String) -> Self {
31 self.error_description = Some(err);
32 self
33 }
34}
35
36impl<S: ScannerImpl> Scanner<S> {
37 pub(crate) fn evaluate_policy(&self, report: Report, current_time: DateTime<Local>) -> Report {
38 let num_expected_scts = match report.not_after - report.not_before {
42 time if time <= TimeDelta::days(180) => 2,
43 _ => 3,
44 };
45
46 let num_scts_from_known_logs = report
49 .scts
50 .iter()
51 .filter(|sct| sct.signature_validation_time.is_some())
53 .count();
54
55 if num_scts_from_known_logs < num_expected_scts {
57 return report.error_description(format!(
58 "Insufficient number of SCTs from known logs. Expected {} but got {}",
59 num_expected_scts, num_scts_from_known_logs
60 ));
61 }
62
63 let mut fresh_inclusion_proofs = 0;
64 let mut old_inclusion_proofs = 0;
65 for sct in report.scts.iter() {
66 if sct.error_description.is_some() {
68 continue;
69 }
70
71 let Some(latest_sth) = &sct.latest_sth else {
73 continue;
75 };
76 if latest_sth.verification_time
77 < current_time - time_delta_from_duration(self.config.sth_freshness_threshold)
78 {
79 continue;
81 }
82
83 let Some(inclusion_proof) = &sct.inclusion_proof else {
85 continue;
87 };
88 if inclusion_proof.verification_time
89 < current_time - time_delta_from_duration(self.config.sth_freshness_threshold)
90 {
91 old_inclusion_proofs += 1;
92 } else {
93 fresh_inclusion_proofs += 1;
94 }
95 }
96
97 if old_inclusion_proofs == 0 && fresh_inclusion_proofs < num_expected_scts {
98 return report.error_description(
99 "Insufficient number of inclusion proofs with fresh sths could be verified!"
100 .to_string(),
101 );
102 }
103
104 report
105 }
106}
107
108fn time_delta_from_duration(duration: Duration) -> TimeDelta {
109 TimeDelta::new(duration.as_secs() as i64, duration.subsec_nanos())
110 .expect("Failed to translate duration into timedelta")
111}
112
113impl StringStoreValue for Report {
114 fn serialize_value(&self) -> String {
115 serde_json::to_string(self).unwrap()
116 }
117
118 fn deserialize_value(value: &str) -> Option<Self> {
119 serde_json::from_str(value).ok()
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct SctReport {
125 log_id: String,
126 #[serde(skip_serializing_if = "Option::is_none", default)]
127 signature_validation_time: Option<DateTime<Local>>,
128 #[serde(skip_serializing_if = "Option::is_none", default)]
129 log_name: Option<String>,
130 #[serde(skip_serializing_if = "Option::is_none", default)]
131 latest_sth: Option<SthReport>,
132 #[serde(skip_serializing_if = "Option::is_none", default)]
133 index: Option<u64>,
134 #[serde(skip_serializing_if = "Option::is_none", default)]
135 inclusion_proof: Option<SthReport>,
136 #[serde(skip_serializing_if = "Option::is_none", default)]
137 error_description: Option<String>,
138}
139
140impl SctReport {
141 pub(crate) fn new(log_id: LogId) -> Self {
142 Self {
143 log_id: log_id.to_string(),
144 signature_validation_time: None,
145 log_name: None,
146 latest_sth: None,
147 index: None,
148 inclusion_proof: None,
149 error_description: None,
150 }
151 }
152
153 pub(crate) fn signature_validation_time(mut self, time: DateTime<Local>) -> Self {
154 self.signature_validation_time = Some(time);
155 self
156 }
157
158 pub(crate) fn log_name(mut self, name: String) -> Self {
159 self.log_name = Some(name);
160 self
161 }
162
163 pub(crate) fn latest_sth(mut self, sth: SthReport) -> Self {
164 self.latest_sth = Some(sth);
165 self
166 }
167
168 pub(crate) fn index(mut self, index: u64) -> Self {
169 self.index = Some(index);
170 self
171 }
172
173 pub(crate) fn inclusion_proof(mut self, sth: SthReport) -> Self {
174 self.inclusion_proof = Some(sth);
175 self
176 }
177
178 pub(crate) fn error_description(mut self, err: String) -> Self {
179 self.error_description = Some(err);
180 self
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct SthReport {
186 height: u64,
187 timestamp: DateTime<Local>,
188 verification_time: DateTime<Local>,
189}
190
191impl From<&Validated<SignedTreeHead>> for SthReport {
192 fn from(value: &Validated<SignedTreeHead>) -> Self {
193 Self {
194 height: value.tree_size(),
195 timestamp: DateTime::from_timestamp_millis(value.timestamp() as i64)
196 .unwrap()
197 .into(),
198 verification_time: DateTime::from_timestamp_millis(
199 value
200 .validated_at()
201 .duration_since(UNIX_EPOCH)
202 .unwrap()
203 .as_millis() as i64,
204 )
205 .unwrap()
206 .into(),
207 }
208 }
209}
210
211