1use crate::encoding::STANDARD;
5use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use sha2::{Digest, Sha256};
9
10use crate::canonicalize;
11
12#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
13pub struct SignatureEnvelope {
14 pub algorithm: String,
15 pub signer: String,
16 pub signature: String,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
20pub struct EvidenceIncident {
21 pub label: String,
22 pub started_at: String,
23 #[serde(skip_serializing_if = "Option::is_none", default)]
24 pub ended_at: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none", default)]
26 pub domains: Option<Vec<String>>,
27 #[serde(skip_serializing_if = "Option::is_none", default)]
28 pub description: Option<String>,
29}
30
31#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
32pub struct EvidenceBundle {
33 pub evidence_version: String,
34 pub bundle_id: String,
35 pub trust_domain: String,
36 pub incident: EvidenceIncident,
37 #[serde(skip_serializing_if = "Option::is_none", default)]
38 pub actors: Option<Vec<String>>,
39 pub events: Vec<Value>,
40 pub policy_decisions: Vec<Value>,
41 pub approvals: Vec<Value>,
42 #[serde(skip_serializing_if = "Option::is_none", default)]
43 pub ceremonies: Option<Vec<Value>>,
44 #[serde(skip_serializing_if = "Option::is_none", default)]
45 pub quorum_outcomes: Option<Vec<Value>>,
46 #[serde(skip_serializing_if = "Option::is_none", default)]
47 pub anchors: Option<Vec<EvidenceAnchor>>,
48 #[serde(skip_serializing_if = "Option::is_none", default)]
49 pub encrypted_payload: Option<Value>,
50 #[serde(skip_serializing_if = "Option::is_none", default)]
51 pub level: Option<String>,
52 pub issued_at: String,
53 pub issuer: String,
54 pub signature: SignatureEnvelope,
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
58pub struct EvidenceAnchor {
59 pub kind: String,
60 #[serde(skip_serializing_if = "Option::is_none", default)]
61 pub url: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none", default)]
63 pub inclusion_proof: Option<Value>,
64}
65
66pub fn evidence_signing_bytes(b: &EvidenceBundle) -> [u8; 32] {
67 let mut value = serde_json::to_value(b).unwrap_or(Value::Null);
68 if let Value::Object(map) = &mut value {
69 map.remove("signature");
70 }
71 let canonical = canonicalize(&value).unwrap_or_default();
72 Sha256::digest(canonical.as_bytes()).into()
73}
74
75#[derive(Clone, Debug, Default)]
76pub struct AssembleArgs {
77 pub bundle_id: String,
78 pub trust_domain: String,
79 pub label: String,
80 pub started_at: String,
81 pub ended_at: Option<String>,
82 pub domains: Option<Vec<String>>,
83 pub description: Option<String>,
84 pub actor_filter: Option<Vec<String>>,
85 pub event_type_pattern: Option<String>,
86 pub policy_decisions: Vec<Value>,
87 pub approvals: Vec<Value>,
88 pub ceremonies: Option<Vec<Value>>,
89 pub quorum_outcomes: Option<Vec<Value>>,
90 pub issuer: String,
91 pub private_key: [u8; 32],
92}
93
94#[derive(Debug)]
95pub struct AssembleResult {
96 pub bundle: EvidenceBundle,
97 pub skipped: usize,
98}
99
100pub fn assemble_evidence_bundle(
101 events: &[Value],
102 args: AssembleArgs,
103) -> Result<AssembleResult, String> {
104 let actor_set: Option<std::collections::HashSet<String>> = args
105 .actor_filter
106 .as_ref()
107 .map(|a| a.iter().cloned().collect());
108 let regex = match args.event_type_pattern.as_deref() {
109 Some(p) => Some(regex::Regex::new(p).map_err(|e| format!("type pattern: {}", e))?),
110 None => None,
111 };
112 let mut skipped = 0usize;
113 let mut filtered = Vec::new();
114 for ev in events {
115 let ts = ev.get("timestamp").and_then(|v| v.as_str()).unwrap_or("");
116 if ts < args.started_at.as_str() {
117 skipped += 1;
118 continue;
119 }
120 if let Some(end) = &args.ended_at {
121 if ts > end.as_str() {
122 skipped += 1;
123 continue;
124 }
125 }
126 if let Some(set) = &actor_set {
127 let actor = ev.get("actor_id").and_then(|v| v.as_str()).unwrap_or("");
128 if !set.contains(actor) {
129 skipped += 1;
130 continue;
131 }
132 }
133 if let Some(re) = ®ex {
134 let typ = ev.get("type").and_then(|v| v.as_str()).unwrap_or("");
135 if !re.is_match(typ) {
136 skipped += 1;
137 continue;
138 }
139 }
140 filtered.push(ev.clone());
141 }
142 if filtered.is_empty() {
143 return Err("evidence bundle requires at least one matching event".into());
144 }
145 let mut actors: Vec<String> = filtered
146 .iter()
147 .filter_map(|ev| {
148 ev.get("actor_id")
149 .and_then(|v| v.as_str())
150 .map(str::to_string)
151 })
152 .collect();
153 actors.sort();
154 actors.dedup();
155 let level = highest_level(&filtered);
156
157 let mut bundle = EvidenceBundle {
158 evidence_version: "1".into(),
159 bundle_id: args.bundle_id,
160 trust_domain: args.trust_domain,
161 incident: EvidenceIncident {
162 label: args.label,
163 started_at: args.started_at,
164 ended_at: args.ended_at,
165 domains: args.domains,
166 description: args.description,
167 },
168 actors: Some(actors),
169 events: filtered,
170 policy_decisions: args.policy_decisions,
171 approvals: args.approvals,
172 ceremonies: args.ceremonies,
173 quorum_outcomes: args.quorum_outcomes,
174 anchors: None,
175 encrypted_payload: None,
176 level: Some(level),
177 issued_at: now_iso8601(),
178 issuer: args.issuer.clone(),
179 signature: SignatureEnvelope {
180 algorithm: "ed25519".into(),
181 signer: args.issuer,
182 signature: String::new(),
183 },
184 };
185 let digest = evidence_signing_bytes(&bundle);
186 let signing = SigningKey::from_bytes(&args.private_key);
187 let sig: Signature = signing.sign(&digest);
188 bundle.signature.signature = STANDARD.encode(sig.to_bytes());
189 Ok(AssembleResult { bundle, skipped })
190}
191
192fn highest_level(events: &[Value]) -> String {
193 let order = ["L0", "L1", "L2", "L3", "L4", "L5"];
194 let mut max = 0usize;
195 for ev in events {
196 let lvl = ev.get("level").and_then(|v| v.as_str()).unwrap_or("L0");
197 if let Some(idx) = order.iter().position(|x| *x == lvl) {
198 if idx > max {
199 max = idx;
200 }
201 }
202 }
203 order[max].into()
204}
205
206#[derive(Debug, Default)]
207pub struct VerifyResult {
208 pub ok: bool,
209 pub reason: Option<String>,
210 pub outer_signature_ok: bool,
211 pub events_verified: usize,
212 pub events_skipped: usize,
213}
214
215pub fn verify_evidence_bundle(
216 bundle: &EvidenceBundle,
217 issuer_public_key: &[u8; 32],
218) -> VerifyResult {
219 let mut result = VerifyResult::default();
220 let digest = evidence_signing_bytes(bundle);
221 let sig_bytes = match STANDARD.decode(&bundle.signature.signature) {
222 Ok(b) => b,
223 Err(e) => {
224 result.reason = Some(format!("signature base64: {}", e));
225 return result;
226 }
227 };
228 let sig = match Signature::from_slice(&sig_bytes) {
229 Ok(s) => s,
230 Err(e) => {
231 result.reason = Some(format!("signature parse: {}", e));
232 return result;
233 }
234 };
235 let vk = match VerifyingKey::from_bytes(issuer_public_key) {
236 Ok(v) => v,
237 Err(e) => {
238 result.reason = Some(format!("verifying key: {}", e));
239 return result;
240 }
241 };
242 if vk.verify(&digest, &sig).is_err() {
243 result.reason = Some("outer signature did not verify".into());
244 return result;
245 }
246 result.outer_signature_ok = true;
247 result.ok = true;
248 result
249}
250
251fn now_iso8601() -> String {
252 let secs = std::time::SystemTime::now()
253 .duration_since(std::time::UNIX_EPOCH)
254 .unwrap_or_default()
255 .as_secs() as i64;
256 let (y, m, d, h, mi, s) = secs_to_ymdhms(secs);
257 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", y, m, d, h, mi, s)
258}
259
260fn secs_to_ymdhms(secs: i64) -> (i32, u32, u32, u32, u32, u32) {
261 let days = secs.div_euclid(86_400);
262 let time = secs.rem_euclid(86_400);
263 let hour = (time / 3600) as u32;
264 let minute = ((time % 3600) / 60) as u32;
265 let second = (time % 60) as u32;
266 let z = days + 719_468;
267 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
268 let doe = (z - era * 146_097) as u64;
269 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
270 let y = yoe as i64 + era * 400;
271 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
272 let mp = (5 * doy + 2) / 153;
273 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
274 let m = if mp < 10 {
275 (mp + 3) as u32
276 } else {
277 (mp - 9) as u32
278 };
279 let year = if m <= 2 { y + 1 } else { y };
280 (year as i32, m, d, hour, minute, second)
281}