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