openhcl_attestation_protocol/igvm_attest/
get.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! The module helps preparing requests and parsing responses that are
//! sent to and received from the IGVm agent runs on the host via GET
//! `IGVM_ATTEST` host request.

use open_enum::open_enum;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;

const ATTESTATION_VERSION: u32 = 2;
const ATTESTATION_SIGNATURE: u32 = 0x414c4348; // 'HCLA'
/// The value is based on the maximum report size of the supported isolated VM
/// Currently it's the size of a SNP report.
const ATTESTATION_REPORT_SIZE_MAX: usize = SNP_VM_REPORT_SIZE;

pub const VBS_VM_REPORT_SIZE: usize = 0x230;
pub const SNP_VM_REPORT_SIZE: usize = sev_guest_device::protocol::SNP_REPORT_SIZE;
pub const TDX_VM_REPORT_SIZE: usize = tdx_guest_device::protocol::TDX_REPORT_SIZE;
/// No TEE attestation report for TVM
pub const TVM_REPORT_SIZE: usize = 0;

const PAGE_SIZE: usize = 4096;

/// Number of pages required by the response buffer of WRAPPED_KEY request
/// Currently the number matches the maximum value defined by `get_protocol`
pub const WRAPPED_KEY_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
/// Number of pages required by the response buffer of KEY_RELEASE request
/// Currently the number matches the maximum value defined by `get_protocol`
pub const KEY_RELEASE_RESPONSE_BUFFER_SIZE: usize = 16 * PAGE_SIZE;
/// Number of pages required by the response buffer of AK_CERT request
/// Currently the AK cert request only requires 1 page.
pub const AK_CERT_RESPONSE_BUFFER_SIZE: usize = PAGE_SIZE;

/// Current AK cert response header version
pub const AK_CERT_RESPONSE_HEADER_VERSION: u32 = 1;

/// Request structure (C-style)
/// The struct (includes the appended [`runtime_claims::RuntimeClaims`]) also serves as the
/// attestation report in vTPM guest attestation.
#[repr(C)]
#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestRequest {
    /// Header (unmeasured)
    pub header: IgvmAttestRequestHeader,
    /// TEE attestation report
    pub attestation_report: [u8; ATTESTATION_REPORT_SIZE_MAX],
    /// Request data (unmeasured)
    pub request_data: IgvmAttestRequestData,
    // Variable-length [`runtime_claims::RuntimeClaims`] (JSON string) in raw bytes will be
    // appended to here.
    // The hash of [`runtime_claims::RuntimeClaims`] in [`IgvmAttestHashType`] will be captured
    // in the `report_data` or equivalent field of the TEE attestation report.
}

open_enum! {
    /// TEE attestation report type (C-style enum)
    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
    pub enum IgvmAttestReportType: u32 {
        /// Invalid report
        INVALID_REPORT = 0,
        /// VBS report
        VBS_VM_REPORT = 1,
        /// SNP report
        SNP_VM_REPORT = 2,
        /// Trusted VM report
        TVM_REPORT = 3,
        /// TDX report
        TDX_VM_REPORT = 4,
    }
}

open_enum! {
    /// Request type (C-style enum)
    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
    pub enum IgvmAttestRequestType: u32 {
        /// Invalid request
        INVALID_REQUEST = 0,
        /// Request for getting wrapped key from AKV.
        KEY_RELEASE_REQUEST = 1,
        /// Request to getting attestation key certificate.
        AK_CERT_REQUEST = 2,
        /// Request for getting VMMD blob from CPS.
        WRAPPED_KEY_REQUEST = 3,
    }
}

open_enum! {
    /// Hash algorithm used for content of report data (C-style enum)
    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
    pub enum IgvmAttestHashType: u32 {
        /// Invalid hash
        INVALID_HASH = 0,
        /// SHA-256
        SHA_256 = 1,
        /// SHA-384
        SHA_384 = 2,
        /// SHA-512
        SHA_512 = 3,
    }
}

