use crate::AnomalyKind;
#[must_use]
pub fn audit(state: &apfs_core::encryption::EncryptionState) -> Vec<AnomalyKind> {
let detail = format!(
"encrypted={}, tags={:?}, passphrase_hint={}",
state.encrypted, state.tags_present, state.has_passphrase_hint
);
crypto_anomalies(state.encrypted, &detail, &state.unknown_tags)
}
fn crypto_anomalies(
encrypted: bool,
detail: &str,
unknown_tags: &[(u16, u64)],
) -> Vec<AnomalyKind> {
let mut out = Vec::new();
if encrypted {
out.push(AnomalyKind::EncryptionLocked);
out.push(AnomalyKind::EncryptionState {
detail: detail.to_string(),
});
}
for &(raw_tag, offset) in unknown_tags {
out.push(AnomalyKind::EncryptionKeybagAnomaly {
raw_tag: (raw_tag & 0xff) as u8,
offset,
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn codes(v: &[AnomalyKind]) -> Vec<&'static str> {
v.iter().map(AnomalyKind::code).collect()
}
#[test]
fn unencrypted_clean_keybag_has_no_findings() {
assert!(crypto_anomalies(false, "encrypted=false", &[]).is_empty());
}
#[test]
fn encrypted_volume_is_locked_and_reported() {
let v = crypto_anomalies(true, "encrypted=true, tags=[VolumeKey]", &[]);
let c = codes(&v);
assert!(c.contains(&"APFS-ENCRYPTION-LOCKED"));
assert!(c.contains(&"APFS-ENCRYPTION-STATE"));
}
#[test]
fn unknown_tag_yields_keybag_anomaly_with_value() {
let v = crypto_anomalies(true, "d", &[(0x55, 16)]);
let anomaly = v
.iter()
.find(|a| a.code() == "APFS-ENCRYPTION-KEYBAG-ANOMALY")
.expect("keybag anomaly present");
let note = forensicnomicon::report::Observation::note(anomaly);
assert!(note.contains("0x55") && note.contains("16"), "{note}");
}
}