use security_core::classification::DataClassification;
use security_events::event::SecurityEvent;
use security_events::kind::EventKind;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use zeroize::Zeroize;
pub struct SensitiveBuffer {
inner: Vec<u8>,
created_at: Instant,
ttl: Option<Duration>,
}
impl SensitiveBuffer {
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self {
inner: data,
created_at: Instant::now(),
ttl: None,
}
}
#[must_use]
pub fn with_ttl(data: Vec<u8>, ttl: Duration) -> Self {
Self {
inner: data,
created_at: Instant::now(),
ttl: Some(ttl),
}
}
#[must_use]
pub fn expose(&self) -> &[u8] {
&self.inner
}
pub fn wipe(&mut self) {
self.inner.zeroize();
}
#[must_use]
pub fn is_expired(&self) -> bool {
match self.ttl {
Some(ttl) => self.created_at.elapsed() >= ttl,
None => false,
}
}
}
impl Drop for SensitiveBuffer {
fn drop(&mut self) {
self.inner.zeroize();
}
}
impl std::fmt::Debug for SensitiveBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SensitiveBuffer([REDACTED] {} bytes)", self.inner.len())
}
}
impl std::fmt::Display for SensitiveBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[REDACTED]")
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BackupExclusion {
#[default]
Exclude,
Allow,
}
impl BackupExclusion {
#[must_use]
pub fn should_exclude_from_backup(&self) -> bool {
matches!(self, Self::Exclude)
}
}
#[derive(Clone, Debug)]
pub struct MobileStoragePolicy {
classification: DataClassification,
require_encryption: bool,
require_hardware_keystore: bool,
}
impl MobileStoragePolicy {
#[must_use]
pub fn encrypted(classification: DataClassification) -> Self {
Self {
classification,
require_encryption: true,
require_hardware_keystore: false,
}
}
#[must_use]
pub fn hardware_backed(classification: DataClassification) -> Self {
Self {
classification,
require_encryption: true,
require_hardware_keystore: true,
}
}
#[must_use]
pub fn for_classification(classification: DataClassification) -> Self {
match classification {
DataClassification::Public | DataClassification::Internal => Self {
classification,
require_encryption: false,
require_hardware_keystore: false,
},
DataClassification::Confidential
| DataClassification::PII
| DataClassification::Regulated => Self {
classification,
require_encryption: true,
require_hardware_keystore: false,
},
DataClassification::Secret | DataClassification::Credentials => Self {
classification,
require_encryption: true,
require_hardware_keystore: true,
},
_ => Self {
classification,
require_encryption: true,
require_hardware_keystore: false,
},
}
}
#[must_use]
pub fn requires_encryption(&self) -> bool {
self.require_encryption
}
#[must_use]
pub fn requires_hardware_keystore(&self) -> bool {
self.require_hardware_keystore
}
#[must_use]
pub fn classification(&self) -> DataClassification {
self.classification
}
#[must_use]
pub fn check_compliance(
&self,
is_encrypted: bool,
has_hardware_keystore: bool,
) -> Vec<SecurityEvent> {
use security_core::severity::SecuritySeverity;
use security_events::event::EventOutcome;
let mut violations = Vec::new();
if self.require_encryption && !is_encrypted {
let mut event = SecurityEvent::new(
EventKind::StoragePolicyViolation,
SecuritySeverity::High,
EventOutcome::Failure,
);
event.resource = Some(format!(
"encryption required for {:?} data but storage is not encrypted",
self.classification
));
violations.push(event);
}
if self.require_hardware_keystore && !has_hardware_keystore {
let mut event = SecurityEvent::new(
EventKind::StoragePolicyViolation,
SecuritySeverity::High,
EventOutcome::Failure,
);
event.resource = Some(format!(
"hardware keystore required for {:?} data but not available",
self.classification
));
violations.push(event);
}
violations
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sensitive_buffer_debug_redacted() {
let buf = SensitiveBuffer::new(vec![1, 2, 3]);
assert!(format!("{:?}", buf).contains("[REDACTED]"));
}
#[test]
fn backup_exclusion_default_is_exclude() {
assert!(BackupExclusion::default().should_exclude_from_backup());
}
#[test]
fn mobile_storage_policy_public_relaxed() {
let p = MobileStoragePolicy::for_classification(DataClassification::Public);
assert!(!p.requires_encryption());
assert!(!p.requires_hardware_keystore());
}
#[test]
fn mobile_storage_policy_credentials_strict() {
let p = MobileStoragePolicy::for_classification(DataClassification::Credentials);
assert!(p.requires_encryption());
assert!(p.requires_hardware_keystore());
}
}