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 open_enum::open_enum;
9use zerocopy::FromBytes;
10use zerocopy::Immutable;
11use zerocopy::IntoBytes;
12use zerocopy::KnownLayout;
13
14const ATTESTATION_VERSION: u32 = 2;
15const ATTESTATION_SIGNATURE: u32 = 0x414c4348; // 'HCLA'
16/// The value is based on the maximum report size of the supported isolated VM
17/// Currently it's the size of a SNP report.
18const ATTESTATION_REPORT_SIZE_MAX: usize = SNP_VM_REPORT_SIZE;
19
20pub const VBS_VM_REPORT_SIZE: usize = 0x230;
21pub const SNP_VM_REPORT_SIZE: usize = sev_guest_device::protocol::SNP_REPORT_SIZE;
22pub const TDX_VM_REPORT_SIZE: usize = tdx_guest_device::protocol::TDX_REPORT_SIZE;
23/// No TEE attestation report for TVM
24pub const TVM_REPORT_SIZE: usize = 0;
25
26const PAGE_SIZE: usize = 4096;
27
28/// Number of pages required by the response buffer of WRAPPED_KEY request
29/// Currently the number matches the maximum value defined by `get_protocol`
30pub const WRAPPED_KEY_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
31/// Number of pages required by the response buffer of KEY_RELEASE request
32/// Currently the number matches the maximum value defined by `get_protocol`
33pub const KEY_RELEASE_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
34/// Number of pages required by the response buffer of AK_CERT request
35/// Currently the AK cert request only requires 1 page.
36pub const AK_CERT_RESPONSE_BUFFER_SIZE: usize = PAGE_SIZE;
37
38/// Current AK cert response header version
39pub const AK_CERT_RESPONSE_HEADER_VERSION: u32 = 1;
40
41/// Request structure (C-style)
42/// The struct (includes the appended [`runtime_claims::RuntimeClaims`]) also serves as the
43/// attestation report in vTPM guest attestation.
44#[repr(C)]
45#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
46pub struct IgvmAttestRequest {
47    /// Header (unmeasured)
48    pub header: IgvmAttestRequestHeader,
49    /// TEE attestation report
50    pub attestation_report: [u8; ATTESTATION_REPORT_SIZE_MAX],
51    /// Request data (unmeasured)
52    pub request_data: IgvmAttestRequestData,
53    // Variable-length [`runtime_claims::RuntimeClaims`] (JSON string) in raw bytes will be
54    // appended to here.
55    // The hash of [`runtime_claims::RuntimeClaims`] in [`IgvmAttestHashType`] will be captured
56    // in the `report_data` or equivalent field of the TEE attestation report.
57}
58
59open_enum! {
60    /// TEE attestation report type (C-style enum)
61    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
62    pub enum IgvmAttestReportType: u32 {
63        /// Invalid report
64        INVALID_REPORT = 0,
65        /// VBS report
66        VBS_VM_REPORT = 1,
67        /// SNP report
68        SNP_VM_REPORT = 2,
69        /// Trusted VM report
70        TVM_REPORT = 3,
71        /// TDX report
72        TDX_VM_REPORT = 4,
73    }
74}
75
76open_enum! {
77    /// Request type (C-style enum)
78    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
79    pub enum IgvmAttestRequestType: u32 {
80        /// Invalid request
81        INVALID_REQUEST = 0,
82        /// Request for getting wrapped key from AKV.
83        KEY_RELEASE_REQUEST = 1,
84        /// Request to getting attestation key certificate.
85        AK_CERT_REQUEST = 2,
86        /// Request for getting VMMD blob from CPS.
87        WRAPPED_KEY_REQUEST = 3,
88    }
89}
90
91open_enum! {
92    /// Hash algorithm used for content of report data (C-style enum)
93    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
94    pub enum IgvmAttestHashType: u32 {
95        /// Invalid hash
96        INVALID_HASH = 0,
97        /// SHA-256
98        SHA_256 = 1,
99        /// SHA-384
100        SHA_384 = 2,
101        /// SHA-512
102        SHA_512 = 3,
103    }
104}
105
106/// Unmeasured data used to provide transport sanity and versioning (C-style struct)
107#[repr(C)]
108#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
109pub struct IgvmAttestRequestHeader {
110    /// Signature
111    pub signature: u32,
112    /// Version
113    pub version: u32,
114    /// Report size
115    pub report_size: u32,
116    /// Request type
117    pub request_type: IgvmAttestRequestType,
118    /// Status
119    pub status: u32,
120    /// Reserved
121    pub reserved: [u32; 3],
122}
123
124impl IgvmAttestRequestHeader {
125    /// Create an `HardwareKeyProtectorHeader` instance.
126    pub fn new(report_size: u32, request_type: IgvmAttestRequestType, status: u32) -> Self {
127        Self {
128            signature: ATTESTATION_SIGNATURE,
129            version: ATTESTATION_VERSION,
130            report_size,
131            request_type,
132            status,
133            reserved: [0u32; 3],
134        }
135    }
136}
137
138const IGVM_ATTEST_VERSION_CURRENT: u32 = 1;
139
140/// Unmeasured user data, used for host attestation requests (C-style struct)
141#[repr(C)]
142#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
143pub struct IgvmAttestRequestData {
144    /// Data size
145    pub data_size: u32,
146    /// Version
147    pub version: u32,
148    /// Report type
149    pub report_type: IgvmAttestReportType,
150    /// Report data hash type
151    pub report_data_hash_type: IgvmAttestHashType,
152    /// Size of the appended raw runtime claims
153    pub variable_data_size: u32,
154}
155
156impl IgvmAttestRequestData {
157    /// Create an `IgvmAttestRequestData` instance.
158    pub fn new(
159        data_size: u32,
160        report_type: IgvmAttestReportType,
161        report_data_hash_type: IgvmAttestHashType,
162        variable_data_size: u32,
163    ) -> Self {
164        Self {
165            data_size,
166            version: IGVM_ATTEST_VERSION_CURRENT,
167            report_type,
168            report_data_hash_type,
169            variable_data_size,
170        }
171    }
172}
173
174/// The response header for `KEY_RELEASE_REQUEST` (C-style struct)
175///
176/// reSearch query: `IGVM_KEY_MESSAGE_HEADER`
177#[repr(C)]
178#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
179pub struct IgvmAttestKeyReleaseResponseHeader {
180    /// Data size
181    pub data_size: u32,
182    /// Version
183    pub version: u32,
184}
185
186/// The response header for `WRAPPED_KEY_REQUEST` (C-style struct)
187/// Currently the definition is the same as [`IgvmAttestKeyReleaseResponseHeader`].
188///
189/// reSearch query: `IGVM_KEY_MESSAGE_HEADER`
190#[repr(C)]
191#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
192pub struct IgvmAttestWrappedKeyResponseHeader {
193    /// Data size
194    pub data_size: u32,
195    /// Version
196    pub version: u32,
197}
198
199/// The response header for `AK_CERT_REQUEST` (C-style struct)
200///
201/// reSearch query: `IGVM_CERT_MESSAGE_HEADER`
202#[repr(C)]
203#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
204pub struct IgvmAttestAkCertResponseHeader {
205    /// Data size
206    pub data_size: u32,
207    /// Version
208    pub version: u32,
209}
210
211/// Definition of the runt-time claims, which will be appended to the
212/// `IgvmAttestRequest` in raw bytes.
213pub mod runtime_claims {
214    use base64_serde::base64_serde_type;
215    use mesh::MeshPayload;
216    use serde::Deserialize;
217    use serde::Serialize;
218
219    base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
220
221    /// Measured runtime claim in JSON format.
222    /// The hash of the data is expected be put into the user_data field of
223    /// the attestation report.
224    #[derive(Debug, Deserialize, Serialize)]
225    #[serde(rename_all = "kebab-case")]
226    pub struct RuntimeClaims {
227        /// An array of [`RsaJwk`]
228        pub keys: Vec<RsaJwk>,
229        /// VM configuration
230        pub vm_configuration: AttestationVmConfig,
231        /// Optional user data
232        #[serde(default, skip_serializing_if = "String::is_empty")]
233        pub user_data: String,
234    }
235
236    impl RuntimeClaims {
237        /// Create runtime claims for `KEY_RELEASE_REQUEST`.
238        pub fn key_release_request_runtime_claims(
239            exponent: &[u8],
240            modulus: &[u8],
241            attestation_vm_config: &AttestationVmConfig,
242        ) -> Self {
243            let transfer_key_jwks = RsaJwk::get_transfer_key_jwks(exponent, modulus);
244            Self {
245                keys: transfer_key_jwks,
246                vm_configuration: attestation_vm_config.clone(),
247                user_data: "".to_string(),
248            }
249        }
250
251        /// Helper function for creating runtime claims of `AK_CERT_REQUEST`.
252        pub fn ak_cert_runtime_claims(
253            ak_pub_exponent: &[u8],
254            ak_pub_modulus: &[u8],
255            ek_pub_exponent: &[u8],
256            ek_pub_modulus: &[u8],
257            attestation_vm_config: &AttestationVmConfig,
258            user_data: &[u8],
259        ) -> Self {
260            let tpm_jwks = RsaJwk::get_tpm_jwks(
261                ak_pub_exponent,
262                ak_pub_modulus,
263                ek_pub_exponent,
264                ek_pub_modulus,
265            );
266            Self {
267                keys: tpm_jwks,
268                vm_configuration: attestation_vm_config.clone(),
269                user_data: hex::encode(user_data),
270            }
271        }
272    }
273
274    /// JWK for an RSA key
275    #[derive(Debug, Deserialize, Serialize)]
276    pub struct RsaJwk {
277        /// Key id
278        pub kid: String,
279        /// Key operations
280        pub key_ops: Vec<String>,
281        /// Key type
282        pub kty: String,
283        /// RSA public exponent
284        #[serde(with = "Base64Url")]
285        pub e: Vec<u8>,
286        /// RSA public modulus
287        #[serde(with = "Base64Url")]
288        pub n: Vec<u8>,
289    }
290
291    impl RsaJwk {
292        /// Create a JWKS from inputs.
293        pub fn get_transfer_key_jwks(exponent: &[u8], modulus: &[u8]) -> Vec<RsaJwk> {
294            let jwk = RsaJwk {
295                kid: "HCLTransferKey".to_string(),
296                key_ops: vec!["encrypt".to_string()],
297                kty: "RSA".to_string(),
298                e: exponent.to_vec(),
299                n: modulus.to_vec(),
300            };
301
302            vec![jwk]
303        }
304
305        /// Create a JWKS from inputs.
306        pub fn get_tpm_jwks(
307            ak_pub_exponent: &[u8],
308            ak_pub_modulus: &[u8],
309            ek_pub_exponent: &[u8],
310            ek_pub_modulus: &[u8],
311        ) -> Vec<RsaJwk> {
312            let ak_pub = RsaJwk {
313                kid: "HCLAkPub".to_string(),
314                key_ops: vec!["sign".to_string()],
315                kty: "RSA".to_string(),
316                e: ak_pub_exponent.to_vec(),
317                n: ak_pub_modulus.to_vec(),
318            };
319            let ek_pub = RsaJwk {
320                kid: "HCLEkPub".to_string(),
321                key_ops: vec!["encrypt".to_string()],
322                kty: "RSA".to_string(),
323                e: ek_pub_exponent.to_vec(),
324                n: ek_pub_modulus.to_vec(),
325            };
326
327            vec![ak_pub, ek_pub]
328        }
329    }
330
331    /// VM configuration to be included in the `RuntimeClaims`.
332    #[derive(Clone, Debug, Deserialize, Serialize, MeshPayload)]
333    #[serde(rename_all = "kebab-case")]
334    pub struct AttestationVmConfig {
335        /// Time stamp
336        #[serde(skip_serializing_if = "Option::is_none")]
337        pub current_time: Option<i64>,
338        /// Base64-encoded hash of the provisioning cert
339        pub root_cert_thumbprint: String,
340        /// Whether the serial console is enabled
341        pub console_enabled: bool,
342        /// Whether secure boot is enabled
343        pub secure_boot: bool,
344        /// Whether the TPM is enabled
345        pub tpm_enabled: bool,
346        /// Whether the TPM states is persisted
347        pub tpm_persisted: bool,
348        /// VM id
349        #[serde(rename = "vmUniqueId")]
350        pub vm_unique_id: String,
351    }
352}