1use tracing::warn;
4
5use crate::LibroError;
6use crate::entry::AuditEntry;
7
8pub fn verify_chain(entries: &[AuditEntry]) -> crate::Result<()> {
10 if entries.is_empty() {
11 return Ok(());
12 }
13
14 for (i, entry) in entries.iter().enumerate() {
15 let expected_hash = entry.compute_hash();
16 if entry.hash() != expected_hash {
17 warn!(
18 index = i,
19 hash = entry.hash(),
20 expected = %expected_hash,
21 "entry self-hash verification failed"
22 );
23 return Err(LibroError::IntegrityViolation {
24 index: i,
25 expected: expected_hash,
26 actual: entry.hash().to_owned(),
27 });
28 }
29 if i > 0 && entry.prev_hash() != entries[i - 1].hash() {
30 warn!(
31 index = i,
32 expected = entries[i - 1].hash(),
33 actual = entry.prev_hash(),
34 "chain linkage broken"
35 );
36 return Err(LibroError::IntegrityViolation {
37 index: i,
38 expected: entries[i - 1].hash().to_owned(),
39 actual: entry.prev_hash().to_owned(),
40 });
41 }
42 }
43
44 Ok(())
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::entry::{AuditEntry, EventSeverity};
51
52 #[test]
53 fn verify_valid_chain() {
54 let e1 = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
55 let e2 = AuditEntry::new(
56 EventSeverity::Info,
57 "s",
58 "b",
59 serde_json::json!({}),
60 e1.hash(),
61 );
62 assert!(verify_chain(&[e1, e2]).is_ok());
63 }
64
65 #[test]
66 fn verify_broken_link() {
67 let e1 = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
68 let e2 = AuditEntry::new(
69 EventSeverity::Info,
70 "s",
71 "b",
72 serde_json::json!({}),
73 "wrong",
74 );
75 assert!(verify_chain(&[e1, e2]).is_err());
76 }
77
78 #[test]
79 fn verify_empty() {
80 assert!(verify_chain(&[]).is_ok());
81 }
82
83 #[test]
84 fn verify_tampered_self_hash() {
85 let mut e1 = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
86 e1.corrupt_hash("tampered");
87 let err = verify_chain(&[e1]).unwrap_err();
88 assert!(err.to_string().contains("entry 0"));
89 }
90
91 #[test]
92 fn verify_single_valid_entry() {
93 let e1 = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
94 assert!(verify_chain(&[e1]).is_ok());
95 }
96
97 #[test]
98 fn verify_long_chain() {
99 let mut entries = Vec::new();
100 let first = AuditEntry::new(EventSeverity::Info, "s", "e0", serde_json::json!({}), "");
101 entries.push(first);
102 for i in 1..50 {
103 let prev = entries[i - 1].hash();
104 entries.push(AuditEntry::new(
105 EventSeverity::Info,
106 "s",
107 format!("e{i}"),
108 serde_json::json!({}),
109 prev,
110 ));
111 }
112 assert!(verify_chain(&entries).is_ok());
113
114 entries[25].corrupt_action("hacked");
116 assert!(verify_chain(&entries).is_err());
117 }
118}