Skip to main content

underhill_attestation/
runtime_sealing.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Local hardware protector verification and recreation for runtime recovery.
5//!
6//! These operations neither access VMGS nor make GET/SKR callouts. The caller
7//! supplies the active DEK and is responsible for persisting a replacement
8//! protector. Hardware-derived keys are freshly obtained on every operation;
9//! they must not be cached across migration, even when the SVN is unchanged.
10//!
11//! Runtime recovery must use a resident [`RuntimeTcbFloor`], initialized from
12//! trusted local hardware or restored through authenticated, VM-bound, fresh
13//! servicing state before worker events are accepted. The stateless helpers
14//! below do not enforce a runtime TCB floor.
15
16mod tcb_floor;
17
18pub(crate) use tcb_floor::BootTcbFloor;
19pub use tcb_floor::RuntimeTcbFloor;
20pub use tcb_floor::saved_state::SavedRuntimeTcbFloor;
21pub use tcb_floor::saved_state::SavedRuntimeTcbFloorSvn;
22
23use crate::hardware_key_sealing::HardwareDerivedKeys;
24use crate::hardware_key_sealing::HardwareDerivedKeysError;
25use crate::hardware_key_sealing::HardwareKeySealingError;
26use crate::hardware_key_sealing::HwKeyProtector;
27use crate::hardware_key_sealing::seal_key;
28use crate::vmgs::parse_hardware_key_protector;
29use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
30use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::HardwareSealingPolicy;
31use openhcl_attestation_protocol::vmgs;
32use tee_call::KeyDerivationPolicy;
33use tee_call::KeyDerivationSvn;
34use tee_call::REPORT_DATA_SIZE;
35use tee_call::TeeCall;
36use tee_call::TeeCallGetDerivedKey;
37use tee_call::TeeType;
38use thiserror::Error;
39use zerocopy::IntoBytes;
40
41/// A local runtime sealing failure, as distinct from a stale or invalid
42/// protector. No error contains the DEK, hardware secret, or derived keys.
43#[derive(Debug, Error)]
44#[error(transparent)]
45pub struct Error(ErrorInner);
46
47#[derive(Debug, Error)]
48enum ErrorInner {
49    #[error("hardware sealing policy is disabled")]
50    DisabledPolicy,
51    #[error("TDX signer-based hardware sealing is unsupported")]
52    TdxSignerPolicyUnsupported,
53    #[error("TEE does not support hardware sealing")]
54    UnsupportedTee,
55    #[error("failed to obtain a local TEE report")]
56    Report(#[source] tee_call::Error),
57    #[error("local TEE report has no key derivation SVN")]
58    MissingKeyDerivationSvn,
59    #[error("local report key derivation SVN does not match the TEE")]
60    ReportSvnMismatch,
61    #[error("local SNP report is malformed or truncated")]
62    MalformedReport,
63    #[error("local SNP report TCB does not match its key derivation SVN")]
64    ReportTcbMismatch,
65    #[error("local TCB has a component below the runtime floor")]
66    TcbLowered,
67    #[error("local TCB is incompatible with the runtime floor comparison domain")]
68    TcbIncompatible,
69    #[error("saved runtime TCB floor has an invalid version, variant, or comparison domain")]
70    InvalidSavedTcbFloor,
71    #[error("failed to freshly derive hardware sealing keys")]
72    Derive(#[source] HardwareDerivedKeysError),
73    #[error("failed to unseal the hardware protector")]
74    Unseal(#[source] HardwareKeySealingError),
75    #[error("failed to seal the active DEK")]
76    Seal(#[source] HardwareKeySealingError),
77}
78
79/// Validate the configured policy before making any hardware calls. In
80/// particular, never turn disabled sealing or TDX signer sealing into Hash.
81fn sealing_context<'a>(
82    tee: &'a dyn TeeCall,
83    config: &AttestationVmConfig,
84) -> Result<(&'a dyn TeeCallGetDerivedKey, bool), Error> {
85    let mix_measurement = match config.hardware_sealing_policy {
86        HardwareSealingPolicy::None => return Err(Error(ErrorInner::DisabledPolicy)),
87        HardwareSealingPolicy::Hash => true,
88        HardwareSealingPolicy::Signer => false,
89    };
90    match tee.tee_type() {
91        TeeType::Snp => {}
92        TeeType::Tdx if mix_measurement => {}
93        TeeType::Tdx => return Err(Error(ErrorInner::TdxSignerPolicyUnsupported)),
94        _ => return Err(Error(ErrorInner::UnsupportedTee)),
95    }
96    let hardware = tee
97        .supports_get_derived_key()
98        .ok_or(Error(ErrorInner::UnsupportedTee))?;
99    Ok((hardware, mix_measurement))
100}
101
102fn svn_matches_tee(svn: KeyDerivationSvn, tee_type: TeeType) -> bool {
103    matches!(
104        (svn, tee_type),
105        (KeyDerivationSvn::Snp { .. }, TeeType::Snp) | (KeyDerivationSvn::Tdx { .. }, TeeType::Tdx)
106    )
107}
108
109/// Reject unknown formats and noncanonical headers before interpreting SVN or
110/// policy bytes. The shared byte parser has already checked the exact blob size.
111fn validated_policy(protector: &HwKeyProtector) -> Option<KeyDerivationPolicy> {
112    match protector {
113        HwKeyProtector::Legacy(p) => {
114            if p.header.version != vmgs::HW_KEY_PROTECTOR_VERSION_2
115                || p.header.length as usize != vmgs::HW_KEY_PROTECTOR_SIZE
116                || p.header.mix_measurement > 1
117                || p.header._reserved != [0; 7]
118            {
119                return None;
120            }
121        }
122        HwKeyProtector::V3(p) => {
123            if p.header.version != vmgs::HW_KEY_PROTECTOR_VERSION_3
124                || p.header.length as usize != vmgs::HW_KEY_PROTECTOR_V3_SIZE
125                || p.header.mix_measurement > 1
126                || p.header._reserved != [0; 3]
127            {
128                return None;
129            }
130            if p.header.tee_type == vmgs::HW_KEY_PROTECTOR_TEE_TYPE_SNP
131                && p.header.svn[8..].iter().any(|&byte| byte != 0)
132            {
133                return None;
134            }
135        }
136    }
137    protector.key_derivation_policy()
138}
139
140/// Check whether the stored protector authenticates and unseals to the active
141/// DEK on the current hardware, using its **stored** policy and SVN.
142///
143/// Returns `Ok(false)` for malformed, incompatible, policy-mismatched, or
144/// unauthenticated protectors, and for a different DEK. Hardware derivation and
145/// crypto operational failures return `Err`, not a mismatch. An unavailable
146/// source SVN is a derivation error: the caller may explicitly retry by creating
147/// a protector with the destination's current SVN. No derived keys are cached.
148/// This stateless helper does not obtain a report or enforce a runtime TCB floor;
149/// runtime candidate verification must use [`RuntimeTcbFloor::verify_protector`].
150pub fn protector_matches(
151    tee: &dyn TeeCall,
152    config: &AttestationVmConfig,
153    protector_bytes: &[u8],
154    dek: &[u8; 32],
155) -> Result<bool, Error> {
156    let (hardware, mix_measurement) = sealing_context(tee, config)?;
157    let Ok(protector) = parse_hardware_key_protector(protector_bytes) else {
158        return Ok(false);
159    };
160    let Some(policy) = validated_policy(&protector) else {
161        return Ok(false);
162    };
163    if policy.mix_measurement != mix_measurement || !svn_matches_tee(policy.svn, tee.tee_type()) {
164        return Ok(false);
165    }
166
167    let keys = HardwareDerivedKeys::derive_key(hardware, config, policy)
168        .map_err(|err| Error(ErrorInner::Derive(err)))?;
169    let unsealed = match protector.unseal_key(&keys) {
170        Ok(unsealed) => unsealed,
171        Err(HardwareKeySealingError::HardwareKeyProtectorHmacVerificationFailed) => {
172            return Ok(false);
173        }
174        Err(err) => return Err(Error(ErrorInner::Unseal(err))),
175    };
176    Ok(constant_time_eq::constant_time_eq_32(&unsealed, dek))
177}
178
179/// Seal the unchanged active DEK into a new v3 protector using a fresh local
180/// report's SVN and the configured policy. This does not rotate the DEK.
181///
182/// Disabled sealing, unsupported TEEs, TDX signer policy, missing or incompatible
183/// report SVN, and report/derivation/sealing failures return `Err`. No fallback
184/// policy or remote key release is attempted.
185///
186/// This stateless compatibility helper enforces **no runtime TCB floor** and
187/// does not validate the raw SNP comparison domain. Runtime recovery must use
188/// [`RuntimeTcbFloor::create_protector`] instead.
189pub fn create_protector(
190    tee: &dyn TeeCall,
191    config: &AttestationVmConfig,
192    dek: &[u8; 32],
193) -> Result<Vec<u8>, Error> {
194    let (hardware, mix_measurement) = sealing_context(tee, config)?;
195    let report = tee
196        .get_attestation_report(&[0; REPORT_DATA_SIZE])
197        .map_err(|err| Error(ErrorInner::Report(err)))?;
198    let svn = report
199        .key_derivation_svn
200        .ok_or(Error(ErrorInner::MissingKeyDerivationSvn))?;
201    if !svn_matches_tee(svn, tee.tee_type()) {
202        return Err(Error(ErrorInner::ReportSvnMismatch));
203    }
204    create_protector_with_svn(hardware, config, svn, mix_measurement, dek)
205}
206
207/// Seal with the already observed SVN, without fetching another report. The
208/// runtime caller must have committed its floor ratchet before entering here.
209fn create_protector_with_svn(
210    hardware: &dyn TeeCallGetDerivedKey,
211    config: &AttestationVmConfig,
212    svn: KeyDerivationSvn,
213    mix_measurement: bool,
214    dek: &[u8; 32],
215) -> Result<Vec<u8>, Error> {
216    let keys = HardwareDerivedKeys::derive_key(
217        hardware,
218        config,
219        KeyDerivationPolicy {
220            svn,
221            mix_measurement,
222        },
223    )
224    .map_err(|err| Error(ErrorInner::Derive(err)))?;
225    let protector = seal_key(&keys, dek).map_err(|err| Error(ErrorInner::Seal(err)))?;
226    Ok(protector.as_bytes().to_vec())
227}
228
229#[cfg(test)]
230mod tests;