openhcl_attestation_protocol/igvm_attest/
get.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The module helps preparing requests and parsing responses that are
5//! sent to and received from the IGVm agent runs on the host via GET
6//! `IGVM_ATTEST` host request.
7
8use bitfield_struct::bitfield;
9use open_enum::open_enum;
10use zerocopy::FromBytes;
11use zerocopy::Immutable;
12use zerocopy::IntoBytes;
13use zerocopy::KnownLayout;
14
15const ATTESTATION_VERSION: u32 = 2;
16const ATTESTATION_SIGNATURE: u32 = 0x414c4348; // 'HCLA'
17/// The value is based on the maximum report size of the supported isolated VM
18/// Currently it's the size of a SNP report.
19const ATTESTATION_REPORT_SIZE_MAX: usize = SNP_VM_REPORT_SIZE;
20
21pub const VBS_VM_REPORT_SIZE: usize = hvdef::vbs::VBS_REPORT_SIZE;
22pub const SNP_VM_REPORT_SIZE: usize = x86defs::snp::SNP_REPORT_SIZE;
23pub const TDX_VM_REPORT_SIZE: usize = x86defs::tdx::TDX_REPORT_SIZE;
24/// No TEE attestation report for TVM
25pub const TVM_REPORT_SIZE: usize = 0;
26
27const PAGE_SIZE: usize = 4096;
28
29/// Number of pages required by the response buffer of WRAPPED_KEY request
30/// Currently the number matches the maximum value defined by `get_protocol`
31pub const WRAPPED_KEY_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
32/// Number of pages required by the response buffer of KEY_RELEASE request
33/// Currently the number matches the maximum value defined by `get_protocol`
34pub const KEY_RELEASE_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
35/// Number of pages required by the response buffer of AK_CERT request
36/// Currently the AK cert request only requires 1 page.
37pub const AK_CERT_RESPONSE_BUFFER_SIZE: usize = PAGE_SIZE;
38
39/// Current IGVM Attest response header version.
40pub const IGVM_ATTEST_RESPONSE_CURRENT_VERSION: IgvmAttestResponseVersion =
41    IgvmAttestResponseVersion::VERSION_2;
42
43open_enum! {
44    /// IGVM Attest response header versions.
45    #[derive(Default, IntoBytes, Immutable, KnownLayout, FromBytes)]
46    pub enum IgvmAttestResponseVersion: u32 {
47        /// Version 1
48        VERSION_1 = 1,
49        /// Version 2
50        VERSION_2 = 2,
51    }
52}
53
54/// Current IGVM Attest request header version.
55pub const IGVM_ATTEST_REQUEST_CURRENT_VERSION: IgvmAttestRequestVersion =
56    IgvmAttestRequestVersion::VERSION_2;
57
58open_enum! {
59    /// IGVM Attest request header versions.
60    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
61    pub enum IgvmAttestRequestVersion: u32 {
62        /// Version 1
63        VERSION_1 = 1,
64        /// Version 2
65        VERSION_2 = 2,
66    }
67}
68
69/// Request base structure (C-style)
70/// The struct (includes the appended [`runtime_claims::RuntimeClaims`]) also serves as the
71/// attestation report in vTPM guest attestation.
72#[repr(C)]
73#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
74pub struct IgvmAttestRequestBase {
75    /// Header (unmeasured)
76    pub header: IgvmAttestRequestHeader,
77    /// TEE attestation report
78    pub attestation_report: [u8; ATTESTATION_REPORT_SIZE_MAX],
79    /// Request data (unmeasured)
80    pub request_data: IgvmAttestRequestData,
81    // Data to be appended at the end of the struct:
82    // - Optional Extended request data [`IgvmAttestRequestDataExt`] (request version 2+).
83    // - Variable-length [`runtime_claims::RuntimeClaims`] (JSON string)
84    //   The hash of [`runtime_claims::RuntimeClaims`] in [`IgvmAttestHashType`] will be captured
85    //   in the `report_data` or equivalent field of the TEE attestation report.
86}
87
88open_enum! {
89    /// TEE attestation report type (C-style enum)
90    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
91    pub enum IgvmAttestReportType: u32 {
92        /// Invalid report
93        INVALID_REPORT = 0,
94        /// VBS report
95        VBS_VM_REPORT = 1,
96        /// SNP report
97        SNP_VM_REPORT = 2,
98        /// Trusted VM report
99        TVM_REPORT = 3,
100        /// TDX report
101        TDX_VM_REPORT = 4,
102    }
103}
104
105open_enum! {
106    /// Request type (C-style enum)
107    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
108    pub enum IgvmAttestRequestType: u32 {
109        /// Invalid request
110        INVALID_REQUEST = 0,
111        /// Request for getting wrapped key from AKV.
112        KEY_RELEASE_REQUEST = 1,
113        /// Request to getting attestation key certificate.
114        AK_CERT_REQUEST = 2,
115        /// Request for getting VMMD blob from CPS.
116        WRAPPED_KEY_REQUEST = 3,
117    }
118}
119
120open_enum! {
121    /// Hash algorithm used for content of report data (C-style enum)
122    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
123    pub enum IgvmAttestHashType: u32 {
124        /// Invalid hash
125        INVALID_HASH = 0,
126        /// SHA-256
127        SHA_256 = 1,
128        /// SHA-384
129        SHA_384 = 2,
130        /// SHA-512
131        SHA_512 = 3,
132    }
133}
134
135/// Unmeasured data used to provide transport sanity and versioning (C-style struct)
136#[repr(C)]
137#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
138pub struct IgvmAttestRequestHeader {
139    /// Signature
140    pub signature: u32,
141    /// Version
142    pub version: u32,
143    /// Report size
144    pub report_size: u32,
145    /// Request type
146    pub request_type: IgvmAttestRequestType,
147    /// Status
148    pub status: u32,
149    /// Reserved
150    pub reserved: [u32; 3],
151}
152
153impl IgvmAttestRequestHeader {
154    /// Create an `HardwareKeyProtectorHeader` instance.
155    pub fn new(report_size: u32, request_type: IgvmAttestRequestType, status: u32) -> Self {
156        Self {
157            signature: ATTESTATION_SIGNATURE,
158            version: ATTESTATION_VERSION,
159            report_size,
160            request_type,
161            status,
162            reserved: [0u32; 3],
163        }
164    }
165}
166
167/// Bitmap of additional Igvm request attributes.
168/// 0 - error_code: Requesting IGVM Agent Error code
169/// 1 - retry: Retry preference
170#[bitfield(u32)]
171#[derive(IntoBytes, FromBytes, Immutable, KnownLayout)]
172pub struct IgvmCapabilityBitMap {
173    pub error_code: bool,
174    pub retry: bool,
175    #[bits(30)]
176    _reserved: u32,
177}
178
179/// Unmeasured user data, used for host attestation requests (C-style struct)
180#[repr(C)]
181#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
182pub struct IgvmAttestRequestData {
183    /// Data size
184    pub data_size: u32,
185    /// Version
186    pub version: IgvmAttestRequestVersion,
187    /// Report type
188    pub report_type: IgvmAttestReportType,
189    /// Report data hash type
190    pub report_data_hash_type: IgvmAttestHashType,
191    /// Size of the appended raw runtime claims
192    pub variable_data_size: u32,
193}
194
195impl IgvmAttestRequestData {
196    /// Create an `IgvmAttestRequestData` instance.
197    pub fn new(
198        version: IgvmAttestRequestVersion,
199        data_size: u32,
200        report_type: IgvmAttestReportType,
201        report_data_hash_type: IgvmAttestHashType,
202        variable_data_size: u32,
203    ) -> Self {
204        Self {
205            data_size,
206            version,
207            report_type,
208            report_data_hash_type,
209            variable_data_size,
210        }
211    }
212}
213
214/// Unmeasured user data appended to `IgvmAttestRequestData` for version 2+,
215/// used for host attestation requests (C-style struct).
216#[repr(C)]
217#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
218pub struct IgvmAttestRequestDataExt {
219    /// Bitmap of additional requested attributes
220    pub capability_bitmap: IgvmCapabilityBitMap,
221}
222
223impl IgvmAttestRequestDataExt {
224    /// Create an `IgvmAttestRequestDataExt` instance.
225    pub fn new(capability_bitmap: IgvmCapabilityBitMap) -> Self {
226        Self { capability_bitmap }
227    }
228}
229
230/// Bitmap indicates a signal to requestor
231/// 0 - IGVM_SIGNAL_RETRY_RCOMMENDED_BIT: Retry recommendation
232#[bitfield(u32)]
233#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
234pub struct IgvmSignal {
235    pub retry: bool,
236    #[bits(31)]
237    _reserved: u32,
238}
239
240/// The common response header that comply with both V1 and V2 Igvm attest response
241#[repr(C)]
242#[derive(Default, Debug, IntoBytes, FromBytes)]
243pub struct IgvmAttestCommonResponseHeader {
244    /// Data size
245    pub data_size: u32,
246    /// Version
247    pub version: IgvmAttestResponseVersion,
248}
249
250/// The response header for `IGVM_ERROR_INFO` (C-style struct)
251#[repr(C)]
252#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
253pub struct IgvmErrorInfo {
254    /// ErrorCode propagated from IgvmAgent
255    pub error_code: u32,
256    /// HttpStatusCode propagated from IgvmAgent that enhances the ErrorCode
257    pub http_status_code: u32,
258    /// Igvm signal from response
259    pub igvm_signal: IgvmSignal,
260    /// Reserved
261    pub reserved: [u32; 3],
262}
263
264/// The response header for `KEY_RELEASE_REQUEST` (C-style struct)
265#[repr(C)]
266#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
267pub struct IgvmAttestKeyReleaseResponseHeader {
268    /// Data size
269    pub data_size: u32,
270    /// Version
271    pub version: IgvmAttestResponseVersion,
272    /// IgvmErrorInfo that contains RPC result and retry recommendation
273    pub error_info: IgvmErrorInfo,
274}
275
276/// The response header for `WRAPPED_KEY_REQUEST` (C-style struct)
277/// Currently the definition is the same as [`IgvmAttestKeyReleaseResponseHeader`].
278#[repr(C)]
279#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
280pub struct IgvmAttestWrappedKeyResponseHeader {
281    /// Data size
282    pub data_size: u32,
283    /// Version
284    pub version: IgvmAttestResponseVersion,
285    /// IgvmErrorInfo that contains RPC result and retry recommendation
286    pub error_info: IgvmErrorInfo,
287}
288
289/// The response header for `AK_CERT_REQUEST` (C-style struct)
290#[repr(C)]
291#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
292pub struct IgvmAttestAkCertResponseHeader {
293    /// Data size
294    pub data_size: u32,
295    /// Version
296    pub version: IgvmAttestResponseVersion,
297    /// IgvmErrorInfo that contains RPC result and retry recommendation
298    pub error_info: IgvmErrorInfo,
299}
300
301/// Definition of the runt-time claims, which will be appended to the
302/// `IgvmAttestRequestBase` in raw bytes.
303pub mod runtime_claims {
304    use base64_serde::base64_serde_type;
305    use mesh::MeshPayload;
306    use serde::Deserialize;
307    use serde::Serialize;
308
309    base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
310
311    /// Measured runtime claim in JSON format.
312    /// The hash of the data is expected be put into the user_data field of
313    /// the attestation report.
314    #[derive(Debug, Deserialize, Serialize)]
315    #[serde(rename_all = "kebab-case")]
316    pub struct RuntimeClaims {
317        /// An array of [`RsaJwk`]
318        pub keys: Vec<RsaJwk>,
319        /// VM configuration
320        pub vm_configuration: AttestationVmConfig,
321        /// Optional user data
322        #[serde(default, skip_serializing_if = "String::is_empty")]
323        pub user_data: String,
324    }
325
326    impl RuntimeClaims {
327        /// Create runtime claims for `KEY_RELEASE_REQUEST`.
328        pub fn key_release_request_runtime_claims(
329            exponent: &[u8],
330            modulus: &[u8],
331            attestation_vm_config: &AttestationVmConfig,
332        ) -> Self {
333            let transfer_key_jwks = RsaJwk::get_transfer_key_jwks(exponent, modulus);
334            Self {
335                keys: transfer_key_jwks,
336                vm_configuration: attestation_vm_config.clone(),
337                user_data: "".to_string(),
338            }
339        }
340
341        /// Helper function for creating runtime claims of `AK_CERT_REQUEST`.
342        pub fn ak_cert_runtime_claims(
343            ak_pub_exponent: &[u8],
344            ak_pub_modulus: &[u8],
345            ek_pub_exponent: &[u8],
346            ek_pub_modulus: &[u8],
347            attestation_vm_config: &AttestationVmConfig,
348            user_data: &[u8],
349        ) -> Self {
350            let tpm_jwks = RsaJwk::get_tpm_jwks(
351                ak_pub_exponent,
352                ak_pub_modulus,
353                ek_pub_exponent,
354                ek_pub_modulus,
355            );
356            Self {
357                keys: tpm_jwks,
358                vm_configuration: attestation_vm_config.clone(),
359                user_data: hex::encode(user_data),
360            }
361        }
362    }
363
364    /// JWK for an RSA key
365    #[derive(Debug, Deserialize, Serialize)]
366    pub struct RsaJwk {
367        /// Key id
368        pub kid: String,
369        /// Key operations
370        pub key_ops: Vec<String>,
371        /// Key type
372        pub kty: String,
373        /// RSA public exponent
374        #[serde(with = "Base64Url")]
375        pub e: Vec<u8>,
376        /// RSA public modulus
377        #[serde(with = "Base64Url")]
378        pub n: Vec<u8>,
379    }
380
381    impl RsaJwk {
382        /// Create a JWKS from inputs.
383        pub fn get_transfer_key_jwks(exponent: &[u8], modulus: &[u8]) -> Vec<RsaJwk> {
384            let jwk = RsaJwk {
385                kid: "HCLTransferKey".to_string(),
386                key_ops: vec!["encrypt".to_string()],
387                kty: "RSA".to_string(),
388                e: exponent.to_vec(),
389                n: modulus.to_vec(),
390            };
391
392            vec![jwk]
393        }
394
395        /// Create a JWKS from inputs.
396        pub fn get_tpm_jwks(
397            ak_pub_exponent: &[u8],
398            ak_pub_modulus: &[u8],
399            ek_pub_exponent: &[u8],
400            ek_pub_modulus: &[u8],
401        ) -> Vec<RsaJwk> {
402            let ak_pub = RsaJwk {
403                kid: "HCLAkPub".to_string(),
404                key_ops: vec!["sign".to_string()],
405                kty: "RSA".to_string(),
406                e: ak_pub_exponent.to_vec(),
407                n: ak_pub_modulus.to_vec(),
408            };
409            let ek_pub = RsaJwk {
410                kid: "HCLEkPub".to_string(),
411                key_ops: vec!["encrypt".to_string()],
412                kty: "RSA".to_string(),
413                e: ek_pub_exponent.to_vec(),
414                n: ek_pub_modulus.to_vec(),
415            };
416
417            vec![ak_pub, ek_pub]
418        }
419    }
420
421    /// VM configuration to be included in the `RuntimeClaims`.
422    #[derive(Clone, Debug, Deserialize, Serialize, MeshPayload)]
423    #[serde(rename_all = "kebab-case")]
424    pub struct AttestationVmConfig {
425        /// Time stamp
426        #[serde(skip_serializing_if = "Option::is_none")]
427        pub current_time: Option<i64>,
428        /// Base64-encoded hash of the provisioning cert
429        pub root_cert_thumbprint: String,
430        /// Whether the serial console is enabled
431        pub console_enabled: bool,
432        /// Whether secure boot is enabled
433        pub secure_boot: bool,
434        /// Whether the TPM is enabled
435        pub tpm_enabled: bool,
436        /// Whether the TPM states is persisted
437        pub tpm_persisted: bool,
438        /// Whether certain vPCI devices are allowed through the device filter
439        pub filtered_vpci_devices_allowed: bool,
440        /// VM id
441        #[serde(rename = "vmUniqueId")]
442        pub vm_unique_id: String,
443    }
444}