usb_forensic/lib.rs
1//! `usb-forensic` — the USB device-history correlation engine.
2//!
3//! A thin **orchestration / correlation** crate — it parses no raw format itself. It
4//! consumes the fleet's reader crates, normalizes their output into one uniform
5//! USB-history [`Claim`], and cross-correlates values across sources, grading each by
6//! how well its independent storage containers agree ([`Consistency`]) so an examiner
7//! can tell a reliable first-connected time from a partial or contradicted one. Every
8//! finding is an **observation** ("consistent with …"), a
9//! `forensicnomicon::report::Finding`; the examiner draws the conclusions.
10//!
11//! ## What runs today
12//!
13//! - **Correlation core:** [`correlate()`] / [`correlate_sources`] → [`DeviceHistory`]
14//! with per-attribute [`Consistency`] + retained provenance; [`to_jsonl`] output.
15//! - **Findings:** [`audit`] → `forensicnomicon` findings (conflicts graded, MITRE
16//! T1070.006 consistent-with; corroborations as reliable history).
17//! - **Sources:** [`PeripheralSource`] (`peripheral-core` — `setupapi.dev.log`,
18//! SYSTEM-hive `Enum\{USBSTOR,SCSI,USB}` device keys, and Linux kernel logs) and
19//! [`LnkSource`] (`lnk-core` — the volume-serial file join).
20//! - **CLI:** the `usb4n6` binary runs the pipeline over setupapi, a SYSTEM hive,
21//! `.lnk`, jump-list, and Linux syslog evidence (type auto-detected).
22//!
23//! Correlation across the setupapi device serial and the LNK volume serial awaits the
24//! registry `MountedDevices` bridge; event-log and macOS sources follow. See
25//! `docs/roadmap.md` and `docs/feature-parity.md`.
26//!
27//! ## The wedge (why it is not a USB Detective clone)
28//!
29//! Headless, library-embeddable, pipeline-native, and **reproducible** — every
30//! reported value re-derivable from `hive → key → raw bytes → decoding rule`. It
31//! targets the pipeline operator (lab automation, Velociraptor/KAPE), not the GUI
32//! examiner. See the README for the full, adversarially-pressure-tested positioning.
33
34#![forbid(unsafe_code)]
35
36pub mod correlate;
37pub mod docx;
38pub mod model;
39pub mod pdf;
40pub mod reconcile;
41pub mod render;
42pub mod report;
43pub mod source;
44pub mod sources;
45pub mod timeline;
46pub mod tz;
47
48pub use correlate::{correlate, to_jsonl, CorrelatedAttribute, DeviceHistory, ProvenancedValue};
49pub use docx::render_docx;
50pub use model::{ArtifactContainer, Attribute, Claim, DeviceKey, Provenance, SourceKind, Value};
51pub use pdf::render_pdf;
52pub use reconcile::{canonicalize_mounted_volumes, reconcile_volume_serials};
53pub use render::{format_epoch, render_accessed_files, render_report, render_table};
54pub use report::audit;
55pub use source::{correlate_sources, HistorySource};
56pub use sources::apple_ipod::{parse_ipod_plist, AppleDevice, AppleIPodSource};
57pub use sources::device_image::{
58 analyse_device_image, export_mbr_hex, parse_boot_sectors, DeviceImage, DeviceImageSource,
59 EncryptionKind,
60};
61pub use sources::emdmgmt::EmdMgmtSource;
62pub use sources::jumplist::{JumpListArtifact, JumpListSource};
63pub use sources::kernel_pnp::{kernel_pnp_events, KernelPnpEvent, KernelPnpSource};
64pub use sources::lnk::{LnkArtifact, LnkSource};
65pub use sources::macos_unified_log::{parse_unified_log, MacUnifiedLogSource, UsbEnumeration};
66pub use sources::macos_usb::{parse_system_profiler, MacUsbDevice, MacUsbSource};
67pub use sources::mountpoints2::MountPoints2Source;
68pub use sources::partition_diag::PartitionDiagSource;
69pub use sources::peripheral::PeripheralSource;
70pub use sources::volume_cache::VolumeCacheSource;
71pub use timeline::{super_timeline, timeline_to_jsonl, TimelineEvent};
72pub use tz::normalize_local_clocks;
73
74use serde::Serialize;
75
76/// The cross-source agreement grade for one reported attribute (first-connected time,
77/// volume name, serial, …) — the defining output of the correlation engine.
78///
79/// It records whether an independent second source corroborated a value and whether the
80/// sources agreed, so a partial or contradicted value is visibly distinct from a
81/// corroborated one. It is a description of the evidence, never a verdict: `Conflicting`
82/// says the sources disagree, not that a value was "spoofed".
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
84#[non_exhaustive]
85pub enum Consistency {
86 /// Exactly one source reported the value; nothing independent to corroborate it.
87 SingleSource,
88 /// Two or more independent sources reported the value and they agree.
89 Corroborated,
90 /// Two or more independent sources reported the value and they disagree.
91 Conflicting,
92}
93
94impl Consistency {
95 /// A short, stable label for human-facing output. This is a published contract:
96 /// existing labels never change; new variants get new labels.
97 #[must_use]
98 pub const fn label(self) -> &'static str {
99 match self {
100 Self::SingleSource => "single-source",
101 Self::Corroborated => "corroborated",
102 Self::Conflicting => "conflicting",
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn labels_are_stable_and_distinct() {
113 let all = [
114 Consistency::SingleSource,
115 Consistency::Corroborated,
116 Consistency::Conflicting,
117 ];
118 let labels: Vec<&str> = all.iter().map(|c| c.label()).collect();
119 assert_eq!(labels, ["single-source", "corroborated", "conflicting"]);
120 assert_eq!(
121 labels
122 .iter()
123 .collect::<std::collections::HashSet<_>>()
124 .len(),
125 all.len(),
126 "labels must be distinct",
127 );
128 }
129}