1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Drift Detection Engine
//!
//! Handles operational anomaly detection by identifying when a platform's
//! measurements deviate from the expected baseline.
use crate::baseline::PcrBaseline;
use crate::digest::TypedDigest;
use crate::pcr::PcrSemantic;
use crate::platform_profiles::PlatformProfile;
use alloc::vec::Vec;
/// Severity of the detected measurement drift.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DriftSeverity {
/// Expected variations or non-security-critical deviations.
Informational,
/// Unexpected but potentially benign deviations (e.g., non-critical config changes).
Warning,
/// Severe deviations indicating compromise or unauthorized baseline modification.
Critical,
}
/// Operational policy for handling detected drift.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DriftPolicyMode {
/// Logging only. Allows all drift levels for establishing baselines.
Learning,
/// Allows Informational and Warning drift (with logs/alerts), but fails on Critical.
Audit,
/// Strict enforcement. Fails on both Warning and Critical drift.
Enforcing,
}
/// A structured report of a single measurement drift.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DriftReport {
/// The expected digest from the baseline.
pub expected: TypedDigest,
/// The actual digest observed.
pub actual: TypedDigest,
/// The semantic meaning of the drifted PCR.
pub semantic: PcrSemantic,
/// The severity assigned to this drift.
pub severity: DriftSeverity,
}
/// The engine responsible for evaluating drift against policy.
pub struct DriftDetectionEngine;
impl DriftDetectionEngine {
/// Evaluates a detected drift against the operational policy mode.
///
/// Returns `true` if the drift is permissible under the current mode,
/// or `false` if it should cause an attestation failure.
#[must_use]
pub fn is_drift_permissible(mode: DriftPolicyMode, severity: DriftSeverity) -> bool {
match (mode, severity) {
(DriftPolicyMode::Learning, _)
| (DriftPolicyMode::Audit, DriftSeverity::Informational | DriftSeverity::Warning)
| (DriftPolicyMode::Enforcing, DriftSeverity::Informational) => true,
(DriftPolicyMode::Audit, DriftSeverity::Critical)
| (DriftPolicyMode::Enforcing, DriftSeverity::Warning | DriftSeverity::Critical) => {
false
}
}
}
/// Detects drift by comparing actual measurements in a PCR bank against the expected PCRs of a platform profile.
///
/// If actual measurements differ from the profile's expected values, a list of `DriftReport` is generated.
#[must_use]
pub fn detect_drift(
profile: &PlatformProfile,
actual_pcrs: &crate::pcr::TypedPcrBank,
upgrade_baseline: Option<&PcrBaseline>,
) -> Vec<DriftReport> {
let mut reports = Vec::new();
for expected in &profile.expected_pcrs {
if let Some(actual_measurement) = actual_pcrs.get(expected.semantic) {
if actual_measurement.digest != expected.expected_digest {
let is_upgrade = if let Some(upgrade) = upgrade_baseline {
let profile_baseline = PcrBaseline {
baseline_id: profile.profile_id.clone(),
version: profile.policy_epoch,
measurements: profile.expected_pcrs.clone(),
created_at: 0,
supersedes: None,
};
if upgrade.is_valid_successor(&profile_baseline) {
if let Some(upgrade_expected) = upgrade
.measurements
.iter()
.find(|m| m.semantic == expected.semantic)
{
upgrade_expected.expected_digest == actual_measurement.digest
} else {
false
}
} else {
false
}
} else {
false
};
let severity = match expected.semantic {
PcrSemantic::Firmware | PcrSemantic::Kernel => {
if is_upgrade {
DriftSeverity::Warning
} else {
DriftSeverity::Critical
}
}
PcrSemantic::Bootloader => {
if is_upgrade {
DriftSeverity::Informational
} else {
DriftSeverity::Warning
}
}
_ => DriftSeverity::Informational,
};
reports.push(DriftReport {
expected: expected.expected_digest,
actual: actual_measurement.digest,
semantic: expected.semantic,
severity,
});
}
} else {
reports.push(DriftReport {
expected: expected.expected_digest,
actual: TypedDigest::new(expected.expected_digest.algorithm, [0; 32]),
semantic: expected.semantic,
severity: DriftSeverity::Critical,
});
}
}
reports
}
}