Skip to main content

veracrypt_forensic/
lib.rs

1//! # veracrypt-forensic — VeraCrypt/TrueCrypt volume anomaly auditor
2//!
3//! Observations over an unlocked volume's recovered facts: flavor (VeraCrypt vs
4//! legacy TrueCrypt), the PRF/cipher in use, and whether the header advertises a
5//! hidden volume. Findings are observations, never verdicts.
6
7#![forbid(unsafe_code)]
8#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
9
10use veracrypt::{Flavor, VolumeInfo};
11
12/// Severity of a VeraCrypt finding.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14pub enum Severity {
15    /// Informational context.
16    Info,
17    /// A weak or legacy configuration.
18    Low,
19    /// A materially notable configuration.
20    Medium,
21}
22
23/// A classified observation with a stable code and note.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Anomaly {
26    /// Severity.
27    pub severity: Severity,
28    /// Stable, scheme-prefixed machine code.
29    pub code: &'static str,
30    /// Human-readable note including the offending value.
31    pub note: String,
32}
33
34/// Audit an unlocked volume's [`VolumeInfo`] plus its declared hidden-volume size.
35#[must_use]
36pub fn audit(info: &VolumeInfo, hidden_size: u64) -> Vec<Anomaly> {
37    let mut out = Vec::new();
38
39    if info.flavor == Flavor::TrueCrypt {
40        out.push(Anomaly {
41            severity: Severity::Low,
42            code: "VC-LEGACY-TRUECRYPT",
43            note: "volume is a legacy TrueCrypt (not VeraCrypt) container".to_string(),
44        });
45    }
46
47    if hidden_size != 0 {
48        out.push(Anomaly {
49            severity: Severity::Medium,
50            code: "VC-HIDDEN-VOLUME-DECLARED",
51            note: format!(
52                "outer header declares a hidden volume of {hidden_size} bytes (deniable-encryption indicator)"
53            ),
54        });
55    }
56
57    out.push(Anomaly {
58        severity: Severity::Info,
59        code: "VC-CIPHER-INVENTORY",
60        note: format!(
61            "PRF {}, cipher {}, data area at byte {}",
62            info.prf.name(),
63            info.cipher.name(),
64            info.encrypted_area_start
65        ),
66    });
67
68    out
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use veracrypt::{Cipher, Prf};
75
76    fn info(flavor: Flavor) -> VolumeInfo {
77        VolumeInfo {
78            flavor,
79            prf: Prf::Sha512,
80            cipher: Cipher::Aes,
81            version: 5,
82            encrypted_area_start: 131_072,
83            encrypted_area_size: 36_864,
84        }
85    }
86
87    #[test]
88    fn veracrypt_clean_only_inventory() {
89        let a = audit(&info(Flavor::VeraCrypt), 0);
90        assert_eq!(a.len(), 1);
91        assert_eq!(a[0].code, "VC-CIPHER-INVENTORY");
92    }
93
94    #[test]
95    fn flags_truecrypt_and_hidden() {
96        let a = audit(&info(Flavor::TrueCrypt), 4096);
97        let codes: Vec<_> = a.iter().map(|x| x.code).collect();
98        assert!(codes.contains(&"VC-LEGACY-TRUECRYPT"));
99        assert!(codes.contains(&"VC-HIDDEN-VOLUME-DECLARED"));
100    }
101}