/// Unmeasured data used to provide transport sanity and versioning (C-style struct)
#[repr(C)]
#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestRequestHeader {
    /// Signature
    pub signature: u32,
    /// Version
    pub version: u32,
    /// Report size
    pub report_size: u32,
    /// Request type
    pub request_type: IgvmAttestRequestType,
    /// Status
    pub status: u32,
    /// Reserved
    pub reserved: [u32; 3],
}

impl IgvmAttestRequestHeader {
    /// Create an `HardwareKeyProtectorHeader` instance.
    pub fn new(report_size: u32, request_type: IgvmAttestRequestType, status: u32) -> Self {
        Self {
            signature: ATTESTATION_SIGNATURE,
            version: ATTESTATION_VERSION,
            report_size,
            request_type,
            status,
            reserved: [0u32; 3],
        }
    }
}

const IGVM_ATTEST_VERSION_CURRENT: u32 = 1;

/// Unmeasured user data, used for host attestation requests (C-style struct)
#[repr(C)]
#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestRequestData {
    /// Data size
    pub data_size: u32,
    /// Version
    pub version: u32,
    /// Report type
    pub report_type: IgvmAttestReportType,
    /// Report data hash type
    pub report_data_hash_type: IgvmAttestHashType,
    /// Size of the appended raw runtime claims
    pub variable_data_size: u32,
}

impl IgvmAttestRequestData {
    /// Create an `IgvmAttestRequestData` instance.
    pub fn new(
        data_size: u32,
        report_type: IgvmAttestReportType,
        report_data_hash_type: IgvmAttestHashType,
        variable_data_size: u32,
    ) -> Self {
        Self {
            data_size,
            version: IGVM_ATTEST_VERSION_CURRENT,
            report_type,
            report_data_hash_type,
            variable_data_size,
        }
    }
}

/// The response header for `KEY_RELEASE_REQUEST` (C-style struct)
///
/// reSearch query: `IGVM_KEY_MESSAGE_HEADER`
#[repr(C)]
#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestKeyReleaseResponseHeader {
    /// Data size
    pub data_size: u32,
    /// Version
    pub version: u32,
}

/// The response header for `WRAPPED_KEY_REQUEST` (C-style struct)
/// Currently the definition is the same as [`IgvmAttestKeyReleaseResponseHeader`].
///
/// reSearch query: `IGVM_KEY_MESSAGE_HEADER`
#[repr(C)]
#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestWrappedKeyResponseHeader {
    /// Data size
    pub data_size: u32,
    /// Version
    pub version: u32,
}

/// The response header for `AK_CERT_REQUEST` (C-style struct)
///
/// reSearch query: `IGVM_CERT_MESSAGE_HEADER`
#[repr(C)]
#[derive(Default, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
pub struct IgvmAttestAkCertResponseHeader {
    /// Data size
    pub data_size: u32,
    /// Version
    pub version: u32,
}

/// Definition of the runt-time claims, which will be appended to the
/// `IgvmAttestRequest` in raw bytes.
pub mod runtime_claims {
    use base64_serde::base64_serde_type;
    use mesh::MeshPayload;
    use serde::Deserialize;
    use serde::Serialize;

