Skip to main content

tee_call/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This module includes the `TeeCall` trait and its implementation. The trait defines
5//! the trusted execution environment (TEE)-specific APIs for attestation and data dealing.
6
7#![cfg(target_os = "linux")]
8#![forbid(unsafe_code)]
9
10use hcl::ioctl::MshvHvcall;
11use hvdef::HypercallCode;
12use thiserror::Error;
13use zerocopy::IntoBytes;
14
15#[expect(missing_docs)] // self-explanatory fields
16#[derive(Debug, Error)]
17pub enum Error {
18    #[error("failed to open /dev/sev-guest")]
19    OpenDevSevGuest(#[source] sev_guest_device::Error),
20    #[error("failed to get an SNP report via /dev/sev-guest")]
21    GetSnpReport(#[source] sev_guest_device::Error),
22    #[error("failed to get an SNP derived key via /dev/sev-guest")]
23    GetSnpDerivedKey(#[source] sev_guest_device::Error),
24    #[error("got all-zeros key")]
25    AllZeroKey,
26    #[error("failed to open /dev/tdx_guest")]
27    OpenDevTdxGuest(#[source] tdx_guest_device::Error),
28    #[error("failed to get a TDX report via /dev/tdx_guest")]
29    GetTdxReport(#[source] tdx_guest_device::Error),
30    #[error("failed to get a TDX derived key via /dev/tdx_guest")]
31    GetTdxDerivedKey(#[source] tdx_guest_device::Error),
32    #[error(
33        "TDX signer-based hardware sealing is not supported: TDX has no signer identity register \
34         to bind a measurement-independent key to, so refusing to derive an under-bound key"
35    )]
36    TdxSignerPolicyUnsupported,
37    #[error("key derivation SVN does not match the TEE type")]
38    KeyDerivationSvnMismatch,
39    #[error("failed to open VBS guest device")]
40    OpenDevVbsGuest(#[source] hcl::ioctl::Error),
41    #[error("failed to get a VBS report via VBS guest device")]
42    GetVbsReport(#[source] hvdef::HvError),
43}
44
45/// Use the SNP-defined derived key size for now.
46pub const HW_DERIVED_KEY_LENGTH: usize = x86defs::snp::SNP_DERIVED_KEY_SIZE;
47
48/// Use the SNP-defined report data size for now.
49// DEVNOTE: This value should be upper bound among all the supported TEE types.
50pub const REPORT_DATA_SIZE: usize = x86defs::snp::SNP_REPORT_DATA_SIZE;
51
52// TDX and SNP report data size are equal so we can use either of them
53static_assertions::const_assert_eq!(
54    x86defs::snp::SNP_REPORT_DATA_SIZE,
55    x86defs::tdx::TDX_REPORT_DATA_SIZE
56);
57
58// TDX and SNP derived key size are equal so we can return either of them as
59// [`HW_DERIVED_KEY_LENGTH`].
60static_assertions::const_assert_eq!(
61    x86defs::snp::SNP_DERIVED_KEY_SIZE,
62    x86defs::tdx::TDX_DERIVED_KEY_SIZE
63);
64
65/// Type of the TEE
66#[derive(Debug)]
67pub enum TeeType {
68    /// AMD SEV-SNP
69    Snp,
70    /// Intel TDX
71    Tdx,
72    /// ARM CCA
73    Cca,
74    /// Virtualization-based Security (VBS)
75    Vbs,
76}
77
78/// TEE-specific SVN material bound into the hardware-derived key for
79/// anti-rollback. Each variant carries exactly the fields its TEE mixes into
80/// key derivation and maps 1:1 to a `HardwareKeyProtector` header version.
81#[derive(Debug, Clone, Copy)]
82pub enum KeyDerivationSvn {
83    /// SNP reported TCB version (`HW_KEY_PROTECTOR` v2).
84    Snp {
85        /// `reported_tcb` from the SNP attestation report.
86        tcb_version: u64,
87    },
88    /// TDX report SVNs, both bound into the key (`HW_KEY_PROTECTOR` v3).
89    Tdx {
90        /// Module `TEE_TCB_SVN` (16 bytes, verbatim from the report).
91        tee_tcb_svn: [u8; 16],
92        /// Platform `CPU_SVN` (16 bytes, verbatim from the report).
93        cpu_svn: [u8; 16],
94    },
95}
96
97/// The result of the `get_attestation_report`.
98pub struct GetAttestationReportResult {
99    /// The report in raw bytes
100    pub report: Vec<u8>,
101    /// SVN material for hardware key derivation; `None` for TEEs that don't
102    /// derive keys.
103    pub key_derivation_svn: Option<KeyDerivationSvn>,
104}
105
106/// Key derivation policy
107#[derive(Debug, Clone, Copy)]
108pub struct KeyDerivationPolicy {
109    /// TEE-specific SVN material bound into the derived key.
110    pub svn: KeyDerivationSvn,
111    /// Whether to mix measurement into the key derivation.
112    pub mix_measurement: bool,
113}
114
115/// Trait that defines the get attestation report interface for TEE.
116pub trait TeeCall: Send + Sync {
117    /// Get the hardware-backed attestation report.
118    ///
119    /// # Arguments
120    /// * `report_data` - The report data to include in the attestation report.
121    ///
122    /// Returns the attestation report result.
123    fn get_attestation_report(
124        &self,
125        report_data: &[u8; REPORT_DATA_SIZE],
126    ) -> Result<GetAttestationReportResult, Error>;
127    /// Whether [`TeeCallGetDerivedKey`] is implemented.
128    fn supports_get_derived_key(&self) -> Option<&dyn TeeCallGetDerivedKey>;
129    /// Get the [`TeeType`].
130    fn tee_type(&self) -> TeeType;
131}
132
133/// Optional sub-trait that defines the get-derived-key interface for a TEE.
134pub trait TeeCallGetDerivedKey: TeeCall {
135    /// Get the derived key that should be deterministic based on the hardware and software
136    /// configurations.
137    ///
138    /// # Arguments
139    /// * `policy` - The key derivation policy to use.
140    ///
141    /// Returns the derived key.
142    fn get_derived_key(
143        &self,
144        policy: KeyDerivationPolicy,
145    ) -> Result<[u8; HW_DERIVED_KEY_LENGTH], Error>;
146}
147
148/// Implementation of [`TeeCall`] for SNP
149pub struct SnpCall;
150
151impl TeeCall for SnpCall {
152    /// Get the attestation report from /dev/sev-guest.
153    fn get_attestation_report(
154        &self,
155        report_data: &[u8; REPORT_DATA_SIZE],
156    ) -> Result<GetAttestationReportResult, Error> {
157        let dev = sev_guest_device::SevGuestDevice::open().map_err(Error::OpenDevSevGuest)?;
158        let report = dev
159            .get_report(*report_data, 0)
160            .map_err(Error::GetSnpReport)?;
161
162        Ok(GetAttestationReportResult {
163            report: report.as_bytes().to_vec(),
164            key_derivation_svn: Some(KeyDerivationSvn::Snp {
165                tcb_version: report.reported_tcb,
166            }),
167        })
168    }
169
170    /// Key derivation is supported by SNP
171    fn supports_get_derived_key(&self) -> Option<&dyn TeeCallGetDerivedKey> {
172        Some(self)
173    }
174
175    /// Return TeeType::Snp.
176    fn tee_type(&self) -> TeeType {
177        TeeType::Snp
178    }
179}
180
181impl TeeCallGetDerivedKey for SnpCall {
182    /// Get the derived key from /dev/sev-guest.
183    fn get_derived_key(
184        &self,
185        policy: KeyDerivationPolicy,
186    ) -> Result<[u8; HW_DERIVED_KEY_LENGTH], Error> {
187        let KeyDerivationSvn::Snp { tcb_version } = policy.svn else {
188            return Err(Error::KeyDerivationSvnMismatch);
189        };
190
191        let dev = sev_guest_device::SevGuestDevice::open().map_err(Error::OpenDevSevGuest)?;
192
193        // Derive a key mixing in following data:
194        // - GuestPolicy (do not allow different polices to derive same secret)
195        // - Measurement (will not work across release)
196        // - TcbVersion (do not derive same key on older TCB that might have a bug)
197        let guest_field_select = x86defs::snp::GuestFieldSelect::default()
198            .with_guest_policy(true)
199            .with_measurement(policy.mix_measurement)
200            .with_tcb_version(true);
201
202        let derived_key = dev
203            .get_derived_key(
204                0, // VECK
205                guest_field_select.into(),
206                0, // VMPL 0
207                0, // default guest svn to 0
208                tcb_version,
209            )
210            .map_err(Error::GetSnpDerivedKey)?;
211
212        if derived_key.iter().all(|&x| x == 0) {
213            Err(Error::AllZeroKey)?
214        }
215
216        Ok(derived_key)
217    }
218}
219
220/// Implementation of [`TeeCall`] for TDX
221pub struct TdxCall {
222    /// Whether `TD_CTLS.ENABLE_HW_SEAL_KEYS` was successfully set for this TD
223    /// this boot. Unlike SNP (where key derivation is always available), TDX
224    /// hardware-bound seal keys are opt-in at runtime and depend on TDX module
225    /// support, so this is captured at enable time and used to gate
226    /// [`TeeCall::supports_get_derived_key`].
227    hw_seal_keys_enabled: bool,
228}
229
230impl TdxCall {
231    /// Creates a new [`TdxCall`].
232    ///
233    /// * `hw_seal_keys_enabled` - whether `TD_CTLS.ENABLE_HW_SEAL_KEYS` was
234    ///   successfully enabled for this TD, making `TDG.MR.KEY.GET` available.
235    pub fn new(hw_seal_keys_enabled: bool) -> Self {
236        Self {
237            hw_seal_keys_enabled,
238        }
239    }
240}
241
242impl TeeCall for TdxCall {
243    fn get_attestation_report(
244        &self,
245        report_data: &[u8; REPORT_DATA_SIZE],
246    ) -> Result<GetAttestationReportResult, Error> {
247        let dev = tdx_guest_device::TdxGuestDevice::open().map_err(Error::OpenDevTdxGuest)?;
248        let report = dev
249            .get_report(*report_data, 0)
250            .map_err(Error::GetTdxReport)?;
251
252        let mut tee_tcb_svn = [0u8; 16];
253        tee_tcb_svn.copy_from_slice(report.tee_tcb_info.tee_tcb_svn.as_bytes());
254
255        Ok(GetAttestationReportResult {
256            report: report.as_bytes().to_vec(),
257            key_derivation_svn: Some(KeyDerivationSvn::Tdx {
258                tee_tcb_svn,
259                cpu_svn: report.report_mac_struct.cpu_svn,
260            }),
261        })
262    }
263
264    /// Key derivation is supported by TDX via `TDG.MR.KEY.GET`, but only when
265    /// hardware-bound seal keys were successfully enabled for this TD.
266    fn supports_get_derived_key(&self) -> Option<&dyn TeeCallGetDerivedKey> {
267        self.hw_seal_keys_enabled
268            .then_some(self as &dyn TeeCallGetDerivedKey)
269    }
270
271    /// Return TeeType::Tdx.
272    fn tee_type(&self) -> TeeType {
273        TeeType::Tdx
274    }
275}
276
277impl TeeCallGetDerivedKey for TdxCall {
278    /// Get the derived key from /dev/tdx_guest via the `TDG.MR.KEY.GET` TDCALL.
279    fn get_derived_key(
280        &self,
281        policy: KeyDerivationPolicy,
282    ) -> Result<[u8; HW_DERIVED_KEY_LENGTH], Error> {
283        let KeyDerivationSvn::Tdx {
284            tee_tcb_svn,
285            cpu_svn,
286        } = policy.svn
287        else {
288            return Err(Error::KeyDerivationSvnMismatch);
289        };
290
291        // Fail safe for the signer sealing policy on TDX.
292        //
293        // `mix_measurement == false` corresponds to the signer policy, which
294        // asks for a key that is *independent* of the OpenHCL measurement (so it
295        // survives servicing). On SNP this still binds the key to the guest
296        // policy and TCB, but TDX's `TDKEYPOLICY` has no signer/identity
297        // register to substitute for `MRTD`: clearing `MRTD` would leave the key
298        // bound to nothing TD-specific (only the platform seal secret, the
299        // module `TEE_TCB_SVN`, the `CPU_SVN`, and a fixed salt), so any
300        // co-located TD on the same platform+TCB could derive the identical key
301        // and unseal the VMGS DEK. Refuse rather than seal with an under-bound
302        // key; the caller skips
303        // hardware sealing (or fails closed if it is the required source). The
304        // rejection is logged by the higher-level guard in `underhill_attestation`.
305        if !policy.mix_measurement {
306            return Err(Error::TdxSignerPolicyUnsupported);
307        }
308
309        let dev = tdx_guest_device::TdxGuestDevice::open().map_err(Error::OpenDevTdxGuest)?;
310
311        // Build a `TDKEYREQUEST` that binds the derived key to the TD's identity
312        // so that the key is deterministic across boots but unique per-TD. This
313        // mirrors the SNP key derivation, which mixes in the guest measurement
314        // and TCB version.
315        //
316        // - `MRTD` (the build-time measurement) is selected via the key policy;
317        //   the TDX module reads it from the TD's own measurement state.
318        // - `TEE_TCB_SVN` and `CPU_SVN` are the values recorded at seal time
319        //   (verbatim 16-byte report fields). The module requires a valid TCB
320        //   SVN and rejects an all-zero `TEE_TCB_SVN`; supplying the recorded
321        //   values satisfies that check and provides anti-rollback binding on
322        //   both the module and CPU/microcode TCB. Using the recorded values
323        //   (rather than the current report) keeps the derived key stable so a
324        //   previously sealed DEK can still be unsealed.
325        //
326        // The `salt` carries a fixed label so the derived key is domain
327        // separated from any other use of `TDG.MR.KEY.GET`.
328        let mut salt = [0u8; 32];
329        let label = b"TDXHWSEAL";
330        salt[..label.len()].copy_from_slice(label);
331
332        let key_request = x86defs::tdx::TdKeyRequest {
333            key_name: x86defs::tdx::TDX_KEY_NAME_SEAL,
334            sw_key_name: 0,
335            // Request a 256-bit key. `TDX_FEATURES0.SEALKEY_128` enumerates
336            // whether a 128-bit key is *available*; when it is clear, only the
337            // 256-bit key size is valid, so request 256-bit (which also matches
338            // `HW_DERIVED_KEY_LENGTH`).
339            key_size: x86defs::tdx::TDX_KEY_SIZE_256,
340            _reserved0: [0u8; 4],
341            // Always select `MRTD` so the derived key is bound to the TD's
342            // build-time measurement (the analog of SNP mixing in the launch
343            // measurement). Only the hash policy (`mix_measurement == true`)
344            // reaches here; the signer policy is rejected above because TDX has
345            // no identity register to bind to when `MRTD` is cleared.
346            key_policy: x86defs::tdx::TdxKeyPolicy::new().with_mr_td(true),
347            attributes_mask: 0,
348            xfam_mask: 0,
349            // `CPU_SVN` and `TEE_TCB_SVN` recorded at seal time bind the key to
350            // both the CPU/microcode and module TCB for anti-rollback.
351            cpu_svn,
352            tee_tcb_svn,
353            isv_svn: 0,
354            mr_config_svn: 0,
355            mr_owner_config_svn: 0,
356            salt,
357            _reserved1: [0u8; 26],
358        };
359
360        let derived_key = dev
361            .get_derived_key(&key_request)
362            .map_err(Error::GetTdxDerivedKey)?;
363
364        if derived_key.iter().all(|&x| x == 0) {
365            Err(Error::AllZeroKey)?
366        }
367
368        Ok(derived_key)
369    }
370}
371
372/// Implementation of [`TeeCall`] for VBS
373pub struct VbsCall;
374
375impl TeeCall for VbsCall {
376    fn get_attestation_report(
377        &self,
378        report_data: &[u8; REPORT_DATA_SIZE],
379    ) -> Result<GetAttestationReportResult, Error> {
380        let mshv_hvcall = MshvHvcall::new().map_err(Error::OpenDevVbsGuest)?;
381        mshv_hvcall.set_allowed_hypercalls(&[HypercallCode::HvCallVbsVmCallReport]);
382        let report = mshv_hvcall
383            .vbs_vm_call_report(report_data)
384            .map_err(Error::GetVbsReport)?;
385
386        Ok(GetAttestationReportResult {
387            report: report[..hvdef::vbs::VBS_REPORT_SIZE].to_vec(),
388            // Only needed by key derivation, return None for now
389            key_derivation_svn: None,
390        })
391    }
392
393    /// Key derivation is currently not supported by VBS
394    fn supports_get_derived_key(&self) -> Option<&dyn TeeCallGetDerivedKey> {
395        None
396    }
397
398    /// Return TeeType::Vbs.
399    fn tee_type(&self) -> TeeType {
400        TeeType::Vbs
401    }
402}