underhill_attestation/igvm_attest/
mod.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 base64_serde::base64_serde_type;
9use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestHashType;
10use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestReportType;
11use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestType;
12use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
13use tee_call::TeeType;
14use thiserror::Error;
15use zerocopy::FromZeros;
16use zerocopy::IntoBytes;
17
18pub mod ak_cert;
19pub mod key_release;
20pub mod wrapped_key;
21
22base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
23
24#[expect(missing_docs)] // self-explanatory fields
25#[derive(Debug, Error)]
26pub enum Error {
27    #[error(
28        "the size of the attestation report {report_size} is invalid, expected {expected_size}"
29    )]
30    InvalidAttestationReportSize {
31        report_size: usize,
32        expected_size: usize,
33    },
34}
35
36/// Rust-style enum for `IgvmAttestReportType`
37pub enum ReportType {
38    /// VBS report
39    // TODO VBS
40    #[expect(dead_code)]
41    Vbs,
42    /// SNP report
43    Snp,
44    /// TDX report
45    Tdx,
46    /// Trusted VM report
47    Tvm,
48}
49
50impl ReportType {
51    /// Map the value to `IgvmAttestReportType`
52    fn to_external_type(&self) -> IgvmAttestReportType {
53        match self {
54            Self::Vbs => IgvmAttestReportType::VBS_VM_REPORT,
55            Self::Snp => IgvmAttestReportType::SNP_VM_REPORT,
56            Self::Tdx => IgvmAttestReportType::TDX_VM_REPORT,
57            Self::Tvm => IgvmAttestReportType::TVM_REPORT,
58        }
59    }
60}
61
62/// Helper struct to create `IgvmAttestRequest` in raw bytes.
63pub struct IgvmAttestRequestHelper {
64    /// The request type.
65    request_type: IgvmAttestRequestType,
66    /// The report type.
67    report_type: ReportType,
68    /// Raw bytes of `RuntimeClaims`.
69    runtime_claims: Vec<u8>,
70    /// The hash of the `runtime_claims` to be included in the
71    /// `report_data` field of the attestation report.
72    runtime_claims_hash: [u8; tee_call::REPORT_DATA_SIZE],
73    /// THe hash type of the `runtime_claims_hash`.
74    hash_type: IgvmAttestHashType,
75}
76
77impl IgvmAttestRequestHelper {
78    /// Prepare the data necessary for creating the `KEY_RELEASE` request.
79    pub fn prepare_key_release_request(
80        tee_type: TeeType,
81        rsa_exponent: &[u8],
82        rsa_modulus: &[u8],
83        host_time: i64,
84        attestation_vm_config: &AttestationVmConfig,
85    ) -> Self {
86        let report_type = match tee_type {
87            TeeType::Snp => ReportType::Snp,
88            TeeType::Tdx => ReportType::Tdx,
89        };
90
91        let attestation_vm_config =
92            attestation_vm_config_with_time(attestation_vm_config, host_time);
93        let runtime_claims =
94            openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::key_release_request_runtime_claims(rsa_exponent, rsa_modulus, &attestation_vm_config);
95        let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
96
97        let hash_type = IgvmAttestHashType::SHA_256;
98        let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
99        let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
100        runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
101
102        Self {
103            request_type: IgvmAttestRequestType::KEY_RELEASE_REQUEST,
104            report_type,
105            runtime_claims,
106            runtime_claims_hash,
107            hash_type,
108        }
109    }
110
111    /// Prepare the data necessary for creating the `AK_CERT` request.
112    pub fn prepare_ak_cert_request(
113        tee_type: Option<TeeType>,
114        ak_pub_exponent: &[u8],
115        ak_pub_modulus: &[u8],
116        ek_pub_exponent: &[u8],
117        ek_pub_modulus: &[u8],
118        attestation_vm_config: &AttestationVmConfig,
119        guest_input: &[u8],
120    ) -> Self {
121        let report_type = match tee_type {
122            Some(TeeType::Snp) => ReportType::Snp,
123            Some(TeeType::Tdx) => ReportType::Tdx,
124            None => ReportType::Tvm,
125        };
126
127        let runtime_claims =
128            openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::ak_cert_runtime_claims(
129                ak_pub_exponent,
130                ak_pub_modulus,
131                ek_pub_exponent,
132                ek_pub_modulus,
133                attestation_vm_config,
134                guest_input,
135            );
136
137        let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
138
139        let hash_type = IgvmAttestHashType::SHA_256;
140        let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
141        let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
142        runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
143
144        Self {
145            request_type: IgvmAttestRequestType::AK_CERT_REQUEST,
146            report_type,
147            runtime_claims,
148            runtime_claims_hash,
149            hash_type,
150        }
151    }
152
153    /// Return the `runtime_claims_hash`.
154    pub fn get_runtime_claims_hash(&self) -> &[u8; tee_call::REPORT_DATA_SIZE] {
155        &self.runtime_claims_hash
156    }
157
158    /// Set the `request_type`.
159    pub fn set_request_type(&mut self, request_type: IgvmAttestRequestType) {
160        self.request_type = request_type
161    }
162
163    /// Create the request in raw bytes.
164    pub fn create_request(&self, attestation_report: &[u8]) -> Result<Vec<u8>, Error> {
165        create_request(
166            self.request_type,
167            &self.runtime_claims,
168            attestation_report,
169            &self.report_type,
170            self.hash_type,
171        )
172    }
173}
174
175/// Create a request in raw bytes.
176/// A request looks like:
177///     `IgvmAttestRequest` in raw bytes | `runtime_claims` (raw bytes)
178fn create_request(
179    request_type: IgvmAttestRequestType,
180    runtime_claims: &[u8],
181    attestation_report: &[u8],
182    report_type: &ReportType,
183    hash_type: IgvmAttestHashType,
184) -> Result<Vec<u8>, Error> {
185    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequest;
186    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData;
187    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
188
189    let expected_report_size = get_report_size(report_type);
190    if attestation_report.len() != expected_report_size {
191        Err(Error::InvalidAttestationReportSize {
192            report_size: attestation_report.len(),
193            expected_size: expected_report_size,
194        })?
195    }
196
197    let report_size = size_of::<IgvmAttestRequest>() + runtime_claims.len();
198    let user_data_size = size_of::<IgvmAttestRequestData>() + runtime_claims.len();
199    let mut request = IgvmAttestRequest::new_zeroed();
200
201    request.header = IgvmAttestRequestHeader::new(report_size as u32, request_type, 0);
202
203    request.attestation_report[..attestation_report.len()].copy_from_slice(attestation_report);
204
205    request.request_data = IgvmAttestRequestData::new(
206        user_data_size as u32,
207        report_type.to_external_type(),
208        hash_type,
209        runtime_claims.len() as u32,
210    );
211
212    Ok([request.as_bytes(), runtime_claims].concat())
213}
214
215/// Get the expected size of the given report type.
216fn get_report_size(report_type: &ReportType) -> usize {
217    match report_type {
218        ReportType::Vbs => openhcl_attestation_protocol::igvm_attest::get::VBS_VM_REPORT_SIZE,
219        ReportType::Snp => openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE,
220        ReportType::Tdx => openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE,
221        ReportType::Tvm => openhcl_attestation_protocol::igvm_attest::get::TVM_REPORT_SIZE,
222    }
223}
224
225/// Helper function that returns the given config with the `current_time` set.
226fn attestation_vm_config_with_time(
227    vm_config: &AttestationVmConfig,
228    host_epoch: i64,
229) -> AttestationVmConfig {
230    let mut vm_config = vm_config.clone();
231    vm_config.current_time = Some(host_epoch);
232    vm_config
233}
234
235/// Helper function that converts the `RuntimeClaims` to raw bytes.
236fn runtime_claims_to_bytes(
237    runtime_claims: &openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims,
238) -> Vec<u8> {
239    let runtime_claims = serde_json::to_string(runtime_claims).expect("JSON serialization failed");
240    runtime_claims.as_bytes().to_vec()
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_create_request() {
249        let result = create_request(
250            IgvmAttestRequestType::AK_CERT_REQUEST,
251            &[],
252            &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE],
253            &ReportType::Snp,
254            IgvmAttestHashType::SHA_256,
255        );
256        assert!(result.is_ok());
257
258        let result = create_request(
259            IgvmAttestRequestType::AK_CERT_REQUEST,
260            &[],
261            &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE + 1],
262            &ReportType::Snp,
263            IgvmAttestHashType::SHA_256,
264        );
265        assert!(result.is_err());
266    }
267
268    #[test]
269    fn test_transfer_key_jwk() {
270        const EXPECTED_JWK: &str = r#"[{"kid":"HCLTransferKey","key_ops":["encrypt"],"kty":"RSA","e":"RVhQT05FTlQ","n":"TU9EVUxVUw"}]"#;
271
272        let rsa_jwk = openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RsaJwk::get_transfer_key_jwks(
273            b"EXPONENT",
274            b"MODULUS",
275        );
276
277        let result = serde_json::to_string(&rsa_jwk);
278        assert!(result.is_ok());
279
280        let transfer_key_jwk = result.unwrap();
281        assert_eq!(transfer_key_jwk, EXPECTED_JWK);
282    }
283
284    #[test]
285    fn test_vm_configuration_no_time() {
286        const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"vmUniqueId":""}"#;
287
288        let attestation_vm_config = AttestationVmConfig {
289            current_time: None,
290            root_cert_thumbprint: String::new(),
291            console_enabled: false,
292            secure_boot: false,
293            tpm_enabled: false,
294            tpm_persisted: false,
295            vm_unique_id: String::new(),
296        };
297        let result = serde_json::to_string(&attestation_vm_config);
298        assert!(result.is_ok());
299
300        let vm_config = result.unwrap();
301        assert_eq!(vm_config, EXPECTED_JWK);
302    }
303
304    #[test]
305    fn test_vm_configuration_with_time() {
306        const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"vmUniqueId":""}"#;
307
308        let attestation_vm_config = AttestationVmConfig {
309            current_time: None,
310            root_cert_thumbprint: String::new(),
311            console_enabled: false,
312            secure_boot: false,
313            tpm_enabled: false,
314            tpm_persisted: false,
315            vm_unique_id: String::new(),
316        };
317        let attestation_vm_config =
318            attestation_vm_config_with_time(&attestation_vm_config, 1691103220);
319        let result = serde_json::to_string(&attestation_vm_config);
320        assert!(result.is_ok());
321
322        let vm_config = result.unwrap();
323        assert_eq!(vm_config, EXPECTED_JWK);
324    }
325}