    base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);

    /// Measured runtime claim in JSON format.
    /// The hash of the data is expected be put into the user_data field of
    /// the attestation report.
    #[derive(Debug, Deserialize, Serialize)]
    #[serde(rename_all = "kebab-case")]
    pub struct RuntimeClaims {
        /// An array of [`RsaJwk`]
        pub keys: Vec<RsaJwk>,
        /// VM configuration
        pub vm_configuration: AttestationVmConfig,
        /// Optional user data
        #[serde(default, skip_serializing_if = "String::is_empty")]
        pub user_data: String,
    }

    impl RuntimeClaims {
        /// Create runtime claims for `KEY_RELEASE_REQUEST`.
        pub fn key_release_request_runtime_claims(
            exponent: &[u8],
            modulus: &[u8],
            attestation_vm_config: &AttestationVmConfig,
        ) -> Self {
            let transfer_key_jwks = RsaJwk::get_transfer_key_jwks(exponent, modulus);
            Self {
                keys: transfer_key_jwks,
                vm_configuration: attestation_vm_config.clone(),
                user_data: "".to_string(),
            }
        }

        /// Helper function for creating runtime claims of `AK_CERT_REQUEST`.
        pub fn ak_cert_runtime_claims(
            ak_pub_exponent: &[u8],
            ak_pub_modulus: &[u8],
            ek_pub_exponent: &[u8],
            ek_pub_modulus: &[u8],
            attestation_vm_config: &AttestationVmConfig,
            user_data: &[u8],
        ) -> Self {
            let tpm_jwks = RsaJwk::get_tpm_jwks(
                ak_pub_exponent,
                ak_pub_modulus,
                ek_pub_exponent,
                ek_pub_modulus,
            );
            Self {
                keys: tpm_jwks,
                vm_configuration: attestation_vm_config.clone(),
                user_data: hex::encode(user_data),
            }
        }
    }

    /// JWK for an RSA key
    #[derive(Debug, Deserialize, Serialize)]
    pub struct RsaJwk {
        /// Key id
        pub kid: String,
        /// Key operations
        pub key_ops: Vec<String>,
        /// Key type
        pub kty: String,
        /// RSA public exponent
        #[serde(with = "Base64Url")]
        pub e: Vec<u8>,
        /// RSA public modulus
        #[serde(with = "Base64Url")]
        pub n: Vec<u8>,
    }

    impl RsaJwk {
        /// Create a JWKS from inputs.
        pub fn get_transfer_key_jwks(exponent: &[u8], modulus: &[u8]) -> Vec<RsaJwk> {
            let jwk = RsaJwk {
                kid: "HCLTransferKey".to_string(),
                key_ops: vec!["encrypt".to_string()],
                kty: "RSA".to_string(),
                e: exponent.to_vec(),
                n: modulus.to_vec(),
            };

            vec![jwk]
        }

        /// Create a JWKS from inputs.
        pub fn get_tpm_jwks(
            ak_pub_exponent: &[u8],
            ak_pub_modulus: &[u8],
            ek_pub_exponent: &[u8],
            ek_pub_modulus: &[u8],
        ) -> Vec<RsaJwk> {
            let ak_pub = RsaJwk {
                kid: "HCLAkPub".to_string(),
                key_ops: vec!["sign".to_string()],
                kty: "RSA".to_string(),
                e: ak_pub_exponent.to_vec(),
                n: ak_pub_modulus.to_vec(),
            };
            let ek_pub = RsaJwk {
                kid: "HCLEkPub".to_string(),
                key_ops: vec!["encrypt".to_string()],
                kty: "RSA".to_string(),
                e: ek_pub_exponent.to_vec(),
                n: ek_pub_modulus.to_vec(),
            };

            vec![ak_pub, ek_pub]
        }
    }

    /// VM configuration to be included in the `RuntimeClaims`.
    #[derive(Clone, Debug, Deserialize, Serialize, MeshPayload)]
    #[serde(rename_all = "kebab-case")]
    pub struct AttestationVmConfig {
        /// Time stamp
        #[serde(skip_serializing_if = "Option::is_none")]
        pub current_time: Option<i64>,
        /// Base64-encoded hash of the provisioning cert
        pub root_cert_thumbprint: String,
        /// Whether the serial console is enabled
        pub console_enabled: bool,
        /// Whether secure boot is enabled
        pub secure_boot: bool,
        /// Whether the TPM is enabled
        pub tpm_enabled: bool,
        /// Whether the TPM states is persisted
        pub tpm_persisted: bool,
        /// VM id
        #[serde(rename = "vmUniqueId")]
        pub vm_unique_id: String,
    }
}