chie_crypto/
audit_log.rs

1//! Cryptographic Operation Audit Logging
2//!
3//! This module provides secure, tamper-evident audit logging for cryptographic operations.
4//! All sensitive operations (key generation, signing, encryption, etc.) can be logged
5//! with metadata for compliance and forensic purposes.
6//!
7//! # Features
8//!
9//! - Tamper-evident logging using Merkle trees
10//! - Structured audit log entries with timestamps
11//! - Operation categorization and severity levels
12//! - Query and filtering capabilities
13//! - Retention policies with automatic cleanup
14//! - Export to JSON for external analysis
15//! - Secure storage with integrity verification
16//!
17//! # Use Cases in CHIE Protocol
18//!
19//! - Compliance auditing (GDPR, CCPA, FIPS)
20//! - Security incident investigation
21//! - Key lifecycle tracking
22//! - Access control verification
23//! - Anomaly detection
24//!
25//! # Example
26//!
27//! ```
28//! use chie_crypto::audit_log::{AuditLog, AuditEntry, OperationType, SeverityLevel};
29//!
30//! let mut audit_log = AuditLog::new();
31//!
32//! // Log a key generation operation
33//! audit_log.log(
34//!     OperationType::KeyGeneration,
35//!     SeverityLevel::Info,
36//!     "Generated Ed25519 keypair for user alice",
37//!     Some("user_id=alice, key_type=Ed25519"),
38//! );
39//!
40//! // Log an encryption operation
41//! audit_log.log(
42//!     OperationType::Encryption,
43//!     SeverityLevel::Info,
44//!     "Encrypted file document.pdf",
45//!     Some("file_size=1024000, algorithm=ChaCha20-Poly1305"),
46//! );
47//!
48//! // Query audit logs
49//! let key_gen_logs = audit_log.query_by_operation(OperationType::KeyGeneration);
50//! assert_eq!(key_gen_logs.len(), 1);
51//!
52//! // Verify log integrity
53//! assert!(audit_log.verify_integrity());
54//! ```
55
56use crate::hash::hash;
57use chrono::{DateTime, Duration, Utc};
58use serde::{Deserialize, Serialize};
59use std::collections::HashMap;
60
61/// Type of cryptographic operation
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub enum OperationType {
64    /// Key generation
65    KeyGeneration,
66    /// Key import/export
67    KeyImportExport,
68    /// Key rotation
69    KeyRotation,
70    /// Key deletion
71    KeyDeletion,
72    /// Digital signature
73    Signing,
74    /// Signature verification
75    SignatureVerification,
76    /// Encryption
77    Encryption,
78    /// Decryption
79    Decryption,
80    /// Hashing
81    Hashing,
82    /// Key derivation
83    KeyDerivation,
84    /// Random number generation
85    RandomGeneration,
86    /// Certificate issuance
87    CertificateIssuance,
88    /// Certificate revocation
89    CertificateRevocation,
90    /// Access control check
91    AccessControl,
92    /// Policy enforcement
93    PolicyEnforcement,
94    /// Audit log access
95    AuditAccess,
96    /// Other operation
97    Other,
98}
99
100/// Severity level of the audit entry
101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
102pub enum SeverityLevel {
103    /// Debug-level information
104    Debug,
105    /// Informational message
106    Info,
107    /// Warning message
108    Warning,
109    /// Error message
110    Error,
111    /// Critical security event
112    Critical,
113}
114
115/// Audit log entry
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct AuditEntry {
118    /// Unique entry ID
119    pub id: u64,
120    /// Timestamp of the operation
121    pub timestamp: DateTime<Utc>,
122    /// Type of operation
123    pub operation_type: OperationType,
124    /// Severity level
125    pub severity: SeverityLevel,
126    /// Human-readable description
127    pub description: String,
128    /// Additional metadata (key-value pairs as string)
129    pub metadata: Option<String>,
130    /// User or entity performing the operation
131    pub principal: Option<String>,
132    /// Hash of previous entry (for tamper detection)
133    pub previous_hash: Vec<u8>,
134    /// Hash of this entry
135    pub entry_hash: Vec<u8>,
136}
137
138impl AuditEntry {
139    /// Create a new audit entry
140    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        // Compute hash of this entry
152        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    /// Verify the integrity of this entry
182    fn verify(&self, expected_previous_hash: &[u8]) -> bool {
183        // Check previous hash matches
184        if self.previous_hash != expected_previous_hash {
185            return false;
186        }
187
188        // Recompute entry hash
189        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/// Audit log with tamper-evident chaining
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct AuditLog {
211    /// All audit entries
212    entries: Vec<AuditEntry>,
213    /// Current principal (user/entity)
214    current_principal: Option<String>,
215    /// Retention policy (days to keep logs)
216    retention_days: Option<i64>,
217    /// Statistics
218    stats: AuditStatistics,
219}
220
221/// Audit log statistics
222#[derive(Debug, Clone, Default, Serialize, Deserialize)]
223pub struct AuditStatistics {
224    /// Total number of entries
225    pub total_entries: u64,
226    /// Entries by operation type
227    pub by_operation: HashMap<String, u64>,
228    /// Entries by severity
229    pub by_severity: HashMap<String, u64>,
230}
231
232impl AuditLog {
233    /// Create a new empty audit log
234    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    /// Create a new audit log with retention policy
244    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    /// Set the current principal (user/entity)
254    pub fn set_principal(&mut self, principal: String) {
255        self.current_principal = Some(principal);
256    }
257
258    /// Clear the current principal
259    pub fn clear_principal(&mut self) {
260        self.current_principal = None;
261    }
262
263    /// Log a cryptographic operation
264    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        // Update statistics
291        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        // Apply retention policy if set
304        if let Some(days) = self.retention_days {
305            self.apply_retention_policy(days);
306        }
307    }
308
309    /// Apply retention policy (remove old entries)
310    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    /// Manually apply retention policy
316    pub fn cleanup(&mut self) {
317        if let Some(days) = self.retention_days {
318            self.apply_retention_policy(days);
319        }
320    }
321
322    /// Get all entries
323    pub fn entries(&self) -> &[AuditEntry] {
324        &self.entries
325    }
326
327    /// Get total number of entries
328    pub fn len(&self) -> usize {
329        self.entries.len()
330    }
331
332    /// Check if log is empty
333    pub fn is_empty(&self) -> bool {
334        self.entries.is_empty()
335    }
336
337    /// Query entries by operation type
338    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    /// Query entries by severity level (at least this level)
346    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    /// Query entries by time range
354    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    /// Query entries by principal
366    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    /// Verify the integrity of the entire audit log
374    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    /// Get audit statistics
390    pub fn statistics(&self) -> &AuditStatistics {
391        &self.stats
392    }
393
394    /// Export audit log to JSON
395    pub fn export_json(&self) -> Result<String, serde_json::Error> {
396        serde_json::to_string_pretty(self)
397    }
398
399    /// Import audit log from JSON
400    pub fn import_json(json: &str) -> Result<Self, serde_json::Error> {
401        serde_json::from_str(json)
402    }
403
404    /// Export to bytes (bincode)
405    pub fn to_bytes(&self) -> Vec<u8> {
406        crate::codec::encode(self).expect("serialization failed")
407    }
408
409    /// Import from bytes (bincode)
410    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        // Tamper with an entry
501        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); // Warning, Error, Critical
556    }
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); // 1 day retention
629
630        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        // Manually set an old timestamp
641        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        // Critical should be returned by all severity queries
772        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}