use super::AuditEvent;
use std::sync::atomic::{AtomicU64, Ordering};
pub trait AuditIntegrity: Send + Sync {
fn add_integrity(&self, event: &mut AuditEvent);
fn verify(&self, event: &AuditEvent) -> bool {
let _ = event;
true }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoIntegrity;
impl AuditIntegrity for NoIntegrity {
fn add_integrity(&self, _event: &mut AuditEvent) {
}
}
#[derive(Debug)]
pub struct SequenceIntegrity {
sequence: AtomicU64,
}
impl SequenceIntegrity {
#[must_use]
pub fn new() -> Self {
Self {
sequence: AtomicU64::new(0),
}
}
#[must_use]
pub fn with_start(start: u64) -> Self {
Self {
sequence: AtomicU64::new(start),
}
}
pub fn current(&self) -> u64 {
self.sequence.load(Ordering::SeqCst)
}
pub fn reset(&self, value: u64) {
self.sequence.store(value, Ordering::SeqCst);
}
}
impl Default for SequenceIntegrity {
fn default() -> Self {
Self::new()
}
}
impl AuditIntegrity for SequenceIntegrity {
fn add_integrity(&self, event: &mut AuditEvent) {
let seq = self.sequence.fetch_add(1, Ordering::SeqCst);
event.add_metadata("sequence", seq);
}
fn verify(&self, event: &AuditEvent) -> bool {
event.metadata.contains_key("sequence")
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ChecksumIntegrity;
impl ChecksumIntegrity {
#[must_use]
pub fn new() -> Self {
Self
}
fn calculate_checksum(event: &AuditEvent) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::UNIX_EPOCH;
let mut hasher = DefaultHasher::new();
format!("{:?}", event.event_type).hash(&mut hasher);
if let Some(ref id) = event.correlation_id {
id.hash(&mut hasher);
}
if let Some(ref principal) = event.principal {
principal.hash(&mut hasher);
}
if let Some(ref method) = event.method {
method.hash(&mut hasher);
}
format!("{:?}", event.result).hash(&mut hasher);
if let Ok(duration) = event.timestamp.duration_since(UNIX_EPOCH) {
duration.as_nanos().hash(&mut hasher);
}
hasher.finish()
}
}
impl AuditIntegrity for ChecksumIntegrity {
fn add_integrity(&self, event: &mut AuditEvent) {
let checksum = Self::calculate_checksum(event);
event.add_metadata("checksum", checksum);
}
fn verify(&self, event: &AuditEvent) -> bool {
let stored_checksum = match event.metadata.get("checksum") {
Some(serde_json::Value::Number(n)) => n.as_u64(),
_ => return false,
};
if let Some(stored) = stored_checksum {
let mut temp_event = event.clone();
temp_event.metadata.remove("checksum");
let calculated = Self::calculate_checksum(&temp_event);
stored == calculated
} else {
false
}
}
}
pub struct CombinedIntegrity {
mechanisms: Vec<Box<dyn AuditIntegrity>>,
}
impl CombinedIntegrity {
#[must_use]
pub fn new(mechanisms: Vec<Box<dyn AuditIntegrity>>) -> Self {
Self { mechanisms }
}
#[must_use]
pub fn from_arcs(mechanisms: Vec<std::sync::Arc<dyn AuditIntegrity>>) -> Self {
Self {
mechanisms: mechanisms
.into_iter()
.map(|m| -> Box<dyn AuditIntegrity> { Box::new(ArcIntegrityWrapper(m)) })
.collect(),
}
}
}
impl AuditIntegrity for CombinedIntegrity {
fn add_integrity(&self, event: &mut AuditEvent) {
for mechanism in &self.mechanisms {
mechanism.add_integrity(event);
}
}
fn verify(&self, event: &AuditEvent) -> bool {
self.mechanisms.iter().all(|m| m.verify(event))
}
}
#[derive(Clone)]
struct ArcIntegrityWrapper(std::sync::Arc<dyn AuditIntegrity>);
impl AuditIntegrity for ArcIntegrityWrapper {
fn add_integrity(&self, event: &mut AuditEvent) {
self.0.add_integrity(event);
}
fn verify(&self, event: &AuditEvent) -> bool {
self.0.verify(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::audit_logging::{AuditEventType, AuditResult};
#[test]
fn test_no_integrity() {
let integrity = NoIntegrity;
let mut event = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.result(AuditResult::Success)
.build();
integrity.add_integrity(&mut event);
assert!(event.metadata.is_empty());
assert!(integrity.verify(&event));
}
#[test]
fn test_sequence_integrity() {
let integrity = SequenceIntegrity::new();
let mut event1 = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.result(AuditResult::Success)
.build();
integrity.add_integrity(&mut event1);
let mut event2 = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.result(AuditResult::Success)
.build();
integrity.add_integrity(&mut event2);
let seq1 = event1.metadata.get("sequence").unwrap().as_u64().unwrap();
let seq2 = event2.metadata.get("sequence").unwrap().as_u64().unwrap();
assert_eq!(seq1, 0);
assert_eq!(seq2, 1);
assert!(integrity.verify(&event1));
assert!(integrity.verify(&event2));
}
#[test]
fn test_checksum_integrity() {
let integrity = ChecksumIntegrity::new();
let mut event = AuditEvent::builder()
.event_type(AuditEventType::AuthenticationAttempt)
.principal("user@example.com")
.method("login")
.result(AuditResult::Success)
.build();
integrity.add_integrity(&mut event);
assert!(event.metadata.contains_key("checksum"));
assert!(integrity.verify(&event));
event.principal = Some("attacker@example.com".to_string());
assert!(!integrity.verify(&event));
}
#[test]
fn test_combined_integrity() {
let combined = CombinedIntegrity::new(vec![
Box::new(SequenceIntegrity::new()),
Box::new(ChecksumIntegrity::new()),
]);
let mut event = AuditEvent::builder()
.event_type(AuditEventType::MethodInvocation)
.result(AuditResult::Success)
.build();
combined.add_integrity(&mut event);
assert!(event.metadata.contains_key("sequence"));
assert!(event.metadata.contains_key("checksum"));
assert!(combined.verify(&event));
}
}