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}