Skip to main content

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        /// CCA report
103        CCA_VM_REPORT = 5,
104    }
105}
106
107open_enum! {
108    /// Request type (C-style enum)
109    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
110    pub enum IgvmAttestRequestType: u32 {
111        /// Invalid request
112        INVALID_REQUEST = 0,
113        /// Request for getting wrapped key from AKV.
114        KEY_RELEASE_REQUEST = 1,
115        /// Request to getting attestation key certificate.
116        AK_CERT_REQUEST = 2,
117        /// Request for getting VMMD blob from CPS.
118        WRAPPED_KEY_REQUEST = 3,
119    }
120}
121
122open_enum! {
123    /// Hash algorithm used for content of report data (C-style enum)
124    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
125    pub enum IgvmAttestHashType: u32 {
126        /// Invalid hash
127        INVALID_HASH = 0,
128        /// SHA-256
129        SHA_256 = 1,
130        /// SHA-384
131        SHA_384 = 2,
132        /// SHA-512
133        SHA_512 = 3,
134    }
135}
136
137/// Unmeasured data used to provide transport sanity and versioning (C-style struct)
138#[repr(C)]
139#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
140pub struct IgvmAttestRequestHeader {
141    /// Signature
142    pub signature: u32,
143    /// Version
144    pub version: u32,
145    /// Report size
146    pub report_size: u32,
147    /// Request type
148    pub request_type: IgvmAttestRequestType,
149    /// Status
150    pub status: u32,
151    /// Reserved
152    pub reserved: [u32; 3],
153}
154
155impl IgvmAttestRequestHeader {
156    /// Create an `HardwareKeyProtectorHeader` instance.
157    pub fn new(report_size: u32, request_type: IgvmAttestRequestType, status: u32) -> Self {
158        Self {
159            signature: ATTESTATION_SIGNATURE,
160            version: ATTESTATION_VERSION,
161            report_size,
162            request_type,
163            status,
164            reserved: [0u32; 3],
165        }
166    }
167}
168
169/// Bitmap of additional Igvm request attributes.
170/// 0 - error_code: Requesting IGVM Agent Error code
171/// 1 - retry: Retry preference
172/// 2 - skip_hw_unsealing: Skip hardware unsealing in case key release request fails
173/// 3 - use_rsa_aes_key_wrap_384: Request that the IGVM Agent ask Azure Key Vault
174///     (AKV) to wrap and release the key with the SHA-384 variant of the
175///     composite RSA+AES key-wrap scheme (PKCS#11 CKM_RSA_AES_KEY_WRAP /
176///     AKV's RSA_AES_KEY_WRAP_384): RSA-OAEP-SHA384 (MGF1-SHA-384) wraps an
177///     AES-256 KEK that performs AES Key Wrap on the released key. The default
178///     is the same CKM_RSA_AES_KEY_WRAP scheme with the inner RSA-OAEP using
179///     SHA-1 (MGF1-SHA-1).
180/// 4 - corim_endorsement: Request that the IGVM Agent fetch the CoRIM launch
181///     endorsement for the guest and include it in the attestation flow.
182#[bitfield(u32)]
183#[derive(IntoBytes, FromBytes, Immutable, KnownLayout)]
184pub struct IgvmCapabilityBitMap {
185    pub error_code: bool,
186    pub retry: bool,
187    pub skip_hw_unsealing: bool,
188    pub use_rsa_aes_key_wrap_384: bool,
189    pub corim_endorsement: bool,
190    #[bits(27)]
191    _reserved: u32,
192}
193
194/// Unmeasured user data, used for host attestation requests (C-style struct)
195#[repr(C)]
196#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
197pub struct IgvmAttestRequestData {
198    /// Data size
199    pub data_size: u32,
200    /// Version
201    pub version: IgvmAttestRequestVersion,
202    /// Report type
203    pub report_type: IgvmAttestReportType,
204    /// Report data hash type
205    pub report_data_hash_type: IgvmAttestHashType,
206    /// Size of the appended raw runtime claims
207    pub variable_data_size: u32,
208}
209
210impl IgvmAttestRequestData {
211    /// Create an `IgvmAttestRequestData` instance.
212    pub fn new(
213        version: IgvmAttestRequestVersion,
214        data_size: u32,
215        report_type: IgvmAttestReportType,
216        report_data_hash_type: IgvmAttestHashType,
217        variable_data_size: u32,
218    ) -> Self {
219        Self {
220            data_size,
221            version,
222            report_type,
223            report_data_hash_type,
224            variable_data_size,
225        }
226    }
227}
228
229/// Unmeasured user data appended to `IgvmAttestRequestData` for version 2+,
230/// used for host attestation requests (C-style struct).
231#[repr(C)]
232#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
233pub struct IgvmAttestRequestDataExt {
234    /// Bitmap of additional requested attributes
235    pub capability_bitmap: IgvmCapabilityBitMap,
236}
237
238impl IgvmAttestRequestDataExt {
239    /// Create an `IgvmAttestRequestDataExt` instance.
240    pub fn new(capability_bitmap: IgvmCapabilityBitMap) -> Self {
241        Self { capability_bitmap }
242    }
243}
244
245/// Bitmap indicates a signal to requestor
246/// 0 - IGVM_SIGNAL_RETRY_RECOMMENDED_BIT: Retry recommendation
247/// 1 - IGVM_SIGNAL_SKIP_HW_UNSEALING_RECOMMENDED_BIT: Skip hardware unsealing
248/// 2 - IGVM_SIGNAL_RSA_AES_KEY_WRAP_384_USED_BIT: Set by the IGVM Agent to
249///     indicate that the agent asked AKV for the SHA-384 variant
250///     (RSA_AES_KEY_WRAP_384) and AKV used it to wrap the key in the payload.
251///     If this bit is clear, AKV wrapped the key with the default
252///     CKM_RSA_AES_KEY_WRAP scheme (inner RSA-OAEP using SHA-1).
253/// 3 - IGVM_SIGNAL_CORIM_ENDORSEMENT_REQUESTED_BIT: Set by the IGVM Agent to
254///     indicate that the agent requested the CoRIM endorsement.
255#[bitfield(u32)]
256#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
257pub struct IgvmSignal {
258    pub retry: bool,
259    pub skip_hw_unsealing: bool,
260    pub rsa_aes_key_wrap_384_used: bool,
261    pub corim_endorsement_requested: bool,
262    #[bits(28)]
263    _reserved: u32,
264}
265
266/// The common response header that comply with both V1 and V2 Igvm attest response
267#[repr(C)]
268#[derive(Default, Debug, IntoBytes, FromBytes)]
269pub struct IgvmAttestCommonResponseHeader {
270    /// Data size
271    pub data_size: u32,
272    /// Version
273    pub version: IgvmAttestResponseVersion,
274}
275
276/// The response header for `IGVM_ERROR_INFO` (C-style struct)
277#[repr(C)]
278#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
279pub struct IgvmErrorInfo {
280    /// ErrorCode propagated from IgvmAgent
281    pub error_code: u32,
282    /// HttpStatusCode propagated from IgvmAgent that enhances the ErrorCode
283    pub http_status_code: u32,
284    /// Igvm signal from response
285    pub igvm_signal: IgvmSignal,
286    /// Reserved
287    pub reserved: [u32; 3],
288}
289
290/// The response header for `KEY_RELEASE_REQUEST` (C-style struct)
291#[repr(C)]
292#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
293pub struct IgvmAttestKeyReleaseResponseHeader {
294    /// Data size
295    pub data_size: u32,
296    /// Version
297    pub version: IgvmAttestResponseVersion,
298    /// IgvmErrorInfo that contains RPC result and retry recommendation
299    pub error_info: IgvmErrorInfo,
300}
301
302/// The response header for `WRAPPED_KEY_REQUEST` (C-style struct)
303/// Currently the definition is the same as [`IgvmAttestKeyReleaseResponseHeader`].
304#[repr(C)]
305#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
306pub struct IgvmAttestWrappedKeyResponseHeader {
307    /// Data size
308    pub data_size: u32,
309    /// Version
310    pub version: IgvmAttestResponseVersion,
311    /// IgvmErrorInfo that contains RPC result and retry recommendation
312    pub error_info: IgvmErrorInfo,
313}
314
315/// The response header for `AK_CERT_REQUEST` (C-style struct)
316#[repr(C)]
317#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
318pub struct IgvmAttestAkCertResponseHeader {
319    /// Data size
320    pub data_size: u32,
321    /// Version
322    pub version: IgvmAttestResponseVersion,
323    /// IgvmErrorInfo that contains RPC result and retry recommendation
324    pub error_info: IgvmErrorInfo,
325}
326
327/// Definition of the runt-time claims, which will be appended to the
328/// `IgvmAttestRequestBase` in raw bytes.
329pub mod runtime_claims {
330    use base64_serde::base64_serde_type;
331    use guid::Guid;
332    use mesh::MeshPayload;
333    use serde::Deserialize;
334    use serde::Serialize;
335
336    base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
337
338    /// Measured runtime claim in JSON format.
339    /// The hash of the data is expected be put into the user_data field of
340    /// the attestation report.
341    #[derive(Debug, Deserialize, Serialize)]
342    #[serde(rename_all = "kebab-case")]
343    pub struct RuntimeClaims {
344        /// An array of [`RsaJwk`]
345        pub keys: Vec<RsaJwk>,
346        /// VM configuration
347        pub vm_configuration: AttestationVmConfig,
348        /// Optional user data
349        #[serde(default, skip_serializing_if = "String::is_empty")]
350        pub user_data: String,
351    }
352
353    impl RuntimeClaims {
354        /// Create runtime claims for `KEY_RELEASE_REQUEST`.
355        pub fn key_release_request_runtime_claims(
356            exponent: &[u8],
357            modulus: &[u8],
358            attestation_vm_config: &AttestationVmConfig,
359        ) -> Self {
360            let transfer_key_jwks = RsaJwk::get_transfer_key_jwks(exponent, modulus);
361            Self {
362                keys: transfer_key_jwks,
363                vm_configuration: attestation_vm_config.clone(),
364                user_data: "".to_string(),
365            }
366        }
367
368        /// Helper function for creating runtime claims of `AK_CERT_REQUEST`.
369        pub fn ak_cert_runtime_claims(
370            ak_pub_exponent: &[u8],
371            ak_pub_modulus: &[u8],
372            ek_pub_exponent: &[u8],
373            ek_pub_modulus: &[u8],
374            attestation_vm_config: &AttestationVmConfig,
375            user_data: &[u8],
376        ) -> Self {
377            let tpm_jwks = RsaJwk::get_tpm_jwks(
378                ak_pub_exponent,
379                ak_pub_modulus,
380                ek_pub_exponent,
381                ek_pub_modulus,
382            );
383            Self {
384                keys: tpm_jwks,
385                vm_configuration: attestation_vm_config.clone(),
386                user_data: hex::encode(user_data),
387            }
388        }
389    }
390
391    /// JWK for an RSA key
392    #[derive(Debug, Deserialize, Serialize)]
393    pub struct RsaJwk {
394        /// Key id
395        pub kid: String,
396        /// Key operations
397        pub key_ops: Vec<String>,
398        /// Key type
399        pub kty: String,
400        /// RSA public exponent
401        #[serde(with = "Base64Url")]
402        pub e: Vec<u8>,
403        /// RSA public modulus
404        #[serde(with = "Base64Url")]
405        pub n: Vec<u8>,
406    }
407
408    impl RsaJwk {
409        /// Create a JWKS from inputs.
410        pub fn get_transfer_key_jwks(exponent: &[u8], modulus: &[u8]) -> Vec<RsaJwk> {
411            let jwk = RsaJwk {
412                kid: "HCLTransferKey".to_string(),
413                key_ops: vec!["encrypt".to_string()],
414                kty: "RSA".to_string(),
415                e: exponent.to_vec(),
416                n: modulus.to_vec(),
417            };
418
419            vec![jwk]
420        }
421
422        /// Create a JWKS from inputs.
423        pub fn get_tpm_jwks(
424            ak_pub_exponent: &[u8],
425            ak_pub_modulus: &[u8],
426            ek_pub_exponent: &[u8],
427            ek_pub_modulus: &[u8],
428        ) -> Vec<RsaJwk> {
429            let ak_pub = RsaJwk {
430                kid: "HCLAkPub".to_string(),
431                key_ops: vec!["sign".to_string()],
432                kty: "RSA".to_string(),
433                e: ak_pub_exponent.to_vec(),
434                n: ak_pub_modulus.to_vec(),
435            };
436            let ek_pub = RsaJwk {
437                kid: "HCLEkPub".to_string(),
438                key_ops: vec!["encrypt".to_string()],
439                kty: "RSA".to_string(),
440                e: ek_pub_exponent.to_vec(),
441                n: ek_pub_modulus.to_vec(),
442            };
443
444            vec![ak_pub, ek_pub]
445        }
446    }
447
448    /// Claims for VMGS provenance.
449    #[derive(Clone, Debug, Deserialize, Serialize, MeshPayload)]
450    #[serde(rename_all = "kebab-case")]
451    pub struct VmgsProvisioner {
452        /// VMGS ID
453        #[serde(with = "serde_helpers::as_string")]
454        pub id: Guid,
455        /// Signer (root cert thumbprint + leaf subject name as a decentralized
456        /// identifier)
457        pub signer: String,
458    }
459
460    /// Supported hardware sealing policy
461    #[derive(Clone, Copy, Debug, Deserialize, Serialize, MeshPayload)]
462    pub enum HardwareSealingPolicy {
463        #[serde(rename = "none")]
464        None,
465        #[serde(rename = "hash")]
466        Hash,
467        #[serde(rename = "signer")]
468        Signer,
469    }
470
471    /// TPM reference implementation version.
472    #[derive(Clone, Copy, Debug, Deserialize, Serialize, MeshPayload)]
473    pub enum AttestationTpmVersion {
474        /// TPM reference implementation version 1.38
475        #[serde(rename = "1.38")]
476        V138,
477        /// TPM reference implementation version 1.85
478        #[serde(rename = "185")]
479        V185,
480    }
481
482    /// VM configuration to be included in the `RuntimeClaims`.
483    #[derive(Clone, Debug, Deserialize, Serialize, MeshPayload)]
484    #[serde(rename_all = "kebab-case")]
485    pub struct AttestationVmConfig {
486        /// Time stamp
487        #[serde(skip_serializing_if = "Option::is_none")]
488        pub current_time: Option<i64>,
489        /// Base64-encoded hash of the provisioning cert
490        pub root_cert_thumbprint: String,
491        /// Whether the serial console is enabled
492        pub console_enabled: bool,
493        /// Whether the serial console, if enabled, is interactive
494        pub interactive_console_enabled: bool,
495        /// Whether secure boot is enabled
496        pub secure_boot: bool,
497        /// Whether the TPM is enabled
498        pub tpm_enabled: bool,
499        /// TPM reference implementation version
500        pub tpm_version: AttestationTpmVersion,
501        /// Whether the VM is in stateful mode (i.e. attestation is not
502        /// suppressed).
503        ///
504        /// NOTE: This is a legacy field. Its name (`tpm-persisted` on the wire)
505        /// predates stateless + hardware sealing and does NOT describe whether
506        /// TPM state is actually persisted to the VMGS at runtime — that broader
507        /// decision is made by the VMM (see `no_persistent_secrets` in
508        /// `underhill_core`). The name and value semantics are kept unchanged to
509        /// preserve the attestation runtime-claims contract and the
510        /// hardware-derived key KDF input.
511        pub tpm_persisted: bool,
512        /// Whether certain vPCI devices are allowed through the device filter
513        pub filtered_vpci_devices_allowed: bool,
514        /// VM id
515        #[serde(rename = "vmUniqueId")]
516        pub vm_unique_id: String,
517        /// VMGS provenance data
518        #[serde(skip_serializing_if = "Option::is_none")]
519        pub vmgs_provisioner: Option<VmgsProvisioner>,
520        /// Hardware sealing policy
521        pub hardware_sealing_policy: HardwareSealingPolicy,
522    }
523
524    impl Default for AttestationVmConfig {
525        fn default() -> Self {
526            Self {
527                current_time: None,
528                root_cert_thumbprint: String::new(),
529                console_enabled: false,
530                interactive_console_enabled: false,
531                secure_boot: false,
532                tpm_enabled: true,
533                tpm_version: AttestationTpmVersion::V138,
534                tpm_persisted: true,
535                filtered_vpci_devices_allowed: false,
536                vm_unique_id: String::new(),
537                vmgs_provisioner: None,
538                hardware_sealing_policy: HardwareSealingPolicy::None,
539            }
540        }
541    }
542}