1#![allow(deprecated)]
3
4use crate::crypto::{hash_bytes, KeyPair};
5use crate::proof::{Action, IntegrityProof};
6use serde::{Deserialize, Serialize};
7use std::fs::{File, OpenOptions};
8use std::io::{BufReader, BufWriter};
9use std::path::{Path, PathBuf};
10use thiserror::Error;
11
12#[derive(Debug, Error)]
13pub enum AuditError {
14 #[error("IO error: {0}")]
15 IoError(#[from] std::io::Error),
16
17 #[error("Serialization error: {0}")]
18 SerializationError(#[from] serde_json::Error),
19
20 #[error(
21 "Chain integrity broken at index {index}: expected prev_hash {expected:?}, found {found:?}"
22 )]
23 BrokenChain {
24 index: usize,
25 expected: [u8; 32],
26 found: [u8; 32],
27 },
28
29 #[error("Invalid signature at index {0}")]
30 InvalidSignature(usize),
31
32 #[error("Crypto error: {0}")]
33 CryptoError(#[from] crate::crypto::CryptoError),
34}
35
36pub type Result<T> = std::result::Result<T, AuditError>;
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40pub enum Decision {
41 Approved,
42 Denied { reason: String },
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct AuditEntry {
51 pub index: u64,
53
54 pub timestamp: u64,
56
57 pub action: Action,
59
60 pub proof: IntegrityProof,
62
63 pub decision: Decision,
65
66 pub prev_hash: [u8; 32],
68
69 pub current_hash: [u8; 32],
71
72 pub signature: Vec<u8>,
74}
75
76impl AuditEntry {
77 pub fn compute_hash(&self) -> [u8; 32] {
79 let mut data = Vec::new();
80 data.extend_from_slice(&self.index.to_le_bytes());
81 data.extend_from_slice(&self.timestamp.to_le_bytes());
82 data.extend_from_slice(&self.action.hash());
83 data.extend_from_slice(&self.proof.action_hash);
84 data.extend_from_slice(&self.prev_hash);
85
86 hash_bytes(&data)
87 }
88
89 fn signing_data(&self) -> Vec<u8> {
91 serde_json::to_vec(&(
92 self.index,
93 self.timestamp,
94 &self.action,
95 &self.proof,
96 &self.decision,
97 &self.prev_hash,
98 &self.current_hash,
99 ))
100 .unwrap()
101 }
102}
103
104pub struct AuditLog {
109 entries: Vec<AuditEntry>,
110 storage_path: Option<PathBuf>,
111 keypair: KeyPair,
112}
113
114impl AuditLog {
115 pub fn new(keypair: KeyPair) -> Result<Self> {
117 Ok(AuditLog {
118 entries: Vec::new(),
119 storage_path: None,
120 keypair,
121 })
122 }
123
124 pub fn with_storage(keypair: KeyPair, path: impl AsRef<Path>) -> Result<Self> {
126 let path = path.as_ref().to_path_buf();
127
128 let entries = if path.exists() {
130 Self::load_from_file(&path)?
131 } else {
132 Vec::new()
133 };
134
135 Ok(AuditLog {
136 entries,
137 storage_path: Some(path),
138 keypair,
139 })
140 }
141
142 pub fn append(
144 &mut self,
145 action: Action,
146 proof: IntegrityProof,
147 decision: Decision,
148 ) -> Result<()> {
149 let prev_hash = self
151 .entries
152 .last()
153 .map(|e| e.current_hash)
154 .unwrap_or([0u8; 32]); let index = self.entries.len() as u64;
157 let timestamp = chrono::Utc::now().timestamp() as u64;
158
159 let mut entry = AuditEntry {
161 index,
162 timestamp,
163 action,
164 proof,
165 decision,
166 prev_hash,
167 current_hash: [0u8; 32],
168 signature: Vec::new(),
169 };
170
171 entry.current_hash = entry.compute_hash();
173
174 let signing_data = entry.signing_data();
176 entry.signature = self.keypair.sign(&signing_data)?;
177
178 self.entries.push(entry.clone());
180
181 if let Some(path) = &self.storage_path {
183 self.append_to_file(path, &entry)?;
184 }
185
186 Ok(())
187 }
188
189 pub fn verify_chain(&self) -> Result<()> {
191 for i in 1..self.entries.len() {
192 let prev = &self.entries[i - 1];
193 let curr = &self.entries[i];
194
195 if curr.prev_hash != prev.current_hash {
197 return Err(AuditError::BrokenChain {
198 index: i,
199 expected: prev.current_hash,
200 found: curr.prev_hash,
201 });
202 }
203
204 let expected_hash = curr.compute_hash();
206 if curr.current_hash != expected_hash {
207 return Err(AuditError::BrokenChain {
208 index: i,
209 expected: expected_hash,
210 found: curr.current_hash,
211 });
212 }
213
214 let signing_data = curr.signing_data();
216 self.keypair
217 .verify(&signing_data, &curr.signature)
218 .map_err(|_| AuditError::InvalidSignature(i))?;
219 }
220
221 Ok(())
222 }
223
224 pub fn entries(&self) -> &[AuditEntry] {
226 &self.entries
227 }
228
229 pub fn len(&self) -> usize {
231 self.entries.len()
232 }
233
234 pub fn is_empty(&self) -> bool {
236 self.entries.is_empty()
237 }
238
239 fn load_from_file(path: &Path) -> Result<Vec<AuditEntry>> {
241 let file = File::open(path)?;
242 let reader = BufReader::new(file);
243 let entries: Vec<AuditEntry> = serde_json::from_reader(reader)?;
244 Ok(entries)
245 }
246
247 fn append_to_file(&self, path: &Path, _entry: &AuditEntry) -> Result<()> {
249 let file = OpenOptions::new()
251 .create(true)
252 .write(true)
253 .truncate(true)
254 .open(path)?;
255
256 let writer = BufWriter::new(file);
257 serde_json::to_writer_pretty(writer, &self.entries)?;
258
259 Ok(())
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::proof::{Action, ActionType, VerificationStatus};
267
268 fn create_test_proof() -> IntegrityProof {
269 IntegrityProof {
270 nonce: [1u8; 32],
271 timestamp: 1000,
272 ttl: 60,
273 action_hash: [2u8; 32],
274 action_type: ActionType::Delete,
275 capsule_hash: "test_hash".into(),
276 status: VerificationStatus::OK,
277 signature: vec![],
278 }
279 }
280
281 #[test]
282 fn test_audit_log_append() {
283 let keypair = KeyPair::generate().unwrap();
284 let mut log = AuditLog::new(keypair).unwrap();
285
286 let action = Action::delete("test.txt");
287 let proof = create_test_proof();
288 let decision = Decision::Approved;
289
290 log.append(action, proof, decision).unwrap();
291
292 assert_eq!(log.len(), 1);
293 assert_eq!(log.entries()[0].index, 0);
294 }
295
296 #[test]
297 fn test_audit_log_chain_linkage() {
298 let keypair = KeyPair::generate().unwrap();
299 let mut log = AuditLog::new(keypair).unwrap();
300
301 log.append(
303 Action::delete("file1.txt"),
304 create_test_proof(),
305 Decision::Approved,
306 )
307 .unwrap();
308
309 log.append(
311 Action::delete("file2.txt"),
312 create_test_proof(),
313 Decision::Approved,
314 )
315 .unwrap();
316
317 assert_eq!(log.entries()[1].prev_hash, log.entries()[0].current_hash);
319 }
320
321 #[test]
322 fn test_verify_chain_success() {
323 let keypair = KeyPair::generate().unwrap();
324 let mut log = AuditLog::new(keypair).unwrap();
325
326 for i in 0..5 {
328 log.append(
329 Action::delete(format!("file{}.txt", i)),
330 create_test_proof(),
331 Decision::Approved,
332 )
333 .unwrap();
334 }
335
336 assert!(log.verify_chain().is_ok());
338 }
339
340 #[test]
341 fn test_verify_chain_detects_tampering() {
342 let keypair = KeyPair::generate().unwrap();
343 let mut log = AuditLog::new(keypair).unwrap();
344
345 log.append(
347 Action::delete("file1.txt"),
348 create_test_proof(),
349 Decision::Approved,
350 )
351 .unwrap();
352 log.append(
353 Action::delete("file2.txt"),
354 create_test_proof(),
355 Decision::Approved,
356 )
357 .unwrap();
358 log.append(
359 Action::delete("file3.txt"),
360 create_test_proof(),
361 Decision::Approved,
362 )
363 .unwrap();
364
365 log.entries[1].current_hash[0] ^= 0xFF;
367
368 assert!(log.verify_chain().is_err());
370 }
371
372 #[test]
373 fn test_genesis_block() {
374 let keypair = KeyPair::generate().unwrap();
375 let mut log = AuditLog::new(keypair).unwrap();
376
377 log.append(
378 Action::delete("test.txt"),
379 create_test_proof(),
380 Decision::Approved,
381 )
382 .unwrap();
383
384 assert_eq!(log.entries()[0].prev_hash, [0u8; 32]);
386 }
387}