use security_core::severity::SecuritySeverity;
use security_events::event::{EventOutcome, SecurityEvent};
use security_events::kind::EventKind;
use security_events::sink::SecuritySink;
use serde::Serialize;
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub enum IntegrityResult {
Valid,
Tampered,
SideLoaded,
}
#[derive(Clone, Debug)]
pub struct IntegrityCheck {
mode: IntegrityMode,
}
#[derive(Clone, Debug)]
pub struct IntegrityCheckResult {
pub result: IntegrityResult,
pub details: Vec<String>,
}
#[derive(Clone, Debug)]
enum IntegrityMode {
Signature {
expected_hash: String,
},
StoreVerification {
allowed_stores: Vec<String>,
},
ResourceIntegrity {
expected_hashes: HashMap<String, String>,
},
}
impl IntegrityCheck {
pub fn new_signature(expected_hash: &str) -> Self {
Self {
mode: IntegrityMode::Signature {
expected_hash: expected_hash.to_string(),
},
}
}
pub fn new_store_verification(allowed_stores: Vec<String>) -> Self {
Self {
mode: IntegrityMode::StoreVerification { allowed_stores },
}
}
pub fn new_resource_integrity() -> Self {
Self {
mode: IntegrityMode::ResourceIntegrity {
expected_hashes: HashMap::new(),
},
}
}
pub fn add_resource_hash(&mut self, resource_path: &str, expected_hash: &str) {
if let IntegrityMode::ResourceIntegrity { expected_hashes } = &mut self.mode {
expected_hashes.insert(resource_path.to_string(), expected_hash.to_string());
}
}
pub fn verify(&self, actual_hash: &str) -> IntegrityResult {
match &self.mode {
IntegrityMode::Signature { expected_hash } => {
if actual_hash == expected_hash {
IntegrityResult::Valid
} else {
IntegrityResult::Tampered
}
}
_ => IntegrityResult::Valid,
}
}
pub fn verify_with_events(
&self,
actual_hash: &str,
sink: &impl SecuritySink,
) -> IntegrityResult {
let result = self.verify(actual_hash);
if result == IntegrityResult::Tampered {
let mut event = SecurityEvent::new(
EventKind::IntegrityViolation,
SecuritySeverity::Critical,
EventOutcome::Blocked,
);
event.resource = Some("app_signature".to_string());
sink.write_event(&event);
}
result
}
pub fn verify_store(&self, actual_store: &str) -> IntegrityResult {
match &self.mode {
IntegrityMode::StoreVerification { allowed_stores } => {
if allowed_stores.iter().any(|s| s == actual_store) {
IntegrityResult::Valid
} else {
IntegrityResult::SideLoaded
}
}
_ => IntegrityResult::Valid,
}
}
pub fn verify_store_with_events(
&self,
actual_store: &str,
sink: &impl SecuritySink,
) -> IntegrityResult {
let result = self.verify_store(actual_store);
if result == IntegrityResult::SideLoaded {
let mut event = SecurityEvent::new(
EventKind::IntegrityViolation,
SecuritySeverity::High,
EventOutcome::Blocked,
);
event.resource = Some(format!("store:{actual_store}"));
sink.write_event(&event);
}
result
}
pub fn verify_resources(&self, actual_hashes: &HashMap<String, String>) -> IntegrityResult {
match &self.mode {
IntegrityMode::ResourceIntegrity { expected_hashes } => {
for (path, expected) in expected_hashes {
match actual_hashes.get(path) {
Some(actual) if actual == expected => {}
_ => return IntegrityResult::Tampered,
}
}
IntegrityResult::Valid
}
_ => IntegrityResult::Valid,
}
}
pub fn verify_resources_with_events(
&self,
actual_hashes: &HashMap<String, String>,
sink: &impl SecuritySink,
) -> IntegrityResult {
let result = self.verify_resources(actual_hashes);
if result == IntegrityResult::Tampered {
let mut event = SecurityEvent::new(
EventKind::IntegrityViolation,
SecuritySeverity::Critical,
EventOutcome::Blocked,
);
event.resource = Some("resource_integrity".to_string());
sink.write_event(&event);
}
result
}
}