1use crate::hash::hash;
57use chrono::{DateTime, Duration, Utc};
58use serde::{Deserialize, Serialize};
59use std::collections::HashMap;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub enum OperationType {
64 KeyGeneration,
66 KeyImportExport,
68 KeyRotation,
70 KeyDeletion,
72 Signing,
74 SignatureVerification,
76 Encryption,
78 Decryption,
80 Hashing,
82 KeyDerivation,
84 RandomGeneration,
86 CertificateIssuance,
88 CertificateRevocation,
90 AccessControl,
92 PolicyEnforcement,
94 AuditAccess,
96 Other,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
102pub enum SeverityLevel {
103 Debug,
105 Info,
107 Warning,
109 Error,
111 Critical,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct AuditEntry {
118 pub id: u64,
120 pub timestamp: DateTime<Utc>,
122 pub operation_type: OperationType,
124 pub severity: SeverityLevel,
126 pub description: String,
128 pub metadata: Option<String>,
130 pub principal: Option<String>,
132 pub previous_hash: Vec<u8>,
134 pub entry_hash: Vec<u8>,
136}
137
138impl AuditEntry {
139 fn new(
141 id: u64,
142 operation_type: OperationType,
143 severity: SeverityLevel,
144 description: String,
145 metadata: Option<String>,
146 principal: Option<String>,
147 previous_hash: Vec<u8>,
148 ) -> Self {
149 let timestamp = Utc::now();
150
151 let mut hash_input = Vec::new();
153 hash_input.extend_from_slice(&id.to_le_bytes());
154 hash_input.extend_from_slice(timestamp.to_rfc3339().as_bytes());
155 hash_input.extend_from_slice(&crate::codec::encode(&operation_type).unwrap());
156 hash_input.extend_from_slice(&crate::codec::encode(&severity).unwrap());
157 hash_input.extend_from_slice(description.as_bytes());
158 if let Some(ref m) = metadata {
159 hash_input.extend_from_slice(m.as_bytes());
160 }
161 if let Some(ref p) = principal {
162 hash_input.extend_from_slice(p.as_bytes());
163 }
164 hash_input.extend_from_slice(&previous_hash);
165
166 let entry_hash = hash(&hash_input).to_vec();
167
168 Self {
169 id,
170 timestamp,
171 operation_type,
172 severity,
173 description,
174 metadata,
175 principal,
176 previous_hash,
177 entry_hash,
178 }
179 }
180
181 fn verify(&self, expected_previous_hash: &[u8]) -> bool {
183 if self.previous_hash != expected_previous_hash {
185 return false;
186 }
187
188 let mut hash_input = Vec::new();
190 hash_input.extend_from_slice(&self.id.to_le_bytes());
191 hash_input.extend_from_slice(self.timestamp.to_rfc3339().as_bytes());
192 hash_input.extend_from_slice(&crate::codec::encode(&self.operation_type).unwrap());
193 hash_input.extend_from_slice(&crate::codec::encode(&self.severity).unwrap());
194 hash_input.extend_from_slice(self.description.as_bytes());
195 if let Some(ref m) = self.metadata {
196 hash_input.extend_from_slice(m.as_bytes());
197 }
198 if let Some(ref p) = self.principal {
199 hash_input.extend_from_slice(p.as_bytes());
200 }
201 hash_input.extend_from_slice(&self.previous_hash);
202
203 let computed_hash = hash(&hash_input);
204 computed_hash.as_slice() == self.entry_hash.as_slice()
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct AuditLog {
211 entries: Vec<AuditEntry>,
213 current_principal: Option<String>,
215 retention_days: Option<i64>,
217 stats: AuditStatistics,
219}
220
221#[derive(Debug, Clone, Default, Serialize, Deserialize)]
223pub struct AuditStatistics {
224 pub total_entries: u64,
226 pub by_operation: HashMap<String, u64>,
228 pub by_severity: HashMap<String, u64>,
230}
231
232impl AuditLog {
233 pub fn new() -> Self {
235 Self {
236 entries: Vec::new(),
237 current_principal: None,
238 retention_days: None,
239 stats: AuditStatistics::default(),
240 }
241 }
242
243 pub fn with_retention(retention_days: i64) -> Self {
245 Self {
246 entries: Vec::new(),
247 current_principal: None,
248 retention_days: Some(retention_days),
249 stats: AuditStatistics::default(),
250 }
251 }
252
253 pub fn set_principal(&mut self, principal: String) {
255 self.current_principal = Some(principal);
256 }
257
258 pub fn clear_principal(&mut self) {
260 self.current_principal = None;
261 }
262
263 pub fn log(
265 &mut self,
266 operation_type: OperationType,
267 severity: SeverityLevel,
268 description: &str,
269 metadata: Option<&str>,
270 ) {
271 let id = self.entries.len() as u64;
272 let previous_hash = self
273 .entries
274 .last()
275 .map(|e| e.entry_hash.clone())
276 .unwrap_or_else(|| vec![0u8; 32]);
277
278 let entry = AuditEntry::new(
279 id,
280 operation_type,
281 severity,
282 description.to_string(),
283 metadata.map(|s| s.to_string()),
284 self.current_principal.clone(),
285 previous_hash,
286 );
287
288 self.entries.push(entry);
289
290 self.stats.total_entries += 1;
292 *self
293 .stats
294 .by_operation
295 .entry(format!("{:?}", operation_type))
296 .or_insert(0) += 1;
297 *self
298 .stats
299 .by_severity
300 .entry(format!("{:?}", severity))
301 .or_insert(0) += 1;
302
303 if let Some(days) = self.retention_days {
305 self.apply_retention_policy(days);
306 }
307 }
308
309 fn apply_retention_policy(&mut self, retention_days: i64) {
311 let cutoff = Utc::now() - Duration::days(retention_days);
312 self.entries.retain(|entry| entry.timestamp > cutoff);
313 }
314
315 pub fn cleanup(&mut self) {
317 if let Some(days) = self.retention_days {
318 self.apply_retention_policy(days);
319 }
320 }
321
322 pub fn entries(&self) -> &[AuditEntry] {
324 &self.entries
325 }
326
327 pub fn len(&self) -> usize {
329 self.entries.len()
330 }
331
332 pub fn is_empty(&self) -> bool {
334 self.entries.is_empty()
335 }
336
337 pub fn query_by_operation(&self, operation_type: OperationType) -> Vec<&AuditEntry> {
339 self.entries
340 .iter()
341 .filter(|e| e.operation_type == operation_type)
342 .collect()
343 }
344
345 pub fn query_by_severity(&self, min_severity: SeverityLevel) -> Vec<&AuditEntry> {
347 self.entries
348 .iter()
349 .filter(|e| e.severity >= min_severity)
350 .collect()
351 }
352
353 pub fn query_by_time_range(
355 &self,
356 start: DateTime<Utc>,
357 end: DateTime<Utc>,
358 ) -> Vec<&AuditEntry> {
359 self.entries
360 .iter()
361 .filter(|e| e.timestamp >= start && e.timestamp <= end)
362 .collect()
363 }
364
365 pub fn query_by_principal(&self, principal: &str) -> Vec<&AuditEntry> {
367 self.entries
368 .iter()
369 .filter(|e| e.principal.as_ref().is_some_and(|p| p == principal))
370 .collect()
371 }
372
373 pub fn verify_integrity(&self) -> bool {
375 if self.entries.is_empty() {
376 return true;
377 }
378
379 let mut previous_hash = vec![0u8; 32];
380 for entry in &self.entries {
381 if !entry.verify(&previous_hash) {
382 return false;
383 }
384 previous_hash = entry.entry_hash.clone();
385 }
386 true
387 }
388
389 pub fn statistics(&self) -> &AuditStatistics {
391 &self.stats
392 }
393
394 pub fn export_json(&self) -> Result<String, serde_json::Error> {
396 serde_json::to_string_pretty(self)
397 }
398
399 pub fn import_json(json: &str) -> Result<Self, serde_json::Error> {
401 serde_json::from_str(json)
402 }
403
404 pub fn to_bytes(&self) -> Vec<u8> {
406 crate::codec::encode(self).expect("serialization failed")
407 }
408
409 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
411 Ok(crate::codec::decode(bytes)?)
412 }
413}
414
415impl Default for AuditLog {
416 fn default() -> Self {
417 Self::new()
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_audit_log_basic() {
427 let mut log = AuditLog::new();
428
429 log.log(
430 OperationType::KeyGeneration,
431 SeverityLevel::Info,
432 "Generated keypair",
433 None,
434 );
435
436 assert_eq!(log.len(), 1);
437 assert!(!log.is_empty());
438 }
439
440 #[test]
441 fn test_audit_log_with_metadata() {
442 let mut log = AuditLog::new();
443
444 log.log(
445 OperationType::Encryption,
446 SeverityLevel::Info,
447 "Encrypted file",
448 Some("file=test.txt, size=1024"),
449 );
450
451 assert_eq!(
452 log.entries()[0].metadata,
453 Some("file=test.txt, size=1024".to_string())
454 );
455 }
456
457 #[test]
458 fn test_audit_log_with_principal() {
459 let mut log = AuditLog::new();
460 log.set_principal("alice".to_string());
461
462 log.log(
463 OperationType::Signing,
464 SeverityLevel::Info,
465 "Signed message",
466 None,
467 );
468
469 assert_eq!(log.entries()[0].principal, Some("alice".to_string()));
470 }
471
472 #[test]
473 fn test_integrity_verification() {
474 let mut log = AuditLog::new();
475
476 log.log(
477 OperationType::KeyGeneration,
478 SeverityLevel::Info,
479 "Op 1",
480 None,
481 );
482 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
483 log.log(OperationType::Signing, SeverityLevel::Info, "Op 3", None);
484
485 assert!(log.verify_integrity());
486 }
487
488 #[test]
489 fn test_integrity_tamper_detection() {
490 let mut log = AuditLog::new();
491
492 log.log(
493 OperationType::KeyGeneration,
494 SeverityLevel::Info,
495 "Op 1",
496 None,
497 );
498 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
499
500 if let Some(entry) = log.entries.get_mut(0) {
502 entry.description = "Tampered!".to_string();
503 }
504
505 assert!(!log.verify_integrity());
506 }
507
508 #[test]
509 fn test_query_by_operation() {
510 let mut log = AuditLog::new();
511
512 log.log(
513 OperationType::KeyGeneration,
514 SeverityLevel::Info,
515 "Op 1",
516 None,
517 );
518 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
519 log.log(
520 OperationType::KeyGeneration,
521 SeverityLevel::Info,
522 "Op 3",
523 None,
524 );
525
526 let results = log.query_by_operation(OperationType::KeyGeneration);
527 assert_eq!(results.len(), 2);
528 }
529
530 #[test]
531 fn test_query_by_severity() {
532 let mut log = AuditLog::new();
533
534 log.log(
535 OperationType::KeyGeneration,
536 SeverityLevel::Info,
537 "Op 1",
538 None,
539 );
540 log.log(
541 OperationType::Encryption,
542 SeverityLevel::Warning,
543 "Op 2",
544 None,
545 );
546 log.log(OperationType::Signing, SeverityLevel::Error, "Op 3", None);
547 log.log(
548 OperationType::KeyDeletion,
549 SeverityLevel::Critical,
550 "Op 4",
551 None,
552 );
553
554 let results = log.query_by_severity(SeverityLevel::Warning);
555 assert_eq!(results.len(), 3); }
557
558 #[test]
559 fn test_query_by_principal() {
560 let mut log = AuditLog::new();
561
562 log.set_principal("alice".to_string());
563 log.log(
564 OperationType::KeyGeneration,
565 SeverityLevel::Info,
566 "Op 1",
567 None,
568 );
569
570 log.set_principal("bob".to_string());
571 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
572
573 log.set_principal("alice".to_string());
574 log.log(OperationType::Signing, SeverityLevel::Info, "Op 3", None);
575
576 let alice_ops = log.query_by_principal("alice");
577 assert_eq!(alice_ops.len(), 2);
578 }
579
580 #[test]
581 fn test_query_by_time_range() {
582 let mut log = AuditLog::new();
583
584 log.log(
585 OperationType::KeyGeneration,
586 SeverityLevel::Info,
587 "Op 1",
588 None,
589 );
590
591 let start = Utc::now();
592 std::thread::sleep(std::time::Duration::from_millis(10));
593
594 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
595
596 let end = Utc::now();
597
598 let results = log.query_by_time_range(start, end);
599 assert_eq!(results.len(), 1);
600 }
601
602 #[test]
603 fn test_statistics() {
604 let mut log = AuditLog::new();
605
606 log.log(
607 OperationType::KeyGeneration,
608 SeverityLevel::Info,
609 "Op 1",
610 None,
611 );
612 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
613 log.log(
614 OperationType::KeyGeneration,
615 SeverityLevel::Warning,
616 "Op 3",
617 None,
618 );
619
620 let stats = log.statistics();
621 assert_eq!(stats.total_entries, 3);
622 assert_eq!(*stats.by_operation.get("KeyGeneration").unwrap(), 2);
623 assert_eq!(*stats.by_severity.get("Info").unwrap(), 2);
624 }
625
626 #[test]
627 fn test_retention_policy() {
628 let mut log = AuditLog::with_retention(1); log.log(
631 OperationType::KeyGeneration,
632 SeverityLevel::Info,
633 "Op 1",
634 None,
635 );
636 log.log(OperationType::Encryption, SeverityLevel::Info, "Op 2", None);
637
638 assert_eq!(log.len(), 2);
639
640 if let Some(entry) = log.entries.get_mut(0) {
642 entry.timestamp = Utc::now() - Duration::days(2);
643 }
644
645 log.cleanup();
646 assert_eq!(log.len(), 1);
647 }
648
649 #[test]
650 fn test_json_export_import() {
651 let mut log = AuditLog::new();
652 log.log(
653 OperationType::KeyGeneration,
654 SeverityLevel::Info,
655 "Test",
656 None,
657 );
658
659 let json = log.export_json().unwrap();
660 let restored = AuditLog::import_json(&json).unwrap();
661
662 assert_eq!(restored.len(), 1);
663 assert!(restored.verify_integrity());
664 }
665
666 #[test]
667 fn test_bincode_serialization() {
668 let mut log = AuditLog::new();
669 log.log(
670 OperationType::KeyGeneration,
671 SeverityLevel::Info,
672 "Test",
673 None,
674 );
675
676 let bytes = log.to_bytes();
677 let restored = AuditLog::from_bytes(&bytes).unwrap();
678
679 assert_eq!(restored.len(), 1);
680 assert!(restored.verify_integrity());
681 }
682
683 #[test]
684 fn test_multiple_principals() {
685 let mut log = AuditLog::new();
686
687 log.set_principal("alice".to_string());
688 log.log(
689 OperationType::KeyGeneration,
690 SeverityLevel::Info,
691 "Alice op 1",
692 None,
693 );
694 log.log(
695 OperationType::Encryption,
696 SeverityLevel::Info,
697 "Alice op 2",
698 None,
699 );
700
701 log.set_principal("bob".to_string());
702 log.log(
703 OperationType::Signing,
704 SeverityLevel::Info,
705 "Bob op 1",
706 None,
707 );
708
709 log.clear_principal();
710 log.log(
711 OperationType::Decryption,
712 SeverityLevel::Info,
713 "System op",
714 None,
715 );
716
717 assert_eq!(log.query_by_principal("alice").len(), 2);
718 assert_eq!(log.query_by_principal("bob").len(), 1);
719 }
720
721 #[test]
722 fn test_all_operation_types() {
723 let mut log = AuditLog::new();
724
725 let operations = [
726 OperationType::KeyGeneration,
727 OperationType::KeyImportExport,
728 OperationType::KeyRotation,
729 OperationType::KeyDeletion,
730 OperationType::Signing,
731 OperationType::SignatureVerification,
732 OperationType::Encryption,
733 OperationType::Decryption,
734 OperationType::Hashing,
735 OperationType::KeyDerivation,
736 OperationType::RandomGeneration,
737 OperationType::CertificateIssuance,
738 OperationType::CertificateRevocation,
739 OperationType::AccessControl,
740 OperationType::PolicyEnforcement,
741 OperationType::AuditAccess,
742 OperationType::Other,
743 ];
744
745 for op in &operations {
746 log.log(*op, SeverityLevel::Info, "Test operation", None);
747 }
748
749 assert_eq!(log.len(), operations.len());
750 assert!(log.verify_integrity());
751 }
752
753 #[test]
754 fn test_all_severity_levels() {
755 let mut log = AuditLog::new();
756
757 let severities = [
758 SeverityLevel::Debug,
759 SeverityLevel::Info,
760 SeverityLevel::Warning,
761 SeverityLevel::Error,
762 SeverityLevel::Critical,
763 ];
764
765 for severity in &severities {
766 log.log(OperationType::Other, *severity, "Test severity", None);
767 }
768
769 assert_eq!(log.len(), severities.len());
770
771 assert_eq!(log.query_by_severity(SeverityLevel::Debug).len(), 5);
773 assert_eq!(log.query_by_severity(SeverityLevel::Info).len(), 4);
774 assert_eq!(log.query_by_severity(SeverityLevel::Warning).len(), 3);
775 assert_eq!(log.query_by_severity(SeverityLevel::Error).len(), 2);
776 assert_eq!(log.query_by_severity(SeverityLevel::Critical).len(), 1);
777 }
778}